Веб-поиск для локальной LLM без VPN
VPN это не тот инструмент. Он гоняет весь ваш трафик через чужой сервер, часто нестабилен и первым отваливается при блокировках. Для того чтобы модель искала в вебе, есть путь чище: поиск на стороне сервера.
Почему локальная LLM не знает актуальных ответов
Любая модель заморожена на дате окончания обучения. Локальная 7B не знает о вышедшей на этой неделе версии фреймворка, о новом методе в библиотеке или о цене, которая изменилась в прошлом месяце. И поскольку её учили быть полезной, вместо честного "не знаю" она с полной уверенностью выдаёт правдоподобную выдумку. В небольшом тесте локальная Qwen2.5-7B сама по себе ответила верно на 3 из 8 вопросов про актуальные факты. С живыми веб-данными в промпте - на 7 из 8.
Вывод простой: модель нужно не переучивать, а подкладывать ей свежие данные из веба прямо в контекст. Это и называется веб-поиском для локальной LLM, и состоит он не из одного шага, а из пяти.
Конвейер: поиск, загрузка, очистка, ранжирование, промпт
"Веб-поиск для локальной модели" - это на самом деле пять шагов. Сама модель делает только последний:
- Поиск - превратить вопрос в запрос и получить ранжированные URL результатов.
- Загрузка - скачать сами страницы (многие результаты закрыты и возвращаются пустыми).
- Очистка - убрать навигацию, рекламу и обвязку, оставив только контент (в markdown).
- Ранжирование - оставить только пассажи, относящиеся к вопросу. Именно этот шаг делает данные помещающимися в окно.
- Промпт - положить эти пассажи в контекстное окно и дать модели ответить.
Пропустите шаг 4 - и упрётесь в стену, на которой ломается большинство первых попыток.
Стена токенов: почему сырые страницы ломают модель
Локальная модель работает с маленьким окном - тем, что помещается в вашу VRAM, обычно от 4k до 32k токенов, а не с теоретическим максимумом модели. Три полные веб-страницы легко дают более 20 000 токенов.
В нашем замере один набор страниц из выдачи составил 91 039 символов, это примерно 22 759 токенов - в 2,8 раза больше окна на 8k. Модель при этом не отвечает хуже: локальный рантайм (llama.cpp под LM Studio или Ollama) не сокращает ввод сам, а обрезает его или падает с ошибкой. Вы получаете пустой ответ или жёсткую ошибку длины контекста.
Выглядит это так - на вопросе про CEO Anthropic страницы дали около 22 759 токенов, и модель вернула:
HTTP 400: n_keep 29992 >= n_ctx 8192
Та же модель с токен-ограниченным пакетом ответила "Dario Amodei" всего из 3 454 символов. Лекарство - не большее окно, а меньше данных, но правильных: несколько релевантных пассажей, обрезанных под известный бюджет токенов. Разбор измеренного сбоя и решение есть в гайде как дать LLM доступ к вебу.
Подход 1 - свой конвейер на SearXNG
SearXNG - это бесплатный self-hosted метапоисковик. Именно он стоит поисковым бэкендом за Perplexica (в README прямо сказано "Web search powered by SearxNG") и похожими self-hosted RAG-сборками, как раз потому что он бесплатный. Но он закрывает только шаг 1. Загрузку, очистку и ранжирование вы пишете сами, а на публичных инстансах ловите лимиты и блокировки.
# Только шаг 1: спрашиваем инстанс SearXNG и получаем URL результатов import requests def searxng_search(query, instance="http://localhost:8080"): r = requests.get(f"{instance}/search", params={"q": query, "format": "json"}, timeout=15) r.raise_for_status() return [hit["url"] for hit in r.json().get("results", [])[:5]] # Загрузку каждого URL, очистку от обвязки, ранжирование пассажей # и обрезку под окно вы делаете сами - это уже не SearXNG.
Реальные ограничения
- Публичные инстансы лимитируют и блокируют автоматические запросы, так что для надёжности приходится поднимать свой.
format=jsonотключён на многих инстансах, и тогда вы парсите HTML.- Это только поиск. Загрузка, очистка и ранжирование (то, что делает поиск пригодным) - на вас.
Подход 2 - веб-поиск через API Ollama
У Ollama появился хостовый web search API. Он закрывает поиск и возвращает сниппеты - это шаг вперёд по сравнению с сырым SearXNG. Но нужен бесплатный аккаунт Ollama и API-ключ, а возвращает он результаты, а не готовый под контекст пакет, так что шаг с бюджетом токенов всё ещё ваш.
import requests
def ollama_web_search(query, api_key):
r = requests.post("https://ollama.com/api/web_search",
headers={"Authorization": f"Bearer {api_key}"},
json={"query": query}, timeout=30)
r.raise_for_status()
# схема: docs.ollama.com/capabilities/web-search
return r.json() # результаты со сниппетами - обрезка под окно всё ещё на вас
Реальные ограничения
- Нужен аккаунт и ключ - трение для сетапа в духе "локально, без облачных аккаунтов".
- Возвращает результаты и сниппеты. Обрезка под окно модели и переранжирование по релевантности - ваш код.
- Нет контроля над тем, как обрабатываются закрытые страницы.
Если Ollama - ваш основной рантайм, отдельно разобран сценарий подключения в гайде Ollama и доступ в интернет. Сам Ollama не является MCP-хостом, поэтому для инструментов поверх него ставят MCP-совместимый клиент.
Подход 3 - токен-ограниченный пакет доказательств
Три шага, которые реально делают веб-поиск пригодным для локальной модели - загрузить настоящую страницу, очистить её и отранжировать до того, что помещается, - каждый раз одни и те же. Поисковый API, сделанный под LLM, проходит все пять шагов и возвращает токен-ограниченный пакет доказательств: модель получает только те пассажи, что отвечают на вопрос. Grounder - один из таких инструментов (его делаем мы); важнее сам паттерн, чем поставщик.
import requests KEY = "your_grounder_key" # бесплатный тариф: 1500 страниц в месяц, без карты # Один вызов: поиск -> загрузка -> ранжирование -> пакет под ваше окно r = requests.post("https://grounder.dev/v1/deep_search", headers={"Authorization": f"Bearer {KEY}"}, json={"query": "последняя стабильная LTS-версия Node.js", "max_tokens": 600}, # гарантирует, что влезет в модель на 8k timeout=180) pack = r.json() # pack["passages"] - ранжированные пассажи со ссылками, обрезанные под окно evidence = "\n\n".join(p["text"] for p in pack["passages"]) # Передайте evidence локальной модели в промпте - оно останется в пределах окна.
Дальше модель отвечает по данным, которые точно помещаются:
import requests # любой OpenAI-совместимый локальный сервер (LM Studio, Ollama) resp = requests.post("http://localhost:1234/v1/chat/completions", json={ "model": "qwen2.5-7b-instruct", "messages": [{"role": "user", "content": f"Отвечай только по этим данным.\n\nДАННЫЕ:\n{evidence}\n\nВопрос: последняя стабильная LTS-версия Node.js"}] }).json() print(resp["choices"][0]["message"]["content"])
Тот же паттерн лежит в основе более общего разбора - как подключить нейросеть к интернету: инструменты подключаются к MCP-совместимому клиенту, а модель получает уже готовые к контексту данные.
Какой подход подходит
| SearXNG (свой конвейер) | Ollama web search | Токен-ограниченный пакет | |
|---|---|---|---|
| Какие шаги закрывает | Поиск | Поиск + сниппеты | Поиск + загрузка + очистка + ранжирование |
| Помещается в малое окно | Пишете сами | Пишете сами | Да (обрезка по токенам) |
| Закрытые страницы | На вас | Ограниченно | Обрабатываются |
| Нужен аккаунт | Нет (self-host) | Да | Да (есть бесплатный тариф) |
| Операционная нагрузка | Высокая (хостинг + разработка) | Средняя | Низкая |
Россия и вопрос VPN
У локального сценария в России есть отдельная сложность: даже собрав конвейер, шаг загрузки упирается в заблокированные сайты. SearXNG на вашей машине и локальная модель сидят за российским каналом, поэтому нужный источник может просто не открыться, и тогда включают VPN.
Токен-ограниченный пакет снимает это иначе. Grounder делает поиск и читает страницы со своих серверов за пределами России, а вам возвращает уже очищенные пассажи. Заблокированный источник открывается на стороне сервиса, VPN на вашей машине не нужен. Честная граница: это решает повседневную блокировку отдельных сайтов, а не полное отключение по белым спискам - от тотального шатдауна интернета никакой внешний сервис не спасёт.
Прозрачность: Grounder - наш коммерческий продукт, так что интерес у нас есть. Но конвейер из пяти шагов и ограничение по токенам верны независимо от инструмента: SearXNG и API Ollama - реальные и рабочие варианты, а собрать шаг загрузки и ранжирования самому - тоже честный путь. Числа выше взяты из нашего собственного прогона (Qwen2.5-7B, окно 8k, 8 вопросов) - это направляющий тест, а не статистическое исследование. Конкретные номера версий сверяйте с официальным источником.
Частые вопросы
Можно ли сделать веб-поиск для LLM вообще без VPN?
Да. Если поиск и чтение страниц выполняет сервер за пределами России, вашему компьютеру VPN не нужен - достаточно доступа к этому серверу.
Чем это лучше локального поиска вроде SearXNG?
Локальный поиск качает страницы из вашей сети, поэтому упирается в блокировки. grounder читает их снаружи, включая закрытые в РФ источники.