Как встроить ИИ-поиск в приложение с эмбеддингами
Практическое руководство: как добавить семантический поиск в приложение с помощью эмбеддингов и векторных баз данных.
Классический поиск по ключевым словам не находит результат, если пользователь сформулировал запрос иначе, чем написано в тексте. Семантический поиск на эмбеддингах решает эту проблему: запрос «как уменьшить расходы на облако» найдёт документ с заголовком «оптимизация затрат на AWS», даже если слово «расходы» в нём не встречается. Принцип работы векторных баз данных мы разбирали ранее — здесь покажем, как интегрировать поиск в реальное приложение.
Как работает семантический поиск
Алгоритм в трёх шагах. Первый: каждый документ превращается в вектор (массив из 768–3072 чисел) с помощью модели эмбеддингов. Вектор кодирует смысл текста. Второй: векторы сохраняются в специализированную базу данных. Третий: при поиске запрос пользователя тоже превращается в вектор, и база находит ближайшие по косинусному расстоянию документы.
Весь процесс — от запроса до результата — занимает 50–200 мс при базе до миллиона документов.
Выбор модели эмбеддингов
Для русского языка подходят несколько моделей:
OpenAI text-embedding-3-small — облачная модель, $0.02 за миллион токенов. Размерность: 1536. Хорошее качество на русском языке. Минус — данные уходят в облако.
OpenAI text-embedding-3-large — более точная версия, $0.13 за миллион токенов. Размерность: 3072. Для задач, где нужна максимальная точность.
intfloat/multilingual-e5-large — open-source модель, работает локально. 560 МБ, размерность 1024. Хорошо работает с русским. Не требует API-ключей и не отправляет данные наружу.
BAAI/bge-m3 — мультиязычная модель с поддержкой sparse и dense эмбеддингов. Размерность 1024. Одна из лучших open-source моделей для русского языка по бенчмаркам MTEB.
Реализация с OpenAI и ChromaDB
ChromaDB — простейшая векторная база для прототипирования. Работает как встроенная библиотека, без отдельного сервера:
pip install chromadb openaiimport chromadb
from openai import OpenAI
client = OpenAI()
chroma = chromadb.PersistentClient(path="./search_db")
collection = chroma.get_or_create_collection("documents")
def get_embedding(text):
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
# Индексация документов
documents = [
{"id": "1", "text": "Оптимизация затрат на AWS: 10 проверенных стратегий", "url": "/aws-costs"},
{"id": "2", "text": "Kubernetes: автоматическое масштабирование подов", "url": "/k8s-autoscale"},
{"id": "3", "text": "PostgreSQL: настройка производительности для высоких нагрузок", "url": "/pg-tuning"},
]
for doc in documents:
embedding = get_embedding(doc["text"])
collection.add(
ids=[doc["id"]],
embeddings=[embedding],
documents=[doc["text"]],
metadatas=[{"url": doc["url"]}]
)
# Поиск
query = "как уменьшить расходы на облако"
query_embedding = get_embedding(query)
results = collection.query(
query_embeddings=[query_embedding],
n_results=3
)
for doc, metadata, distance in zip(results["documents"][0], results["metadatas"][0], results["distances"][0]):
print(f"[{1-distance:.2f}] {doc} → {metadata['url']}")Продакшен: Qdrant
Для продакшена ChromaDB не подходит — нет горизонтального масштабирования, нет фильтрации по метаданным при поиске. Qdrant — зрелая векторная база с REST API, фильтрами и кластеризацией:
# Запуск через Docker
docker run -p 6333:6333 -v qdrant_data:/qdrant/storage qdrant/qdrantfrom qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance, PointStruct, Filter, FieldCondition, MatchValue
qdrant = QdrantClient(host="localhost", port=6333)
# Создание коллекции
qdrant.create_collection(
collection_name="articles",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)
# Индексация
points = [
PointStruct(
id=i,
vector=get_embedding(doc["text"]),
payload={"text": doc["text"], "category": doc.get("category", "general")}
)
for i, doc in enumerate(documents)
]
qdrant.upsert(collection_name="articles", points=points)
# Поиск с фильтрацией
results = qdrant.search(
collection_name="articles",
query_vector=get_embedding("уменьшить расходы"),
query_filter=Filter(
must=[FieldCondition(key="category", match=MatchValue(value="infrastructure"))]
),
limit=5
)Локальные эмбеддинги без API
Для конфиденциальных данных используйте локальную модель через sentence-transformers:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("intfloat/multilingual-e5-large")
def get_embedding_local(text):
return model.encode(f"query: {text}").tolist()
# Использование идентично облачному варианту
vector = get_embedding_local("как уменьшить расходы на облако")Модель загружается при первом запуске (2 ГБ) и работает полностью офлайн. На CPU — 50–100 мс на запрос, на GPU — 5–10 мс.
Чанкинг документов
Длинные документы нужно разбивать на фрагменты (чанки) перед индексацией. Оптимальный размер чанка — 200–500 токенов. Слишком мелкие — теряется контекст. Слишком крупные — снижается точность поиска.
Разбивайте по абзацам или семантическим блокам, а не по фиксированному числу символов. Добавляйте перекрытие 10–20% между чанками. Сохраняйте метаданные: заголовок документа, URL, дату — они нужны для ранжирования и фильтрации результатов.
Гибридный поиск
Наилучшие результаты даёт комбинация семантического и ключевого поиска. Qdrant и другие современные базы поддерживают гибридный поиск: запрос одновременно ищется по эмбеддингам (смысл) и по BM25 (точное совпадение ключевых слов). Результаты объединяются через reciprocal rank fusion.
Гибридный подход закрывает слабость семантического поиска — он иногда «понимает» запрос слишком творчески и возвращает тематически похожие, но нерелевантные документы. Ключевой поиск выступает якорем точности.
Векторный поиск и эмбеддинги: как это работает
Традиционный поиск (BM25, TF-IDF) ищет совпадение ключевых слов. Семантический поиск на эмбеддингах ищет смысловую близость — запрос «как приготовить пасту» найдёт документ «рецепт макарон», хотя слов нет ни одного общего. Это революция для поиска по корпоративным базам знаний, FAQ и документации.
Выбор модели эмбеддингов
| Модель | Размерность | Языки | Скорость | Качество (RU) |
|---|---|---|---|---|
| text-embedding-3-large (OpenAI) | 3072 | 100+ | API | ★★★★★ |
| text-embedding-3-small (OpenAI) | 1536 | 100+ | API | ★★★★☆ |
| multilingual-e5-large (Microsoft) | 1024 | 100+ | Локально | ★★★★☆ |
| paraphrase-multilingual-mpnet | 768 | 50+ | Локально | ★★★★☆ |
| E5-mistral-7b | 4096 | 100+ | GPU required | ★★★★★ |
Векторные базы данных: сравнение
| БД | Тип | Масштаб | Цена | Особенность |
|---|---|---|---|---|
| pgvector | PostgreSQL extension | До ~1M векторов | Бесплатно | SQL, ACID |
| Chroma | Self-hosted / Cloud | Средний | Бесплатно (local) | Простейший старт |
| Weaviate | Self-hosted / Cloud | Большой | От $0 (self-hosted) | Hybrid search |
| Pinecone | Managed cloud | Любой | От $0 (free tier) | Managed, prod-ready |
| Milvus | Self-hosted | Миллиарды | Бесплатно | Enterprise scale |
Hybrid Search: лучшее из обоих миров
На практике лучшие результаты даёт комбинация семантического поиска (эмбеддинги) и ключевого (BM25). Semantic search хорош для концептуальных запросов, BM25 — для конкретных терминов и кодов. Weaviate и Elasticsearch имеют встроенный hybrid search. Для pgvector используйте pg_search (ParadeDB) для BM25.
Практические применения
- RAG (Retrieval-Augmented Generation) — поиск релевантных документов для контекста LLM
- Дедупликация — нахождение похожих записей в базе данных
- Рекомендации — похожие товары, статьи, пользователи
- Классификация без дообучения — zero-shot через cosine similarity с примерами классов
- Умный поиск по FAQ — нахождение ответа даже при разных формулировках вопроса
Сравнение embedding-моделей для семантического поиска
| Модель | MTEB Score | Размерность | Цена (1M токенов) | Self-hosted |
|---|---|---|---|---|
| text-embedding-3-large (OpenAI) | 64.6 | 3072 | $0.13 | Нет |
| text-embedding-3-small (OpenAI) | 62.3 | 1536 | $0.02 | Нет |
| voyage-3 (Anthropic) | 67.1 | 1024 | $0.06 | Нет |
| E5-large-v2 | 62.2 | 1024 | Бесплатно | Да |
| nomic-embed-text-v1.5 | 62.3 | 768 | Бесплатно | Да |
| BGE-M3 (BAAI) | 66.8 | 1024 | Бесплатно | Да |
Семантический поиск: базовая реализация
from openai import OpenAI
import numpy as np
client = OpenAI()
def embed(text):
return client.embeddings.create(
input=text,
model='text-embedding-3-small'
).data[0].embedding
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# База документов
docs = [
'Квантовые компьютеры используют кубиты',
'Машинное обучение требует больших данных',
'Python популярен среди data scientists',
]
doc_embeddings = [embed(d) for d in docs]
# Поиск
query = 'Как работают квантовые вычисления?'
query_emb = embed(query)
scores = [cosine_similarity(query_emb, de) for de in doc_embeddings]
best = docs[np.argmax(scores)]
print(f'Лучший результат: {best}')Векторные базы данных: выбор
- FAISS (Meta): локально, без сервера, идеально для прототипов и небольших баз (<10M векторов)
- Qdrant: self-hosted или облако, отличная производительность, REST/gRPC API, фильтрация по metadata
- Weaviate: встроенная векторизация, GraphQL API, подходит для enterprise
- Pinecone: managed-сервис без инфраструктурных затрат; дороже при больших объёмах
- pgvector: расширение PostgreSQL — если уже используете PG, минимальный overhead
Практические рекомендации
- Для RAG-системы: voyage-3 + Qdrant — лучший баланс качества и цены в 2026
- Без внешних API: BGE-M3 локально + FAISS — конкурентное качество бесплатно
- Масштаб production: text-embedding-3-large + Pinecone или Qdrant Cloud
- Гибридный поиск: комбинируйте векторный + BM25 (полнотекстовый) — улучшает recall на 10–20%