Обычный поиск по сайту выдаёт статьи только если в них встречаются точные слова запроса. Пользователь ищет «как оплатить» — не находит статью «способы расчёта». Семантический поиск решает эту проблему: он понимает смысл, а не строки. Мы внедряем такие системы для интернет-магазинов, документации и порталов. Наш опыт — более 10 проектов с AI-поиском, сертифицированные решения на базе PostgreSQL и Qdrant. Свяжитесь с нами, чтобы обсудить ваш сценарий.
Почему семантический поиск лучше полнотекстового? — реализация ai поиска
Полнотекстовый поиск (PostgreSQL tsvector, Elasticsearch) ищет по совпадению слов. Семантический — по смыслу. Он преобразует текст в вектор — числовой массив из 768–3072 чисел. Тексты с близкими векторами семантически похожи. Это даёт прирост точности релевантных результатов в 2–3 раза, особенно для длинных и разговорных запросов.
Как мы это делаем: стек и кейс
Для интернет-магазина с 50 000 товаров мы внедрили гибридный поиск на базе OpenAI embeddings и pgvector. Результат: среднее время ответа 0,3 секунды, точность 92%.
Выбор модели. Используем text-embedding-3-small (1536 измерений) — оптимальный баланс скорости и качества. Для русского языка он даёт отличные результаты.
Векторная база. PostgreSQL с расширением pgvector и HNSW-индексом:
CREATE EXTENSION vector; CREATE TABLE content_chunks ( id BIGSERIAL PRIMARY KEY, content_id BIGINT REFERENCES content(id), chunk_text TEXT NOT NULL, chunk_index INT, embedding vector(1536), metadata JSONB ); CREATE INDEX ON content_chunks USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); Индексация. Разбиваем текст на чанки по 400 токенов с перекрытием 50 слов, получаем эмбеддинги через OpenAI API и сохраняем в таблицу:
import OpenAI from 'openai'; const openai = new OpenAI(); async function indexContent(contentItem) { const chunks = chunkText(contentItem.body, { maxTokens: 400, overlap: 50 }); const { data: embeddings } = await openai.embeddings.create({ model: 'text-embedding-3-small', input: chunks, }); // Сохраняем в pgvector батчами по 100 for (let i = 0; i < chunks.length; i += 100) { const batchChunks = chunks.slice(i, i + 100); const batchEmbeds = embeddings.slice(i, i + 100); await db.query(` INSERT INTO content_chunks (content_id, chunk_text, chunk_index, embedding, metadata) VALUES ($1, $2, $3, $4::vector, $5) `, [contentItem.id, batchChunks, /* ... */]); } } Поиск. Комбинируем векторный и полнотекстовый поиск через RRF (Reciprocal Rank Fusion):
async function semanticSearch(query, { limit = 10, threshold = 0.7 } = {}) { const { data: [{ embedding }] } = await openai.embeddings.create({ model: 'text-embedding-3-small', input: query, }); const results = await db.query(` WITH semantic AS ( SELECT content_id, chunk_text, 1 - (embedding <=> $1::vector) AS score, ROW_NUMBER() OVER (ORDER BY embedding <=> $1::vector) AS rank FROM content_chunks ORDER BY embedding <=> $1::vector LIMIT 20 ), fulltext AS ( SELECT id AS content_id, body AS chunk_text, ts_rank(to_tsvector('russian', body), plainto_tsquery('russian', $2)) AS score, ROW_NUMBER() OVER (ORDER BY ts_rank(...) DESC) AS rank FROM content WHERE to_tsvector('russian', body) @@ plainto_tsquery('russian', $2) LIMIT 20 ) SELECT COALESCE(s.content_id, f.content_id) AS id, COALESCE(s.chunk_text, f.chunk_text) AS text, (COALESCE(1.0 / (60 + s.rank), 0) + COALESCE(1.0 / (60 + f.rank), 0)) AS rrf_score FROM semantic s FULL OUTER JOIN fulltext f ON s.content_id = f.content_id ORDER BY rrf_score DESC LIMIT $3 `, [`[${embedding.join(',')}]`, query, limit]); return results.rows; } Что такое гибридный поиск и зачем он нужен?
Гибридный поиск объединяет результаты векторного и полнотекстового методов через RRF. Это компенсирует слабости каждого: векторный поиск находит по смыслу, но может пропустить точное вхождение термина; полнотекстовый — наоборот. Вместе они обеспечивают высокую релевантность даже для сложных запросов. Мы используем этот подход во всех проектах. Важно: качественное внедрение требует опыта — наши инженеры гарантируют результат.
Как выбрать embedding-модель для русского языка?
Выбор модели критичен. Multilingual-модели (например, Cohere) часто уступают специализированным на русском. Мы тестировали несколько вариантов и рекомендуем:
| Модель | Размерность | Качество на русском | Скорость | Стоимость |
|---|---|---|---|---|
| OpenAI text-embedding-3-small | 1536 | отлично | высокая | низкая |
| OpenAI text-embedding-3-large | 3072 | превосходно | средняя | средняя |
| Cohere embed-multilingual-v3 | 1024 | хорошо | высокая | средняя |
| BGE-M3 (self-hosted) | 1024 | хорошо | зависит от GPU | бесплатно |
Source: OpenAI Embeddings documentation
Процесс работы
- Аудит контента — выделяем типы текстов, размер, частоту обновлений.
- Выбор модели и векторной БД — определяем компромисс между качеством и бюджетом.
- Настройка индексации — чанкинг, конфигурация индекса, batch-обработка.
- Разработка API поиска — endpoint с параметрами: запрос, фильтры, пагинация.
- Создание UI — поисковая строка, сниппеты с подсветкой, прогрессивная загрузка.
- Тестирование — A/B-тест с текущим поиском, мониторинг метрик.
- Деплой и мониторинг — алерты по задержкам, запросы без результатов.
Пример реализации инкрементальной переиндексации
Чтобы не переиндексировать все документы при каждом изменении, используем триггеры на таблице контента и очередь задач (Bull/PGBoss). При добавлении или обновлении записи ставим задачу на переиндексацию только этого документа. Фоновый воркер забирает задачу, получает эмбеддинги и обновляет соответствующий чанк. Это позволяет поддерживать актуальность без полной переиндексации даже при тысячах изменений в день.Сроки ориентировочно
| Этап | Срок (дней) |
|---|---|
| Семантический поиск по 10К документов (pgvector) | 4–5 |
| Гибридный поиск (вектор + полнотекст) | +1–2 |
| Переранжирование через Cohere Rerank | +1 |
| UI с подсветкой и аналитикой | +2–3 |
| Инкрементальная переиндексация | +1–2 |
Итого: от 8 до 12 рабочих дней. Стоимость рассчитывается индивидуально.
Что входит в работу
- Полная документация архитектуры (схема БД, API спецификация, инструкция по развёртыванию).
- Исходный код под ключ с CI/CD.
- Доступ к репозиторию, дампу данных, мониторинг-дашборду.
- Обучение команды (2–3 часа).
- Техническая поддержка 3 месяца.
Типичные ошибки при внедрении
- Неправильный чанкинг: слишком длинные чанки (>1000 токенов) снижают точность, слишком короткие — теряют контекст. Оптимум: 300–500 токенов с перекрытием 50–100.
- Выбор модели без учёта языка: multilingual-модели (например, Cohere) часто работают хуже на русском, чем специализированные OpenAI embeddings.
- Отсутствие переранжирования: даже хороший векторный поиск иногда выдаёт нерелевантные топ-результаты. Cross-encoder rerank исправляет это.
Хотите внедрить семантический поиск? Свяжитесь с нами — обсудим ваш проект. Получите консультацию по вашему сценарию использования.







