Embeddings: Невидимая магия, которая делает RAG возможным

06.06.2026 21:00

Полгода назад мы запустили внутреннего ИИ-ассистента для техподдержки. Архитектура классическая: RAG (Retrieval-Augmented Generation), векторная база, GPT-4o на генерации ответов. Спустя неделю пользователи начали жаловаться: бот галлюцинирует. Мы переписали системные промпты, добавили few-shot примеры, снизили temperature до нуля. Не помогло. Бот продолжал уверенно отвечать невпопад.

Проблема скрывалась не в LLM. Проблема была в фундаменте — в эмбеддингах. Мы взяли дефолтную модель, залили базу знаний и забыли. А когда заглянули в логи поиска, увидели, что на запрос "как сбросить пароль от VPN" векторная база возвращала документы про "настройку VPN на роутере" и "политику смены паролей в Active Directory". Слова те же, смысл другой.

Эмбеддинги — это не просто массив чисел. Это семантическое ядро вашего RAG. Если ядро кривое, никакая, даже самая умная LLM, не спасет результат.

Анатомия векторного пространства

Что происходит, когда вы отправляете текст в embedding-модель? Текст разбивается на токены, проходит через слои трансформера, и на выходе мы получаем вектор — массив чисел с плавающей точкой (float32). Например, text-embedding-3-small от OpenAI возвращает вектор из 1536 измерений.

Каждое измерение кодирует определенный семантический признак. Мы не знаем, какой именно — нейросеть сама формирует эти абстракции в процессе обучения. Но мы знаем главное: тексты с похожим смыслом оказываются рядом в этом многомерном пространстве.

"Рядом" — ключевое слово. И здесь мы сталкиваемся с первой технической ловушкой: как именно считать это расстояние?

Косинус, скалярное произведение или Евклид?

Спустимся на уровень линейной алгебры. Векторные базы данных (Pinecone, Qdrant, pgvector) предлагают на выбор несколько метрик. Разработчики часто выбирают наугад, не понимая последствий.

  1. Euclidean Distance (L2): Прямое расстояние между концами двух векторов. Вычисляется как корень из суммы квадратов разностей координат. Хорошо работает для пространственных данных, но для текстовых эмбеддингов применяется редко. Чем длиннее исходный текст, тем больше может быть магнитуда (длина) вектора, что сильно искажает L2-расстояние.
  2. Dot Product (Скалярное произведение): Сумма произведений соответствующих координат двух векторов. Эта метрика учитывает и угол между векторами, и их длину. Ее главное преимущество — скорость. На современных процессорах (благодаря AVX/SIMD инструкциям) скалярное произведение вычисляется молниеносно.
  3. Cosine Similarity (Косинусное сходство): Тот же Dot Product, но векторы предварительно нормализуются (их длина приводится к единице). Эта метрика измеряет исключительно угол между векторами, игнорируя их магнитуду.

Инженерный инсайт: Современные модели (включая OpenAI text-embedding-ada-002, семейство text-embedding-3 и последние модели Cohere) возвращают уже нормализованные векторы. Их длина всегда равна единице (||v|| = 1). А значит, Dot Product и Cosine Similarity для них математически абсолютно эквивалентны. Но Dot Product вычисляется быстрее, так как не требует дополнительных операций деления на магнитуды. Если ваша векторная база поддерживает Dot Product, и вы используете OpenAI — выбирайте его. На масштабе в десятки миллионов записей вы сэкономите значительные вычислительные ресурсы и снизите latency.

Муки выбора модели

Год назад стандартом де-факто был text-embedding-ada-002. Сегодня ландшафт усложнился. Открываем MTEB (Massive Text Embedding Benchmark) от Hugging Face и видим сотни моделей. BGE-m3, E5-mistral, Cohere-embed-multilingual-v3. Как выбрать?

  • Размерность (Dimensions): Больше измерений — больше семантических нюансов, но выше требования к RAM и медленнее поиск. text-embedding-3-large выдает 3072 измерения. В pgvector миллион таких векторов (тип float32) съест около 12 ГБ оперативной памяти только на хранение индексов. Если база не помещается в RAM, поиск начнет читать с диска, и latency улетит в космос.
  • Matryoshka Representation Learning (MRL): Фича новых моделей OpenAI и некоторых опенсорсных решений (например, Nomic). Она позволяет "обрезать" вектор без катастрофической потери качества. Вы можете взять text-embedding-3-large, запросить размерность 256 вместо 3072, и модель вернет усеченный вектор, который сохраняет 90% семантической точности оригинала, но занимает в 12 раз меньше места. Это спасение для проектов с жесткими лимитами по инфраструктуре.
  • Мультиязычность и кросс-языковой поиск: Если ваша база содержит документы на русском, техническую документацию на английском и тикеты на испанском, вам нужна модель, которая понимает кросс-языковой контекст. Cohere embed-multilingual-v3.0 часто обходит OpenAI на неанглийских текстах. Она умеет мапить запрос "настройка сети" и документ "network configuration guide" в одну и ту же область векторного пространства.

