ИИ-поиск по базе знаний прямо на главной: RAG, источники и follow-up за один вызов

Веб Штурм
  • ИИ-поиск
  • RAG
  • LightRAG
  • Astro
  • SSR
  • Pagefind
  • поиск по сайту
  • AEO

На главной webshturm.ru теперь живой ИИ-поиск по базе знаний сайта, встроенный прямо в hero: посетитель задаёт вопрос обычными словами и получает связный ответ из материалов сайта со ссылками на страницы-источники и тремя уточняющими вопросами (стиль Perplexity), а рядом — блок «Точные совпадения» из полнотекстового поиска. Это и удобный поиск, и витрина наших возможностей в ИИ-разработке — клиент видит работающий продукт, а не слайд про «мы умеем ИИ». Ниже — как это устроено: RAG, серверный эндпоинт, три follow-up за один вызов модели, гибрид с полнотекстовым поиском и защита публичного ИИ-эндпоинта.

Зачем встраивать ИИ-поиск в hero

Обычный поиск по сайту возвращает список страниц — дальше посетитель читает сам и сопоставляет. Это работает, но требует усилий: нужно угадать ключевые слова и пролистать выдачу. ИИ-поиск снимает этот шаг — понимает вопрос целиком и сразу даёт ответ по делу.

Мы поставили его не на отдельную страницу, а в самый заметный блок главной. Причина двойная: во-первых, это полезный инструмент навигации по сайту; во-вторых — демонстрация. Компании, которая делает ИИ-решения, логичнее показать работающий ИИ прямо на витрине, чем рассказывать о нём текстом.

При этом мы не отказались от классического поиска: ИИ-ответ основной, а быстрые точные совпадения идут рядом как fallback. Если ИИ временно недоступен — полнотекстовый поиск продолжает работать.

Архитектура: поток запроса

Поток ИИ-поиска: посетитель задаёт вопрос в hero, запрос идёт на серверный эндпоинт сайта, проходит защиту (лимит, кэш, дневной потолок, при подозрении — капча), затем один вызов ИИ к базе знаний сайта возвращает ответ с источниками и тремя уточняющими вопросами; текст печатается тайпрайтером, источники показываются ссылками, follow-up — кнопками; параллельно полнотекстовый поиск даёт блок точных совпадений; если ответа в базе нет — мягкое сообщение с предложением обсудить

Виджет в hero — островок интерактивности (Svelte island) — отправляет вопрос на серверный эндпоинт сайта (SSR, рендер на запрос). Эндпоинт сначала проходит защиту, затем делает один запрос к базе знаний и возвращает уже разобранный результат: текст ответа, массив источников и до трёх follow-up-вопросов. Сама модель и база знаний живут на серверной стороне — браузер никогда не обращается к ним напрямую.

RAG: ответ только из базы знаний сайта

Под капотом — RAG (retrieval-augmented generation): система сначала достаёт релевантные фрагменты из базы знаний сайта, а потом модель формулирует ответ только на их основе. Это принципиально отличается от «спросить general-purpose модель»: ответ не из общих знаний модели, а из материалов конкретного сайта.

Жёсткие правила (guardrail) встроены в запрос:

  • Отвечать только из контекста базы знаний компании; не выдумывать.
  • Не обещать конкретные цены, сроки и гарантии — это всегда решение человека.
  • Если ответа в контексте нет — честно сказать, что вопрос лучше обсудить напрямую.

К ответу прикладываются ссылки на страницы-источники. Это и проверяемость (можно открыть первоисточник целиком), и мягкий возврат на нужную страницу сайта. Ссылки формируются с учётом аудитории страницы: для одной версии сайта — канонические адреса, для зеркала «Руководителю» — соответствующие ему пути.

Эвристика «нет ответа»

ИИ-поиск, который уверенно отвечает на всё, опаснее отсутствия поиска — он подрывает доверие. Поэтому есть эвристика обоснованности: если модель отвечает в духе «не нашёл информации» или ответ ничем не подкреплён в базе (нет ни источников, ни ссылок-маркеров) — мы не показываем его. Вместо выдумки посетитель видит мягкое сообщение и предложение обсудить задачу. Честное «не знаю» лучше уверенной ошибки.

Три follow-up за один вызов модели

Follow-up-вопросы (как в Perplexity) помогают углубиться в тему, не формулируя новый запрос с нуля. Наивная реализация — отдельный вызов модели «придумай вопросы по этому ответу». Мы сделали иначе: просим их в том же единственном вызове.

Guardrail завершается просьбой добавить в самом конце скрытый блок вопросов — по одному на строку. На стороне эндпоинта мы парсим этот блок, берём до трёх вопросов и вырезаем его из видимого текста ответа. Посетитель видит чистый ответ плюс три кнопки «Спросить дальше»; клик по кнопке подставляет вопрос и запускает новый поиск.

Выгода одного вызова: дешевле (один запрос к модели вместо двух), быстрее (нет второго round-trip) и дружит с кэшем — весь результат, включая follow-up, кэшируется целиком.

Гибрид с полнотекстовым поиском

Параллельно ИИ-запросу виджет на клиенте обращается к полнотекстовому поиску (Pagefind — статический индекс, собираемый при сборке сайта) и показывает топ-совпадения в сворачиваемом блоке «Точные совпадения». Это даёт лучшее из двух миров: ИИ — для смыслового ответа, полнотекстовый — для точных вхождений и как страховка.

Если полнотекстовый поиск недоступен — блок просто скрыт, ИИ-ответ не страдает. И наоборот: если недоступен ИИ — работает полнотекстовый. Два независимых канала, и оба отказоустойчивы по отдельности.

