• Автор записи:
  • Рубрика записи:Без рубрики
  • Время чтения:2 минут чтения

Как встроить ИИ-поиск по сайту: пошаговый гайд для разработчика

Почему штатный поиск по сайту перестаёт работать

Представьте: интернет-магазин на 4000 товаров, у каждой карточки аккуратно заполнено описание. Пользователь вводит «куртка чтобы не промокнуть» — и получает «Ничего не найдено». Хотя дождевики лежат на второй полке. Причина в том, что классический поиск ищет совпадение подстрок: он не знает, что «не промокнуть» и «непромокаемая» — про одно и то же.

ИИ-поиск решает именно эту задачу. Но сразу снимем розовые очки: нейросеть не «понимает» ваш каталог. Она работает ровно настолько хорошо, насколько хорошо вы подготовили данные. Поэтому половина этого гайда — про данные и инфраструктуру, а не про магию моделей.

Чем ИИ-поиск отличается от поиска по ключевым словам

В основе — эмбеддинги: числовые векторы (обычно от 256 до 3072 чисел), в которые модель переводит текст. Смысл в том, что близкие по смыслу тексты дают близкие векторы. Степень близости считают через косинусное расстояние — по сути, угол между векторами.

Дальше два сценария. Простой: нашли похожие документы — показали список. Продвинутый: нашли — и попросили LLM сформулировать ответ по найденным фрагментам. Второй сценарий называется RAG (Retrieval-Augmented Generation, генерация с опорой на найденные данные).

Что понадобится: минимальный стек

  • Модель эмбеддингов. OpenAI text-embedding-3-small/large, Yandex Embeddings, GigaChat, локальные модели через Ollama (например, bge-m3 или e5-large).
  • Векторное хранилище. pgvector в PostgreSQL — если база уже есть, это самый дешёвый старт. Альтернативы: Qdrant, Weaviate, Supabase Vector.
  • Слой гибридного поиска. Полнотекстовый поиск (BM25 или tsvector в Postgres) плюс векторный.
  • LLM для ответа — только если нужен развёрнутый ответ, а не просто список ссылок.

Для магазина на 5000 товаров и 300 статей отдельный кластер Qdrant не нужен — хватит Postgres. Отдельное хранилище оправдано от сотен тысяч документов или при высокой нагрузке.

Шаг 1. Собираем и чистим контент

Выгружаете всё, что должно находиться: карточки товаров, статьи блога, страницы услуг, FAQ. Дальше — самое нудное и самое важное:

  • уберите технический мусор: меню, футеры, cookie-баннеры, блоки «поделиться»;
  • сведите атрибуты к единому виду: «цвет: чёрный» и «Чёрный цвет» должны стать одним значением;
  • добавьте синонимы и народные названия: «дождевик» = «плащ от дождя»;
  • выкиньте дубли — они засоряют выдачу и съедают бюджет на эмбеддинги.

Если описания уже написаны по-человечески — отлично. Если же их собирали «для SEO» набором ключевых слов, ИИ-поиск будет искать так же плохо, как и раньше. На этапе подготовки тут может помочь LLM: переписать описание в читаемый текст. Результат проверяйте руками — модели любят дописывать характеристики, которых у товара нет.

Шаг 2. Режем на чанки

Чанк — фрагмент документа, который индексируется отдельно. Слишком большой: размывается смысл и растёт цена. Слишком мелкий: теряется контекст.

Рабочие значения: 300–800 токенов с перекрытием 10–15%, чтобы фраза на стыке не разрывалась. Для товаров чанк — это карточка целиком: название, бренд, категория, ключевые атрибуты, описание. Для статей — смысловые блоки по подзаголовкам, а не нарезка «по 500 символов» вслепую.

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

Шаг 3. Считаем эмбеддинги

Проходим по чанкам, отправляем в модель, сохраняем вектор рядом с текстом и метаданными (url, тип, дата). Пакетно, по 100–500 чанков за запрос, с ретраями на ошибку 429.

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
resp = client.embeddings.create(model="text-embedding-3-small", input=batch)
vectors = [d.embedding for d in resp.data]

Порядок цен на момент публикации: text-embedding-3-small — около $0,02 за 1 млн токенов. Каталог из 10 000 карточек по 200 токенов — это 2 млн токенов, то есть примерно $0,04 за первичную индексацию. Дорого здесь не железо, а ваше время на подготовку данных.

Жёсткое правило: модель эмбеддингов нельзя менять «на лету». Сменили модель — пересчитывайте весь индекс, иначе векторы старых и новых документов окажутся в несопоставимых пространствах, и поиск тихо сломается без единой ошибки в логах.

Шаг 4. Складываем в PostgreSQL с pgvector

CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunks (
  id bigserial PRIMARY KEY,
  doc_id bigint NOT NULL,
  url text NOT NULL,
  body text NOT NULL,
  embedding vector(1536)
);
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);

