RAG · Retrieval-augmented generation · урок 2 · 13 мин
Embeddings, индекс, hybrid search
Embeddings, vector index, hybrid search
Зачем
После урока ты объяснишь, как текст превращается в вектор, как индекс находит соседей среди миллионов векторов и почему поиск по смыслу и поиск по словам нужны вместе. На собеседовании это звучит как «why hybrid search?» и «how would you pick an embedding model?», а на работе — как выбор, который потом стоит полной переиндексации.
Embedding: координаты смысла
Embedding (числовой отпечаток смысла текста) — длинный список чисел, vector (вектор). Его делает embedding model (отдельная модель для отпечатков). Это не та LLM (large language model, большая языковая модель), что пишет ответ: embedding model только кодирует текст в числа для поиска, LLM читает найденное и отвечает. У них разные размеры, цены и критерии выбора.
Свойство, ради которого всё затевается: тексты с похожим смыслом получают близкие векторы, даже если слова разные. «early settlement charge» и «prepayment fee» окажутся соседями. Типичная длина вектора — несколько сотен или тысяча с лишним чисел, например 1 024.
Два правила, которые часто нарушают. Первое: запрос и документы кодирует одна и та же модель — векторы разных моделей несравнимы. Второе: сменил embedding model — переиндексируешь весь корпус. Поэтому модель выбирают один раз и осознанно.
Cosine similarity: как мерить близость
Близость двух векторов чаще всего меряют cosine similarity (косинусная близость): косинус угла между ними. 1 — смотрят в одну сторону, 0 — не связаны. Формула: скалярное произведение, делённое на произведение длин.
import math
def cosine(u, v):
dot = sum(a * b for a, b in zip(u, v))
return dot / (math.sqrt(sum(a * a for a in u)) * math.sqrt(sum(b * b for b in v)))
fee = [0.9, 0.8, 0.1, 0.0]
charge = [0.8, 0.9, 0.2, 0.1]
weather = [0.1, 0.0, 0.9, 0.8]
print(round(cosine(fee, charge), 2), round(cosine(fee, weather), 2)) # 0.99 0.12
Здесь четыре числа вместо тысячи, но механика та же: «fee» и «charge» почти совпадают по направлению, «weather» далеко.
Практическая деталь: если векторы заранее нормированы до длины 1, косинус равен простому скалярному произведению. Поэтому многие индексы предлагают метрику inner product (скалярное произведение) — она быстрее и при нормированных векторах даёт тот же порядок соседей.
Vector index: быстро найти соседей
Index (указатель) — структура, которая находит нужное, не перечитывая всё. Сравнить запрос с каждым из миллиона векторов можно, но медленно. Vector index решает это приближённо: ANN (approximate nearest neighbours, приближённый поиск ближайших соседей). Самый частый алгоритм — HNSW (Hierarchical Navigable Small World, многослойный граф соседства; Malkov и Yashunin, 2016): поиск начинается в редком верхнем слое и спускается вниз, авторы заявляют логарифмический рост времени с размером базы.
Цена приближения — recall (доля верных соседей, которые индекс нашёл). Параметры вроде ef_search в pgvector меняют баланс: больше — точнее и медленнее.
Память считают заранее. 1 000 000 chunks × 1 024 числа × 4 байта (float32) ≈ 4,1 ГБ только на векторы, без графа. При 20 000 000 chunks это ≈ 82 ГБ — тогда нужны quantization (сжатие чисел) и sharding (разбиение индекса на части по нескольким машинам).
Ловушка фильтров. Если отсев по metadata (дата, доступ) применяется после обхода ANN-индекса, выдача пустеет. README pgvector приводит пример: условие совпадает с 10% строк, HNSW с ef_search = 40 вернёт в среднем около 4 строк. Решения — фильтр внутри поиска (в pgvector — iterative index scans), отдельные или частичные индексы.
BM25: поиск по словам
Keyword search (поиск по словам) строится на inverted index (обратном указателе: слово → где оно встречается). Стандартная формула оценки — BM25: чем чаще слово запроса в документе и чем реже оно в корпусе, тем выше балл; длинные документы штрафуются. Два параметра: k1 (насыщение повторов, обычно 1,2–2,0) и b (поправка на длину, обычно 0,75).
Сильная сторона BM25 — точные строки: коды продуктов, номера пунктов, названия. Код «AF-2026-M» для embedding model — почти шум: в векторе он размывается среди похожих кодов. BM25 ищет именно эту строку. Слабая сторона — перефразировки: «досрочка» не найдёт «early settlement».
Hybrid search и слияние списков
Hybrid search (гибридный поиск) — оба поиска сразу, списки объединяются. Названия и коды ищутся словами, перефразировки — смыслом. Баллы BM25 и косинусы несравнимы по шкале, поэтому сливают обычно не баллы, а ранги. Стандартный способ — RRF (reciprocal rank fusion, слияние по обратным рангам; Cormack и др., 2009): балл документа — сумма 1 / (k + ранг) по всем спискам, k = 60.
Пример. Документ A: 1-й в BM25 и 4-й в векторном поиске → 1/61 + 1/64 ≈ 0,0320. Документ B: 1-й в векторном, в BM25 нет → 1/61 ≈ 0,0164. Документ C: 2-й в обоих → 1/62 + 1/62 ≈ 0,0323. Итог: C, A, B. RRF награждает согласие двух поисков, а не победу в одном.
Суждение: hybrid — разумный выбор по умолчанию, но слияние — решение, которое меряют. На твоих запросах RRF может и опустить верный документ с первого места; проверяют на golden set (наборе вопросов с известными верными chunks).
Как выбирать embedding model
Выбор — измерение, а не настройка по умолчанию. Публичные рейтинги показывают общую силу модели, но не качество на твоих документах, твоих языках и твоём жаргоне. Порядок: собрать 50–100 реальных вопросов с верными chunk IDs; для многоязычного корпуса — отдельно по каждому языку и со смешанными запросами; прогнать 2–3 модели-кандидата; сравнить recall@10 (долю верных chunks, попавших в первые 10 результатов) по каждому языку; учесть цену, длину вектора (память) и где модель может работать (облако или свой сервер — вопрос данных). Модель, лучшая в среднем, может проваливаться на одном языке — поэтому цифры по языкам не смешивают.
Ещё одно ограничение — максимальная длина входа модели. Текст длиннее этого предела обычно обрезается без ошибки, и хвост chunk в поиске просто не участвует. Значит, размер chunk и выбор embedding model решают вместе.
Схема
Термины · Key terms
- embedding
- числовой отпечаток смысла текста, вектор
- embedding model
- модель, которая делает embeddings; не та LLM, что пишет ответ
- cosine similarity
- косинус угла между векторами, мера близости
- vector index
- структура для быстрого поиска ближайших векторов
- ANN (approximate nearest neighbours)
- приближённый поиск соседей, быстрее точного
- HNSW
- многослойный граф соседства, частый алгоритм ANN
- inverted index
- обратный указатель: слово → документы, где оно есть
- BM25
- стандартная формула оценки поиска по словам
- hybrid search
- векторный поиск и поиск по словам вместе
- RRF (reciprocal rank fusion)
- слияние списков по сумме 1 / (60 + ранг)
- golden set
- набор вопросов с заранее известными верными chunks
Сделай сам
Не подглядывая в урок, напиши функцию cosine similarity на Python без библиотек (10 строк). Придумай три вектора из четырёх чисел — «fee», «charge», «weather» — так, чтобы первые два были близки, и выведи обе близости. Потом поменяй одно число в «charge» так, чтобы близость к «fee» упала ниже 0,8.
Обсуди с тьютором: покажи код и вывод и спроси, почему в настоящих embeddings близость двух случайных текстов редко бывает около 0, и что это значит для порога «достаточно похоже».
Проверь себя · ответ под вопросом
1Почему код продукта вроде «AF-2026-M» лучше находит BM25, чем векторы?
2Embedding model и LLM: в чём разница их ролей в RAG?
3Почему выбор embedding model для многоязычного корпуса — измерение, а не настройка по умолчанию?
Interview questions · US / UK
1Why would you use hybrid search instead of pure vector search?
2How would you choose an embedding model for a bilingual knowledge base?
3Follow-up probe: you add a metadata filter and the vector search suddenly returns two results instead of ten. What happened?
Ключевые фразы · Say it aloud
- Similar meaning, nearby vectorsпохожий смысл, близкие векторы
- The embedding model retrieves, the LLM answersembedding model ищет, LLM отвечает
- BM25 wins on exact identifiersBM25 сильнее на точных кодах и названиях
- Hybrid is the default, but the merge is a design choice I measurehybrid по умолчанию, но слияние меряю
- Changing the embedding model means re-indexing the corpusсмена модели — полная переиндексация
- Filter inside the search, not after itфильтр внутри поиска, а не после
Материалы
- pgvector — README — разделы про HNSW, фильтрацию и iterative index scans, hybrid search (10 мин)
- Okapi BM25 — Wikipedia — формула и параметры k1, b (5 мин)
- Cormack, Clarke, Büttcher, 2009 — Reciprocal Rank Fusion — двухстраничная статья, формула RRF и k = 60 (5 мин)
- Malkov, Yashunin — HNSW — abstract, идея многослойного графа (2 мин)