Веб-поиск в Open-WebUI: путь от энтузиазма к разочарованию
Введение
Языковые модели ограничены данными, на которых обучались. Задай вопрос о событии прошлой недели — и получишь уверенную галлюцинацию. Веб-поиск решает эту проблему: модель сначала ищет актуальную информацию в интернете, а потом формирует ответ на основе найденного. DeepSeek, Perplexity, ChatGPT — все крупные сервисы уже умеют это из коробки.
Open-WebUI1 — self-hosted интерфейс для работы с LLM — тоже поддерживает веб-поиск. Я попытался настроить его так, чтобы качество ответов было сопоставимо с DeepSeek Web. Спойлер: не получилось.
Что такое Open-WebUI
Open-WebUI — это веб-интерфейс для взаимодействия с языковыми моделями. Внешне напоминает ChatGPT: окно чата, история сессий, выбор модели. Но под капотом — self-hosted приложение, которое можно развернуть на своём сервере и подключить к любому LLM-провайдеру.
Основные возможности:
- Мультипровайдер. Подключается к OpenRouter, Ollama, OpenAI, Anthropic и любому OpenAI-совместимому API. Можно использовать несколько провайдеров одновременно и переключаться между моделями в одном интерфейсе.
- Сессии и история. Каждый чат — отдельная сессия с полной историей. Поиск по прошлым разговорам, папки для организации.
- Knowledge Base. Загрузка документов (PDF, Markdown, DOCX) с RAG-поиском. В чате пишешь
#имя_коллекции— модель ищет по загруженным документам. - Tools и Functions. Расширения из маркетплейса: выполнение кода, адаптивная память между сессиями, интеграция с внешними сервисами.
- PWA. Работает как мобильное приложение через Add to Home Screen.
- Веб-поиск. Интеграция с поисковыми движками для получения актуальной информации.
Open-WebUI по сути дела создает самостоятельную платформу для управления моделями и предоставляет веб-интерфейс для взаимодействия с ними. Один инстанс может обслуживать нескольких пользователей с разделением доступа.
Варианты установки
Официальная документация предлагает несколько вариантов развёртывания — от Docker Compose с PostgreSQL, PGVector и Redis до минимального контейнера со встроенной SQLite.
Сложный вариант: PostgreSQL + Redis
Первый инстинкт — развернуть «по-взрослому»:
- PostgreSQL с расширением PGVector для векторного поиска по документам
- Redis (Valkey) для кеширования и синхронизации конфигурации между workers
- 3 uvicorn workers для обработки параллельных запросов
На бумаге выглядит солидно. На практике для single-user deployment это оказалось избыточным. Три контейнера вместо одного, дополнительная сеть для связи с базой, init-скрипт для расширения pgvector, конфигурация Redis.
Хуже того, multi-worker архитектура сломала синхронизацию конфигурации. API-ключ для веб-поиска, введённый через админ-панель, сохранялся в базу, отображался в UI, но при runtime оказывался None — worker, обрабатывающий запрос, не получал обновлённое значение.
Простой вариант: SQLite
Откат к одному контейнеру с SQLite всё исправил. Для одного пользователя этого достаточно с большим запасом:
- Один контейнер ~500 MB RAM
- SQLite — данные, конфигурация, история чатов в одном файле
- Один worker — нет проблем с синхронизацией
- Бекап — один файл
webui.dbв restic
В моём случае Open-WebUI работает за Tailscale, доступен только из VPN-сети. Quadlet-файл минимален:
[Container]
ContainerName=open-webui
Image=ghcr.io/open-webui/open-webui:main
Volume=%h/volumes/open-webui:/app/backend/data:Z
Network=caddy-private.network
EnvironmentFile=%h/.config/containers/systemd/open-webui/open-webui.env
EnvironmentFile=%h/volumes/open-webui/.env
Конфигурация: два мира настроек
У Open-WebUI есть архитектурная особенность, которая сначала вызывает недоумение: конфигурация живёт в двух местах, и они не всегда синхронизированы.
Переменные окружения задают начальные значения при первом запуске. Они определяют подключение к провайдерам, поисковому движку, базовые параметры RAG:
ENABLE_RAG_WEB_SEARCH=true
RAG_WEB_SEARCH_ENGINE=searxng
SEARXNG_QUERY_URL=http://searxng:8080/search?q=<query>&format=json
RAG_WEB_SEARCH_RESULT_COUNT=2
База данных (таблица config в SQLite) хранит фактически используемые значения. После первого запуска Open-WebUI копирует значения env vars в базу — и дальше использует только базу. Изменение переменной окружения после первого запуска ничего не даёт — нужно менять через Admin Panel в веб-интерфейсе.
Это приводит к неочевидному поведению:
- Задаёшь
RAG_WEB_SEARCH_RESULT_COUNT=5в env - Запускаешь контейнер, значение копируется в базу
- Меняешь в env на
2, рестартуешь контейнер - Open-WebUI продолжает использовать
5из базы
Часть настроек вообще недоступна через env и существует только в веб-интерфейсе: system prompt для поиска, режим Function Calling (Default FC / Native FC), параметры чанкинга документов. Это значит, что полностью воспроизвести конфигурацию из одних переменных окружения невозможно. При пересоздании контейнера с чистым volume часть настроек придётся вводить вручную.
На практике я храню env-файл в git как документацию начальных значений, а после развёртывания дохожу до нужной конфигурации через Admin Panel.
Зачем нужен веб-поиск в чате
Языковые модели уверенно отвечают на вопросы в рамках обучающей выборки. Но стоит спросить о событии после даты cutoff — и начинаются галлюцинации. Модель не скажет «я не знаю» — она сгенерирует правдоподобный, но выдуманный ответ.
Веб-поиск снимает это ограничение. Модель формулирует поисковый запрос, получает актуальные результаты, читает найденные страницы и синтезирует ответ с цитатами. Вместо галлюцинации — проверяемый факт со ссылкой на источник.
Сравнение: DeepSeek Web vs Open-WebUI
Чтобы понять, к чему стремиться, достаточно сравнить ответы DeepSeek Web и Open-WebUI на один и тот же вопрос, требующий актуальной информации.
DeepSeek Web:
- Формирует несколько поисковых запросов параллельно
- Загружает десятки страниц за секунды
- Синтезирует структурированный ответ с inline-цитатами
- Указывает на противоречия между источниками
- Результат — детализированный обзор с 10-15 ссылками
Open-WebUI с SearXNG:
- Один поисковый запрос за раз
- Загружает 2 страницы (больше не помещается в контекст)
- Ответ поверхностный, часто на основе одного источника
- Цитирование — если промпт явно требует, и то не всегда
- Результат — краткий пересказ одной-двух страниц
Разница не в моделях — одна и та же модель через DeepSeek Web даёт существенно лучший результат, чем через Open-WebUI. Дело в инфраструктуре поиска: сколько запросов выполняется, сколько страниц загружается, как контент препарируется перед подачей в модель.
Варианты интеграции веб-поиска
Open-WebUI поддерживает несколько поисковых бэкендов. Я попробовал два.
Tavily
Tavily2 — коммерческий API для AI-поиска. Принимает запрос, возвращает очищенные результаты, оптимизированные для LLM. Интеграция тривиальна: API key в переменную окружения.
Плюсы:
- Результаты уже подготовлены для LLM — чистый текст без навигации и рекламы
- Настройка — одна переменная с ключом
Минусы:
- Платный сервис с лимитами (1000 запросов/месяц на бесплатном тарифе)
- Зависимость от внешнего API
- Нет контроля над тем, какие движки используются
SearXNG
SearXNG3 — self-hosted метапоисковый движок. Агрегирует результаты из 246 поисковых движков (Google, Bing, DuckDuckGo, Wikipedia и др.), не хранит данные пользователей, отдаёт результаты в JSON через API.
Плюсы:
- Self-hosted, никаких API ключей и лимитов
- 246 поисковых движков с категориями (general, images, news, science)
- JSON API из коробки
- Уже развёрнут в инфраструктуре для других целей
Минусы:
- Возвращает сниппеты, не полные страницы — Open-WebUI должен сам загружать контент
- Качество сниппетов зависит от конфигурации движков
Для self-hosted deployment выбор очевиден — SearXNG. Не зависишь от внешнего API, не платишь за запросы, полный контроль над конфигурацией.
Сложности и попытки их решить
Проблема 1: сниппеты слишком короткие
В режиме по умолчанию Open-WebUI получает от SearXNG только сниппеты — 2-3 предложения на каждый результат. Этого катастрофически мало для осмысленного ответа. Модель фактически пересказывает обрывки из поисковой выдачи.
Попытка решения: включить загрузку полных страниц (BYPASS_WEB_SEARCH_WEB_LOADER=false). Open-WebUI начинает скачивать HTML, парсить и передавать полный текст в модель.
Проблема 2: контекстное окно переполняется
С включённой загрузкой страниц 10 результатов генерируют сотни тысяч токенов. Контекстное окно модели не резиновое.
Решение: уменьшить количество результатов с 10 до 2. Две полные страницы помещаются в контекст. Но информации из двух источников часто недостаточно для качественного ответа.
Проблема 3: сайты блокируют запросы
Многие сайты возвращают 403 Forbidden на запросы без User-Agent. Open-WebUI по умолчанию отправляет запросы с минимальными заголовками.
Решение: добавить Chrome User-Agent:
USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...
Это помогло с большинством сайтов, хотя CloudFlare-защита по-прежнему блокирует часть запросов.
Проблема 4: модель игнорирует часть результатов
В агентном режиме (Native Function Calling) модель получает инструменты search_web и fetch_url. Предполагается, что она ищет, загружает страницы и анализирует. На практике модель часто вызывала fetch_url только для одного URL из двух, пропуская потенциально релевантный результат.
Попытка решения: специальный system prompt с жёстким правилом:
After each
search_webcall, you MUST callfetch_urlfor EVERY URL in the results. Do not cherry-pick — fetch all of them.
Это помогло частично — модель стала загружать все URL, но не всегда следовала инструкции. Промпт — это просьба, не гарантия.
Проблема 5: одна итерация поиска
DeepSeek Web выполняет множество поисковых запросов — уточняет, переформулирует, ищет в разных направлениях. Open-WebUI в лучшем случае делает один-два запроса. System prompt предусматривал до 3 итераций, но на практике модель обычно останавливалась после первого поиска, считая информацию достаточной.
Решения нет. Это фундаментальное ограничение подхода: Open-WebUI полагается на то, что модель сама решит, когда искать дальше. У DeepSeek Web поисковый пайплайн реализован на уровне инфраструктуры, а не через промпт.
Проблема 6: нет параллельных запросов
Даже когда модель выполняет несколько поисков, они идут последовательно. Каждый search_web → ожидание → fetch_url → ожидание → анализ → следующий запрос. Время ответа растёт линейно с количеством итераций.
Решения нет. Архитектура Function Calling в Open-WebUI последовательная.
Итоги
Веб-поиск в Open-WebUI работает, но результат далёк от того, что дают специализированные сервисы вроде DeepSeek Web или Perplexity. Фундаментальные ограничения:
- Мало источников. 2 страницы вместо десятков — потому что контекстное окно конечно, а Open-WebUI не умеет выделять и ранжировать релевантные фрагменты из большого количества документов.
- Нет параллелизма. Последовательные запросы вместо параллельного сбора информации.
- Промпт, не пайплайн. Поведение определяется system prompt — модель может проигнорировать инструкции, остановиться после первого поиска или пропустить URL.
- Нет специализированной обработки. Нет извлечения ключевых фрагментов, ранжирования по релевантности, дедупликации. Полная страница целиком подаётся в контекст.
В результате я откатился к простой конфигурации: SearXNG с двумя результатами, загрузка полных страниц, bypass embedding. Это лучше, чем ничего — модель хотя бы не галлюцинирует о текущих событиях. Но для задач, требующих глубокого исследования с множеством источников, я по-прежнему использую DeepSeek Web или Perplexity.
Возможно, ситуация улучшится с развитием Open-WebUI — проект активно развивается, и поддержка MCP (Model Context Protocol) в последних версиях открывает новые возможности для интеграции с внешними инструментами. Но на данный момент веб-поиск в self-hosted LLM-интерфейсе — это скорее приятное дополнение, чем полноценная замена специализированных сервисов.