Обычный поиск по сайту выдаёт статьи только если в них встречаются точные слова запроса. Пользователь ищет «как оплатить» — не находит статью «способы расчёта». Семантический поиск решает эту проблему: он понимает смысл, а не строки. Мы внедряем такие системы для интернет-магазинов, документации и порталов. Наш опыт — более 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 исправляет это.
Хотите внедрить семантический поиск? Свяжитесь с нами — обсудим ваш проект. Получите консультацию по вашему сценарию использования.







