RAG · Retrieval-augmented generation · урок 2 · 13 мин

Embeddings, индекс, hybrid search

Embeddings, vector index, hybrid search

ТемыRAGПоиск и embeddings

Зачем

После урока ты объяснишь, как текст превращается в вектор, как индекс находит соседей среди миллионов векторов и почему поиск по смыслу и поиск по словам нужны вместе. На собеседовании это звучит как «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, чем векторы?
BM25 ищет точную строку и даёт редкому слову высокий вес. Embedding model кодирует смысл, а код почти не несёт смысла и в векторе размывается среди похожих кодов. Поэтому коды, номера и названия — работа поиска по словам.
2Embedding model и LLM: в чём разница их ролей в RAG?
Embedding model превращает chunks и запрос в векторы для поиска и ничего не пишет. LLM читает найденные chunks и пишет ответ. Их выбирают по разным критериям, и смена embedding model требует переиндексации корпуса.
3Почему выбор embedding model для многоязычного корпуса — измерение, а не настройка по умолчанию?
Публичный рейтинг меряет среднее по чужим данным. Модель может быть сильной в английском и слабой на втором языке, диалекте или смешанных запросах. Поэтому сравнивают recall@k кандидатов на своём golden set отдельно по каждому языку.

Interview questions · US / UK

1Why would you use hybrid search instead of pure vector search?
Strong answer: vectors catch paraphrases, BM25 catches exact codes, names and numbers; merge by rank (RRF, k = 60) because scores are not comparable; measure the merge on a golden set, since it can hurt top-1.
2How would you choose an embedding model for a bilingual knowledge base?
Strong answer: golden set of real queries with correct chunk IDs; recall@k per language and for mixed queries; 2–3 candidates; cost, dimension and memory, hosting and data constraints; switching later means re-embedding everything.
3Follow-up probe: you add a metadata filter and the vector search suddenly returns two results instead of ten. What happened?
Strong answer: the filter is applied after the ANN traversal, so most candidates are dropped; filter inside the search (iterative scan, filtered HNSW), partial or separate indexes; check the recall of the filtered query.

Ключевые фразы · Say it aloud

Материалы