HNSW — индекс для быстрого приближённого поиска ближайших соседей. Он даёт результат не на 100% точный, но в десятки раз быстрее полного перебора, и этой точности в поиске хватает с запасом.

Сам запрос — обычный SQL: SELECT ... ORDER BY embedding <=> $1 LIMIT 20, где <=> — оператор косинусного расстояния в pgvector.

Шаг 5. Делаем поиск гибридным

Чистый вектор плохо справляется с точными совпадениями: артикулы «A-4521» и «A-4523» для модели почти одинаковы, а для покупателя это разные товары. Полнотекстовый поиск, наоборот, идеален для артикулов и брендов, но проваливается на перефразировках.

Поэтому берём оба списка и объединяем. Оценки у них в разных шкалах, складывать их напрямую нельзя, поэтому используют Reciprocal Rank Fusion — объединение по обратным рангам:

score = 1 / (60 + rank_bm25) + 1 / (60 + rank_vector)

Константа 60 пришла из классической работы Cormack et al. и на практике менять её почти никогда не нужно. Дальше топ-20 идёт либо прямо в интерфейс, либо в LLM.

Шаг 6. Добавляем ответ от LLM (RAG)

Если нужен не список ссылок, а связный ответ («да, доставка в Казань — 2 дня, бесплатно от 3000 ₽»), найденные фрагменты отправляются в LLM как контекст. Правила, без которых это не работает:

  • отвечать только по переданным фрагментам, без «общих знаний» модели;
  • если ответа во фрагментах нет — честно сказать «не нашёл» и предложить уточнить запрос;
  • в каждом утверждении — ссылка на источник из выдачи;
  • не отправлять в зарубежный API персональные данные клиентов и внутренние документы, если это не согласовано с политикой обработки данных.

Последний пункт критичен: без ограничений вы гарантированно получите красивый и уверенный ответ про гарантию, которой у вас нет. Проверять это нужно на тестах, а не на проде.

Шаг 7. Интерфейс: мелочи, которые решают

  • Debounce 250–400 мс на ввод — иначе каждый символ улетает в API.
  • Скелетоны вместо спиннера: поиск с эмбеддингами отвечает за 200–800 мс, пустой экран в это время раздражает.
  • Пустое состояние — с популярными запросами и подсказкой «возможно, вы искали».
  • Логирование запросов с нулевой выдачей: это готовый список дыр в контенте.
  • Fallback на обычный полнотекстовый поиск, если векторный сервис недоступен.

Шаг 8. Как понять, что стало лучше

Без измерений вы просто поменяли одну интуицию на другую. Соберите golden set — 50–100 реальных запросов из логов с ожидаемым ответом (конкретный товар, статья, фраза). Дальше прогоняйте его после каждого изменения индекса и смотрите:

  • recall@5 — доля запросов, где нужный документ попал в топ-5;
  • MRR — насколько высоко в среднем стоит правильный ответ;
  • доля запросов с нулевой выдачей;
  • CTR по результатам поиска и доля пользователей, ушедших на второй запрос.

На практике чаще всего проседает не модель, а чанкинг и грязные данные. Прежде чем менять модель на дорогую, посмотрите, что реально попадает в топ-5 на вашем golden set.

Сколько это стоит и российская специфика

Порядок цифр для магазина среднего размера: первичная индексация — единицы долларов, хранение векторов в Postgres — стоимость обычной базы, а основной расход — запросы к LLM, если вы включили генерацию ответов. Кэш частых запросов и «горячих» страниц срезает эту статью расходов в разы.

Из РФ есть два практических нюанса. Первый — оплата зарубежных API: карты, посредники, VPN, и всё это периодически ломается, поэтому в проде нужен запасной вариант и заложенный на это бюджет времени. Второй — отечественные сервисы: YandexGPT, GigaChat и Yandex Embeddings доступны без танцев с оплатой и лучше дружат с русским языком на бытовых запросах. Ограничения у них тоже реальные: меньше выбор моделей, свои лимиты и не всегда предсказуемое качество на узких доменах. Проверяйте на своём golden set, а не на примерах из документации. Цены и лимиты у всех сервисов меняются быстро — сверяйтесь с актуальными тарифами на момент запуска.

Отдельно про лицензии: условия использования сгенерированных текстов и результатов работы API у разных провайдеров отличаются. Если вы индексируете чужой контент или генерируете описания для коммерческого использования, читайте ToS, а не полагайтесь на «все так делают».

Заключение

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

Начните с малого: pgvector, гибридный поиск без LLM, golden set на 50 запросов. Через неделю у вас будут цифры, с которыми можно предметно спорить о целесообразности следующего шага, — а это куда полезнее, чем выбирать модель по обзорам в интернете.

guest

0 Комментарий