
Почему штатный поиск по сайту перестаёт работать
Представьте: интернет-магазин на 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 запросов. Через неделю у вас будут цифры, с которыми можно предметно спорить о целесообразности следующего шага, — а это куда полезнее, чем выбирать модель по обзорам в интернете.