Embeddings: как выбрать модель и не пожалеть
Вы строите поиск или RAG-пайплайн. Берёте первую попавшуюся модель эмбеддингов, запускаете — и всё вроде бы работает. А потом оказывается, что русскоязычные запросы дают мусорные результаты, latency убивает UX, или счёт за API в пять раз выше запланированного. Проблема не в векторном хранилище — проблема в выборе модели на старте.
Разберёмся с тем, какие модели существуют, чем они реально отличаются и как выбрать под конкретную задачу.
Зачем вообще нужны эмбеддинги
Эмбеддинги — это плотные числовые векторы, которые кодируют семантику текста. Похожие по смыслу тексты получают похожие векторы — это и есть база для трёх основных применений.
Semantic search. Пользователь пишет «как настроить авторизацию», а в базе есть документ «конфигурация аутентификации JWT». Точное совпадение слов нулевое, но семантически — это одно и то же. BM25 такой запрос завалит, эмбеддинги — нет.
RAG (Retrieval-Augmented Generation). Перед генерацией ответа LLM нужно достать релевантный контекст из базы знаний. Качество эмбеддинга напрямую определяет, насколько релевантный контекст попадёт в промпт — и насколько точным будет ответ модели.
Классификация и кластеризация. Эмбеддинги как признаки для downstream-задач: определение тональности, группировка тикетов, обнаружение дубликатов. Здесь важна не столько топовая точность, сколько стабильность и стоимость на большом объёме.
Что сравниваем: четыре модели
Рассмотрим четыре варианта, которые реально используются в продакшне. Не весь рынок — только то, что проверено на практике.
OpenAI text-embedding-3-small — 1536 измерений по умолчанию (можно сжать до 512), стоимость $0.02 за 1M токенов. Быстрый, удобный API, хороший результат из коробки. Минус: платный, данные уходят в OpenAI, поддержка не-английских языков заметно слабее.
OpenAI text-embedding-3-large — 3072 измерения, $0.13 за 1M токенов. В 6.5 раз дороже small, прирост качества на MTEB — порядка 3–5 пунктов. Оправдан только если у вас уже есть задача, где small явно не дотягивает.
multilingual-e5-large (sentence-transformers) — open-source модель от Microsoft, 1024 измерения, поддержка 100+ языков включая русский, казахский, арабский. Запускается локально — нулевая стоимость per-запрос. На мультиязычных бенчмарках MTEB регулярно обходит OpenAI small. Цена — нужны GPU или CPU-ресурсы на inference.
Cohere embed-v3 — 1024 измерения, $0.10 за 1M токенов (search), специально обученный для retrieval задач. Поддерживает input_type параметр: отдельно для «документов» и «запросов», что даёт асимметричный поиск. Один из лучших результатов на BEIR-бенчмарке.
Метрики: что важно при выборе
Не смотрите только на MTEB Leaderboard — там английские бенчмарки доминируют. Важно понять свой конкретный профиль.
from sentence_transformers import SentenceTransformer
import numpy as np
import time
def benchmark_model(model_name, texts, queries):
model = SentenceTransformer(model_name)
# Индексируем документы
t0 = time.perf_counter()
doc_embeddings = model.encode(texts, batch_size=64, show_progress_bar=False)
index_time = time.perf_counter() - t0
# Тестируем запросы
query_times = []
for q in queries:
t0 = time.perf_counter()
q_emb = model.encode([q])
query_times.append(time.perf_counter() - t0)
# Косинусное сходство
sims = np.dot(doc_embeddings, q_emb.T).flatten()
return {
"index_time_s": round(index_time, 3),
"avg_query_ms": round(np.mean(query_times) * 1000, 2),
"dim": doc_embeddings.shape[1],
}
texts = ["...ваши документы..."]
queries = ["...ваши запросы..."]
result = benchmark_model("intfloat/multilingual-e5-large", texts, queries)
print(result)
Три параметра, на которые смотрим в первую очередь:
Размерность влияет на память и скорость поиска. 3072 у text-embedding-3-large — это в 3 раза больше памяти под индекс по сравнению с 1024. При 10M документов разница ощутимая.
Latency на inference. Для real-time поиска latency запроса должна быть под 50ms. OpenAI API добавляет сетевой round-trip ~100–200ms. Локальные модели на GPU — 5–20ms.
Стоимость. При больших объёмах ($0.02 vs $0.13 vs $0) разница в десятки тысяч долларов в год. Считайте заранее.
Multilingual: когда без него не обойтись
Если ваши данные или пользователи — на русском, казахском или смеси языков, OpenAI text-embedding-3-small заметно деградирует. Проверьте это просто:
import openai
import numpy as np
client = openai.OpenAI()
def embed(text, model="text-embedding-3-small"):
resp = client.embeddings.create(input=[text], model=model)
return np.array(resp.data[0].embedding)
# Проверяем кросс-языковое сходство
en = embed("database connection timeout error")
ru = embed("ошибка подключения к базе данных — превышен таймаут")
kk = embed("деректер қорына қосылу қатесі")
cos = lambda a, b: np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(f"EN–RU similarity: {cos(en, ru):.3f}")
print(f"EN–KK similarity: {cos(en, kk):.3f}")
print(f"RU–KK similarity: {cos(ru, kk):.3f}")
# text-embedding-3-small: EN-RU ~0.55, RU-KK ~0.40
# multilingual-e5-large: EN-RU ~0.82, RU-KK ~0.74
Разрыв в кросс-языковом сходстве — это не абстрактная метрика. Это то, как часто ваш поиск будет находить релевантный русский документ по английскому запросу (и наоборот). Для мультиязычного продукта multilingual-e5-large — очевидный выбор.
Хранение векторов: pgvector, Qdrant, Pinecone
Модель выбрана — теперь нужно где-то хранить и искать. Три популярных варианта с разными trade-offs.
pgvector — расширение для PostgreSQL. Если у вас уже есть Postgres, это нулевой overhead на инфраструктуру. Поддерживает IVFFlat и HNSW индексы, фильтрацию по метаданным через стандартный SQL. Ограничение: при >5M векторов начинает проседать по QPS.
-- Подключаем расширение
CREATE EXTENSION IF NOT EXISTS vector;
-- Таблица с эмбеддингами
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
source TEXT,
embedding vector(1024)
);
-- HNSW индекс для быстрого ANN-поиска
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Поиск ближайших соседей
SELECT id, content, 1 - (embedding <=> $1) AS score
FROM documents
ORDER BY embedding <=> $1
LIMIT 10;
Qdrant — специализированный векторный движок, написанный на Rust. Нативная поддержка фильтрации по payload, sparse+dense гибридный поиск, хорошая горизонтальная масштабируемость. Self-hosted или managed cloud. Для большинства production RAG-пайплайнов — оптимальный выбор.
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
client = QdrantClient(url="http://localhost:6333")
# Создаём коллекцию
client.create_collection(
collection_name="docs",
vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)
# Загружаем векторы
points = [
PointStruct(
id=i,
vector=embedding.tolist(),
payload={"content": text, "source": source}
)
for i, (embedding, text, source) in enumerate(data)
]
client.upsert(collection_name="docs", points=points)
# Поиск с фильтром по метаданным
from qdrant_client.models import Filter, FieldCondition, MatchValue
results = client.search(
collection_name="docs",
query_vector=query_embedding.tolist(),
query_filter=Filter(
must=[FieldCondition(key="source", match=MatchValue(value="internal_wiki"))]
),
limit=5,
)
Pinecone — fully managed, простой API, быстрый старт. Хорошо подходит для прототипов и команд без DevOps-ресурсов. Минус: стоимость растёт нелинейно с объёмом, данные у провайдера, меньше контроля над конфигурацией индекса.
Итого: как выбирать
Простой алгоритм принятия решения:
# Псевдокод выбора модели эмбеддингов
def choose_embedding_model(use_case):
if use_case.languages != ["en"]:
# Мультиязычные данные — open-source выигрывает
if use_case.can_host_model:
return "intfloat/multilingual-e5-large" # лучшее качество, бесплатно
else:
return "cohere/embed-multilingual-v3.0" # managed, но дорого
if use_case.scale == "prototype":
return "openai/text-embedding-3-small" # быстрый старт
if use_case.retrieval_quality == "critical":
# Сравните small vs large на своих данных
# Разница ~3-5 пп на MTEB, но стоимость в 6.5x
return benchmark_both_and_decide()
# Default для англоязычного продакшна
return "openai/text-embedding-3-small"
Для хранения: начните с pgvector, если уже используете PostgreSQL и объём <5M документов. При росте или необходимости гибридного поиска — мигрируйте на Qdrant. Pinecone — только если нет времени на инфраструктуру и бюджет это позволяет.
Одна рекомендация, которую игнорируют чаще всего: тестируйте модели на своих данных, не на бенчмарках. MTEB — это хорошая стартовая точка, но ваши русско-казахские тексты с доменной терминологией — это не Wikipedia. Напишите тест с 50–100 query-document парами с оценкой relevance, и прогоните все кандидаты. Это 2–3 часа работы, которые сэкономят недели переделки.
Выбор модели эмбеддингов — это не разовое решение. Это архитектурный выбор, который определяет качество поиска, стоимость инфраструктуры и сложность поддержки на годы вперёд. Потратьте день на бенчмарк — сэкономите месяц на рефакторинг.