Веб-поиск и парсинг веб-страниц
Веб-поиск используется для дополнения информации за пределами внутренней базы знаний. Агент ищет результаты с помощью инструмента web_search, а затем читает основной текст веб-страницы с помощью web_fetch. Можно подключить внешние поисковые службы или развернуть SearXNG.
Перейдите в «Настройки → Веб-поиск», выберите провайдера, введите учетные данные и протестируйте подключение, затем выберите эту поисковую конфигурацию в своем Агенте. Количество результатов поиска ограничено настройкой максимального количества результатов в конфигурации Агента.
Поддерживаемые поисковые системы
Поисковые системы регистрируются в методе registerWebSearchProviders в internal/container/container.go:
registry.Register("duckduckgo", infra_web_search.NewDuckDuckGoProvider)
registry.Register("google", infra_web_search.NewGoogleProvider)
registry.Register("bing", infra_web_search.NewBingProvider)
registry.Register("tavily", infra_web_search.NewTavilyProvider)
registry.Register("ollama", infra_web_search.NewOllamaProvider)
registry.Register("baidu", infra_web_search.NewBaiduProvider)
registry.Register("searxng", infra_web_search.NewSearxngProvider)
registry.Register("keenable", infra_web_search.NewKeenableProvider)
registry.Register("zhipu", infra_web_search.NewZhipuProvider)
registry.Register("exa", infra_web_search.NewExaProvider)
registry.Register("metaso", infra_web_search.NewMetasoProvider)
registry.Register("bocha", infra_web_search.NewBochaProvider)
registry.Register("brave", infra_web_search.NewBraveProvider)
registry.Register("serply", infra_web_search.NewSerplyProvider)| Движок | Файл исходного кода | Нужен ли API Key | Точка входа (Endpoint) | Примечание |
|---|---|---|---|---|
| DuckDuckGo | duckduckgo.go | Нет | Сначала HTML-парсинг, затем API в качестве резерва | Бесплатно; можно настроить proxy_url |
google.go | Да (также нужен engine_id) | Google Custom Search API (официальный SDK customsearch/v1) | ||
| Bing | bing.go | Да | https://api.bing.microsoft.com/v7.0/search (захардкожено) | |
| Tavily | tavily.go | Да | https://api.tavily.com/search (захардкожено) | |
| Ollama Web Search | ollama.go | Да | https://ollama.com/api/web_search (захардкожено) | До 10 результатов |
| Baidu Qianfan AI Search | baidu.go | Да | https://qianfan.baidubce.com/v2/ai_search/web_search (захардкожено) | |
| SearXNG | searxng.go | Нет | Пользователь заполняет base_url самостоятельно (селф-хост инстанс) | Единственный движок, допускающий собственный URL, проходит проверку SSRF |
| Keenable | keenable.go | Опционально | https://api.keenable.ai (захардкожено) | Без ключа действует публичное ограничение скорости, с ключом лимиты снимаются |
| Zhipu Search | zhipu.go | Да | https://open.bigmodel.cn/api/paas/v4/web_search (захардкожено), дефолтный движок search_std | |
| Metaso | metaso.go | Да | https://metaso.cn/api/v1/search | Выберите диапазон ресурсов через extra_config.scope, по умолчанию webpage |
| Exa | exa.go | Да | https://api.exa.ai/search | По умолчанию highlights, используйте extra_config.include_text для получения основного текста |
| Bocha | bocha.go | Да | https://api.bochaai.com/v1/web-search | extra_config.freshness, summary |
| Brave Search | brave.go | Да | https://api.search.brave.com/res/v1/web/search | Поддержка передачи country/freshness для каждого запроса |
| Serply | serply.go | Да | https://api.serply.io/v1/search | Результаты Google; поддержка передачи country/freshness (только pd/pw/pm/py) |
На данный момент зарегистрировано 14 поисковых систем.
| Дополнительные конфигурации провайдеров | Значение |
|---|---|
| Metaso scope | webpage (по умолчанию), document, scholar, podcast, video, image |
| Exa include_text | Строковое булево значение, например "true"; по умолчанию текст не извлекается |
| Bocha freshness | noLimit (по умолчанию), oneDay, oneWeek, oneMonth, oneYear |
| Bocha summary | Запрашивать сводку по умолчанию; выключить, если задано "false" |
| Brave фильтрация | Параметры country/freshness инструмента web_search, см. ниже; отличаются от фиксированной конфигурации Bocha |
| Serply фильтрация | Так же, как Brave; country преобразуется в параметр gl от Google, freshness принимает только pd/pw/pm/py, временные интервалы не поддерживаются |
За исключением SearXNG, точки входа всех остальных поисковых движков захардкожены и не могут настраиваться клиентами. Это первая линия защиты от SSRF (комментарий в исходном коде: Not configurable by tenants — prevents SSRF).
Конфигурация поисковых систем (сущность Provider)
В каждом рабочем пространстве можно создать несколько конфигураций поисковых систем (например, "Production Bing", "Test Google"). Они сохраняются в таблице web_search_providers в виде WebSearchProviderEntity (internal/types/web_search_provider.go), а Агент ссылается на них по ID. Структура параметров WebSearchProviderParameters:
| Название | Тип | По умолчанию | Описание |
|---|---|---|---|
api_key | string | пусто | Ключ доступа, шифруется AES-GCM; изменяется только через /credentials, никогда не возвращается в ответах API |
engine_id | string | пусто | Требуется только для Google Custom Search |
base_url | string | пусто | Только для SearXNG: адрес селф-хост инстанса; проходит проверку utils.ValidateURLForSSRF, адреса внутренних сетей должны добавляться в SSRF_WHITELIST |
proxy_url | string | пусто | Опциональный прокси-сервер HTTP/HTTPS (только туннелирование трафика, не заменяет эндпоинты API), также проверяется на SSRF |
extra_config | map[string]string | nil | Специфичные для провайдеров параметры, такие как Metaso scope, Exa include_text, Bocha freshness/summary |
CRUD маршруты (RegisterWebSearchProviderRoutes, internal/router/routes_infra.go): просмотр и изменение под /web-search-providers, POST /test (проверка соединения с незаписанными параметрами), POST /:id/test (тестирование сохраненной конфигурации), PUT /:id/credentials и DELETE /:id/credentials/:field. Тестирование и операции изменения требуют прав Администратора; также есть GET /web-search/providers, возвращающий справочник доступных типов поисковых систем. Полное описание интерфейсов см. в Инфраструктурном API.
Поиск Агента и чтение страниц
Инструмент web_search отвечает за обнаружение источников, а web_fetch — за чтение выбранных страниц. Если пользователь явно передает URL веб-страницы, она может быть прочитана напрямую; когда требуется внешняя или актуальная информация, Агент может выполнить поиск. Надобность обращения к поиску по базе знаний зависит от релевантности задачи и доступных инструментов, принудительное предварительное использование search_knowledge больше не требуется.
web_search
Примеры вызова:
{"query":"Python release notes","count":5}
{"query":"Rust release notes","country":"DE","freshness":"pw","content":true}- Параметр
countзадает число возвращаемых результатов, в диапазоне от 1 до максимального числа результатов в настройках Агента (максимум 20). По умолчанию используется значение Агента. При превышении верхнего предела значение обрезается до предела, а в видимый для модели вывод добавляется корректирующая подсказка — сам вызов при этом не завершается ошибкой; значения меньше 1 возвращают ошибку параметра. - Параметры
countryиfreshnessзадействуются провайдерами Brave и Serply. Код страны принимает двухбуквенный формат илиALL(для глобальных результатов). Временное ограничение принимает форматыpd/pw/pm/pyили интервалYYYY-MM-DDtoYYYY-MM-DD. Еслиcountryопущен для Brave, параметр не передается (Brave по умолчанию использует US). Для Serply параметрcountryмапится вglот Google (если опущен или равенALL,glне передается), аfreshnessпринимает строгоpd/pw/pm/py. Остальные провайдеры эти параметры не поддерживают — их явная передача вернет ошибку, а не будет молча проигнорирована. Возможные значения см. в Официальной документации API Brave. - Параметр
contentпо умолчанию отключен. При установке вtrueАгент параллельно скачивает текст первых 3 страниц (бюджет времени 15 секунд, максимум 5000 символов выдержки со страницы). Остальные результаты сохраняют поисковые аннотации — для них при необходимости нужно запускатьweb_fetch. При сбое скачивания все равно сохраняется аннотация; полный путь к скачанному тексту возвращается черезfull_output_path. Поиск и отдельный инструментweb_fetchразделяют снимки страниц, тайм-ауты не прерывают продолжающийся общий сбор данных. - Относительный параметр времени
ageот Brave сохраняется как есть, чтобы не преобразовывать "2 days ago" в ложную фиксированную дату. - Пустые запросы, недействительные URL и дубликаты отфильтровываются; лимит результатов равен настройке Агента (максимум 20).
- Поиск Агента больше не обращается к
CompressWithRAG, не создает временных баз знаний, не зависит от эмбеддингов/моделей переранжирования или временного состояния Redis. Оптимизация RAG для быстрых ответов чата по-прежнему обрабатывается основным конвейером. - Вывод модели содержит заголовок, доменное имя, дату и внутренний ID страницы
wN. Аннотации и контент от провайдера помечаются как непроверенные источником поисковые улики; ограничение на фрагмент составляет 1500 символов, суммарный бюджет улик на пачку — 16 000 символов.
web_fetch
Примеры вызова:
{"items":[{"url":"w1"},{"url":"https://example.com/guide","limit":4000}]}- Принимаются как известные внутренние ID страниц
wN, так и URL-адреса, предоставленные пользователем или найденные на страницах. Короткие ID раскрываются на границе контекста модели, интерфейс и сохраненный результат содержат настоящие адреса. - Параметр
promptудален из инструмента. Схема выставляет только параметрыurl,offsetиlimit. Вторая модель для аннотирования не вызывается, Агент анализирует основной текст страницы напрямую. - HTML-страницы сначала извлекаются с помощью Readability; в случае успеха контент преобразуется напрямую, в случае неудачи выполняется откат к селекторам
main/article/body, исключая потерю соседних абзацев. После преобразования в Markdown сохраняются заголовки, параграфы, ссылки, таблицы и блоки кода. Относительные ссылки раскрываются относительно конечного HTTP-адреса; встроенные мультимедиа-ресурсы не скачиваются автоматически. - Обычные текстовые форматы, Markdown, JSON/XML считываются напрямую, избегая скрытия тегов типа
<...>как HTML. Двоичные форматы явно возвращаютunsupported_content. - Приоритет отдается HTTP-запросу, однако остается резервный динамический парсинг через Chromium. Сетевые запросы проходят проверку на SSRF, используют защищенный клиент и фиксирование DNS.
- В одном запросе обрабатывается до 8 элементов; одинаковые URL, offset и limit дедуплицируются. Сбои отдельных страниц возвращают локальный статус, успешные страницы возвращаются нормально.
offsetуказывает на сдвиг в символах Unicode с 0, значениеlimitпо умолчанию и верхний предел составляют 8000. В зависимости от бюджета вывода пачки выделяется символьное пространство, возвращаются параметрыoffset,returned_chars,content_lengthи флагtruncated. При наличии остатка возвращаетсяnext_offset.- Чтение продолжается с помощью вызова того же URL с
offset=next_offset. В памяти кэшируется до 8 снимков страниц для продолжения чтения; после вытеснения кэша можно продолжить чтение по возвращенному путиfull_output_path, избегая повторного обращения к сети. В случае истечения срока действия снимка возвращается ошибкаsnapshot_expired(позволяющая Агенту перечитать с offset 0 или использоватьread_file), предотвращая смешивание разных версий страниц. - После скачивания полный текст в Markdown сохраняется в хранилище файлов клиента, к которому относится сессия, возвращается путь
full_output_pathв форматеweb://.... Вызовread_fileможет считывать эти файлы на последующих итерациях без использования песочницы. Доступ проверяется по клиенту, владельцу сессии, сессии, сообщению и веб-странице; обычные вложения не могут быть считаны как веб-страницы. После удаления сообщений или сессии файлы стираются. - В случае ошибки сохранения выловленный текст не теряется: результат будет содержать
storage_error, и дальнейшее чтение будет ограничено кэшем в памяти. Максимальный размер сохраняемого Markdown-файла — 8 МБ. - В
read_fileпараметрoffsetзадает начальную строку с 1, аlimitограничен 2000 строками; максимальное чтение за раз составляет 50 КБ, ограничиваясь также общим бюджетом вывода Агента. При наличии длинных строк возвращаютсяnext_offsetиnext_line_offsetдля продолжения чтения текущей строки; это позволяет Агенту работать без вызова шелл-команд. - Максимальный размер скачиваемой веб-страницы составляет 2 МБ; при превышении возвращается
body_too_largeвместо скрытого усечения страницы. Тайм-аут сетевого запроса составляет 60 секунд. - При сбоях возвращаются стабильные коды ошибок с флагами возможности повторной попытки. Временные сбои могут быть повторены Агентом; постоянные сбои должны привести к переключению на другие источники. Одиночные сбои страниц не должны прерывать весь процесс исследования.
- Если функция интернет-доступа отключена, инструменты
web_searchиweb_fetchне регистрируются в рантайме, даже если они присутствовали в старом массивеallowed_tools. Сбойные страницы не выводятся как подтвержденные источники.
Для быстрого поиска в чате по-прежнему используется NewPipelineFetcher: 15 секунд тайм-аут, лимит скачивания 100 КБ, HTTP-only и извлечение сырого текста.
Роль docker/searxng
SearXNG — это метапоисковик (агрегирующий результаты множества других систем). Он встроен в aihelpme.ru в качестве поискового бэкенда по умолчанию, не требующего API Key, поставляясь в профилях searxng / full в docker-compose.yml:
docker/searxng/settings.yml: Ключевые изменения включают включение форматаjsonвsearch.formats(aihelpme.ru делает запросы к/search?format=json), отключение встроенного лимитера скоростиserver.limiter: false(чтобы aihelpme.ru не блокировалась; при открытом развертывании лимитер нужно включить и настроить список исключений), аsecret_keyзаменяется на значение переменнойSEARXNG_SECRETпри запуске.- Вспомогательный контейнер
searxng-initкопирует шаблоны в отдельный том, исключая перезапись оригинальных конфигураций и попадание сгенерированных ключей в рабочую область git. - Порты на хост-машине задаются переменными
SEARXNG_PORT(дефолт 8888) иSEARXNG_BIND(дефолт127.0.0.1). Не ставьтеSEARXNG_PORTсовпадающим сAPP_PORT(дефолт 8080): это приведет к конфликтам и запросы к приложению будут обрабатываться поисковым движком с возвратом HTML 404. - Контейнер приложения по умолчанию добавляет имя хоста
searxngв белый список SSRF:SSRF_WHITELIST_EXTRA=searxng,qdrant,..., поэтому адресbase_url: http://searxng:8080работает из коробки. - Тайм-аут со стороны aihelpme.ru равен 12 секундам (
defaultSearxngTimeout), что чуть выше, чем тайм-аут SearXNG к внешним источникам (outgoing.max_request_timeout: 10.0), позволяя обрабатывать задержки на уровне бэкенда, а не в виде отмены на клиенте.ValidateSearxngBaseURLиспользуется как при сохранении, так и при запуске, гарантируя целостность валидации.
Ссылки на реализацию
Защита от SSRF для исходящих запросов
Метод NewSearchHTTPClient в internal/infrastructure/web_search/proxy.go создает защищенный HTTP-клиент для всех систем:
DialContextиспользуетutils.SSRFSafeDialContext(проверяет целевой IP во время установки соединения, блокируя DNS rebinding);- Каждый прыжок перенаправления проверяется методом
ssrfSafeRedirectс вызовомValidateURLForSSRF, превышение лимита переходов возвращает ошибку; - Явно заданный
proxy_urlпроходит проверку на SSRF; при его отсутствии используетсяProxyFromEnvironment.
Если включена настройка SSRF_DNS_WHITELIST_ONLY, все внешние поисковые системы, адреса SearXNG и домены веб-страниц, считываемых через web_fetch, должны добавляться в SSRF_WHITELIST, иначе они будут отклонены еще до отправки DNS-запроса, подробнее см. в Руководстве по настройке.
Абстракция интерфейса
Поисковая подсистема описывается двумя интерфейсами (internal/types/interfaces/web_search.go):
// WebSearchProvider defines the interface for web search providers
type WebSearchProvider interface {
Name() string
Search(ctx context.Context, query string, maxResults int, includeDate bool) ([]*types.WebSearchResult, error)
}
// WebSearchService defines the interface for web search services
type WebSearchService interface {
Search(ctx context.Context, providerID string, config *types.WebSearchConfig, query string) ([]*types.WebSearchResult, error)
CompressWithRAG(ctx context.Context, sessionID string, tempKBID string, questions []string, ...) (...)
}Файл internal/infrastructure/web_search/registry.go содержит карту соответствий тип провайдера -> функция-фабрика, экземпляры которых создаются под конкретные запросы клиентов:
type ProviderFactory func(params types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error)
func (r *Registry) Register(id string, factory ProviderFactory)
func (r *Registry) CreateProvider(providerType string, params types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error)Как добавить новую поисковую систему
- Создайте файл
<engine>.goвinternal/infrastructure/web_search/, реализуйте интерфейсinterfaces.WebSearchProvider(Name()+Search()), предоставьте фабрикуfunc New<Engine>Provider(params types.WebSearchProviderParameters) (interfaces.WebSearchProvider, error). Точки входа (Endpoints) должны быть захардкожены в константы, а HTTP-клиент должен собираться черезNewSearchHTTPClient(timeout, params.ProxyURL). - Добавьте константу типа провайдера
WebSearchProviderTypeвinternal/types/web_search_provider.go. - Зарегистрируйте новую фабрику в
registerWebSearchProvidersвinternal/container/container.go:registry.Register("<engine>", infra_web_search.New<Engine>Provider). - При необходимости добавьте проверку специфичных параметров провайдера на уровне сервиса (по аналогии с
ValidateSearxngBaseURL) и обновите справочник в выводеGET /web-search/providersдля фронтенда. - Напишите модульные тесты с использованием
httptestдля симуляции ответов поисковика (см. примеры вsearxng_test.go/zhipu_test.go).