Кстати, починка самого полнотекстового поиска оказалась отдельной историей: он молча не возвращал результатов из-за политики безопасности контента (CSP), которая блокировала WebAssembly-модуль индекса. Лечится разрешением wasm-unsafe-eval в script-src — а не «пустым индексом», как казалось сначала.

Защита публичного ИИ-эндпоинта

Открытый ИИ-эндпоинт — это и потенциальный абьюз, и расход бюджета модели. Защита — четыре уровня (порядок важен):

  1. Кэш ответов (TTL ~1 час) — повтор одного вопроса бесплатный и мгновенный, не доходит до модели. Ключ кэша учитывает аудиторию страницы, чтобы версии сайта не пересекались по ссылкам-источникам.
  2. Глобальный дневной потолок запросов — защита бюджета модели; при превышении отдаём кэш, если он есть, иначе — мягкий отказ.
  3. Лимит с одного адреса в минуту — типовая защита от флуда.
  4. SmartCaptcha при подозрении — если адрес превысил лимит, запрашиваем капчу; валидный токен пропускает запрос. Мы переиспользовали уже работающую на сайте интеграцию SmartCaptcha (та же, что на форме контактов), не заводя новых ключей.

Ещё одно правило: эндпоинт никогда не отдаёт наружу серверную ошибку из-за модели. Сбой поиска (сеть, таймаут, ошибка) превращается в мягкий ответ «временно недоступно» — а не в 500-ю страницу. Для посетителя деградация плавная: ИИ замолчал — работает обычный поиск.

Честное ограничение: счётчики и кэш — в памяти процесса. Они не переживают перезапуск и не шарятся между репликами. Для одного экземпляра приложения этого достаточно; при горизонтальном масштабировании лимиты и кэш нужно вынести во внешнее хранилище.

Тайпрайтер: ответ «печатается»

Ответ приходит на клиент целиком, но «печатается» посимвольно (тайпрайтер) — это привычная для ИИ-интерфейсов динамика, она делает витрину живой. Мы уважаем системную настройку prefers-reduced-motion: если посетитель отключил анимации — ответ показывается сразу целиком. Область ответа помечена aria-live, чтобы скринридеры озвучивали появление текста.

Что мы из этого вынесли

  • Один вызов модели на ответ + источники + follow-up. Скрытый блок вопросов в том же ответе экономит вызовы, ускоряет отклик и идеально кэшируется. Два вызова там, где хватает одного, — типичная переплата.
  • Эвристика «нет ответа» важнее красивого ответа. ИИ-поиск без неё рано или поздно уверенно соврёт. Отказ показать необоснованный ответ — это фича, а не слабость.
  • Гибрид надёжнее монолита. Два независимых канала (ИИ + полнотекстовый) деградируют по отдельности — посетитель почти никогда не остаётся совсем без поиска.
  • Публичный ИИ-эндпоинт защищается слоями. Кэш гасит повторы, потолок бережёт бюджет, лимит и капча — абьюз. И главное — наружу не уходит 5xx из-за модели.
  • Диагностика обманчива. «Поиск не находит ничего» выглядело как пустой индекс, а корнем была политика безопасности, блокировавшая WebAssembly. Не маскируйте ошибки в клиенте — это съедает часы на ложный след.

Ссылки

Хотите такой же ИИ-поиск по своей базе знаний — на сайте, в поддержке или внутри продукта? Напишите нам, обсудим под ваш контент и требования.

Часто задаваемые вопросы

Чем ИИ-поиск отличается от обычного поиска по сайту?
Обычный поиск ищет совпадения слов и возвращает список страниц — дальше читаете сами. ИИ-поиск понимает вопрос целиком и формулирует связный ответ из базы знаний сайта, прикладывая ссылки на страницы-источники и предлагая три уточняющих вопроса. У нас работают оба сразу: ИИ-ответ основной, а рядом — блок «Точные совпадения» из полнотекстового поиска как быстрый fallback.
Откуда ИИ берёт ответы и не выдумывает ли он?
Ответ строится только из контекста базы знаний сайта (подход RAG), а не из общих знаний модели. Guardrail прямо запрещает выдумывать и обещать конкретные цены, сроки и гарантии. К ответу прикладываются ссылки на страницы-источники, чтобы его можно было проверить. Если в базе нет обоснования (нет источников и ссылок-маркеров) — система не показывает сомнительный ответ, а честно предлагает обсудить вопрос напрямую.
Что происходит, если ответа в базе нет?
Срабатывает эвристика «нет знаний»: если модель отвечает в духе «не нашёл информации» или ответ ничем не обоснован в базе, мы не показываем выдумку. Вместо этого посетитель видит мягкое сообщение и призыв обсудить задачу напрямую — это честнее, чем уверенный, но неверный ответ.
Безопасно ли держать публичный ИИ-эндпоинт открытым для всех?
Да, если защитить его. У нас четыре уровня: лимит запросов с одного адреса в минуту, кэш ответов на час (повтор бесплатный и мгновенный), глобальный дневной потолок запросов для защиты бюджета модели и SmartCaptcha при подозрении на злоупотребление. Эндпоинт никогда не отдаёт наружу серверную ошибку из-за модели — при сбое поиска посетитель видит мягкое «временно недоступно», а обычный поиск продолжает работать.
Зачем три уточняющих вопроса и как они получаются?
Три follow-up-вопроса (в стиле Perplexity) помогают посетителю углубиться в тему без формулирования запроса с нуля. Важная деталь: они генерируются в том же единственном вызове модели — guardrail просит завершить ответ скрытым блоком вопросов, который мы парсим и вырезаем из видимого текста. Один вызов вместо двух — дешевле, быстрее и дружит с кэшем.