Архитектурная гибкость и RouterAPI

Здесь возникает архитектурный конфликт. Вы начали с OpenAI. Написали код, завязались на их SDK. База растет. Вы понимаете, что для русского языка Cohere работает лучше, а для специфичных задач хочется потестировать открытую модель Voyage AI.

Переписывать интеграции под каждого провайдера — плохая практика. Вы плодите технический долг, пишете адаптеры для разных форматов ответов, возитесь с разными библиотеками. Мы решили эту проблему через единый шлюз. В нашем случае это RouterAPI.

Вместо того чтобы хардкодить эндпоинты OpenAI или Cohere, мы отправляем запросы в единый API, совместимый с форматом OpenAI.

import requests
import numpy as np

# Единый интерфейс RouterAPI для любых embedding-моделей
ROUTER_API_URL = "https://api.RouterAPI.host/v1/embeddings"
HEADERS = {"Authorization": "Bearer YOUR_ROUTER_TOKEN"}

def get_embedding(text: str, model: str) -> np.ndarray:
 payload = {
 "input": text,
 "model": model # Меняем провайдера одной строкой
 }
 response = requests.post(ROUTER_API_URL, json=payload, headers=HEADERS)
 response.raise_for_status
 vector = response.json["data"][0]["embedding"]
 return np.array(vector)

# Сравним семантику через Dot Product
query = get_embedding("Сброс пароля VPN", "text-embedding-3-small")
doc1 = get_embedding("Настройка VPN на роутере MikroTik", "text-embedding-3-small")
doc2 = get_embedding("Инструкция по восстановлению доступа к Cisco AnyConnect", "text-embedding-3-small")

# Векторы нормализованы, используем быстрое скалярное произведение
score1 = np.dot(query, doc1)
score2 = np.dot(query, doc2)

print(f"Score Doc1: {score1:.4f}") # Низкий скор
print(f"Score Doc2: {score2:.4f}") # Высокий скор

Смена модели сводится к изменению одной строчки в конфигурации. RouterAPI сам маршрутизирует запрос к нужному провайдеру, приводит ответ к единому стандарту и отдает вам готовый вектор. Это развязывает руки: вы можете проводить A/B-тестирование разных моделей прямо на проде, направляя 10% трафика на новую модель и замеряя метрики релевантности (nDCG или MRR).

Практические советы по работе с эмбеддингами

  1. Чанкинг (Chunking): Вектор содержит усредненный смысл текста. Представьте, что вы смешали красную, синюю и зеленую краску — получится невнятный серый цвет. Если вы скормите модели документ на 10 страниц, где обсуждаются налоги, маркетинг и найм сотрудников, вектор размоется. Дробите тексты на логические блоки по 300-500 токенов. Делайте overlap (перекрытие) в 50-100 токенов между чанками, чтобы не разорвать контекст на полуслове.
  2. Метаданные — ваш лучший друг: Не пытайтесь решить все проблемы только векторным поиском. Если пользователь ищет "договор аренды за 2023 год", семантический поиск найдет договоры аренды, но может выдать документы за 2021 год. Сохраняйте метаданные (даты, теги, ID авторов) вместе с векторами. Сначала отфильтруйте документы по year=2023 (pre-filtering), а уже среди них ищите косинусное сходство.
  3. Гибридный поиск: Эмбеддинги отлично ищут по смыслу, но отвратительно ищут по точным совпадениям (артикулы, имена собственные, ID транзакций). Запрос "ошибка OOMKilled" может улететь в сторону общих статей про память, проигнорировав точный лог. Объединяйте векторный поиск (Dense) с классическим полнотекстовым поиском (Sparse/BM25) через алгоритмы вроде Reciprocal Rank Fusion (RRF).

RAG-система живет и умирает на этапе поиска. LLM может лишь красиво упаковать то, что вы ей передали. Если вы передали мусор, вы получите красиво упакованный мусор. Относитесь к эмбеддингам как к критической инфраструктуре. Изучайте бенчмарки, понимайте математику под капотом ваших метрик, настраивайте правильный чанкинг. И закладывайте архитектурную гибкость с самого старта. Использование шлюзов позволяет не привязываться к одному вендору и всегда использовать ту модель, которая лучше всего решает именно вашу задачу.

Теги

Ещё по теме