Гибридный поиск для RAG: почему одних эмбеддингов мало
Векторный поиск хорошо понимает смысл и плохо — точные совпадения. Разбираем, как совместить его с классическим BM25 и зачем после этого нужен реранкер.
Типичный RAG-конвейер выглядит так: документы режут на фрагменты, каждый фрагмент превращают в вектор-эмбеддинг, а на запрос пользователя ищут ближайшие векторы. Для вопросов вроде «как оформить возврат» это работает отлично — модель эмбеддингов понимает, что «вернуть товар» и «оформить возврат» об одном и том же.
Проблемы начинаются, когда в запросе есть что-то точное.
Где эмбеддинги промахиваются #
Плотный вектор сжимает смысл фрагмента в несколько сотен или тысяч чисел. Общий смысл сохраняется, а мелкие, но важные детали размываются. На практике чаще всего страдают:
- артикулы, коды и номера документов:
ТУ 14-3-1128,A7-2240; - числа и единицы измерения — «16 мм» и «18 мм» для модели почти неотличимы;
- редкие термины и названия, которых модель эмбеддингов почти не видела;
- короткие запросы из одного-двух слов, где просто мало смысла для сравнения.
Классический лексический поиск вроде BM25 устроен наоборот: он ничего не понимает о смысле, зато точное совпадение редкого слова даёт ему большой вес.
Как совместить два ранжирования #
Самый простой и устойчивый способ — Reciprocal Rank Fusion. Каждый поиск возвращает свой список, а итоговая оценка документа складывается из его позиций во всех списках:
score(d) = Σ 1 / (k + rank_i(d))
Константу k обычно берут около 60. Метод не требует сводить разные шкалы оценок к одной — используются только места в списках, поэтому его легко добавить к любой паре поисковиков.
def rrf(rankings, k=60):
scores = {}
for ranking in rankings:
for rank, doc_id in enumerate(ranking, start=1):
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
return sorted(scores, key=scores.get, reverse=True)
fused = rrf([bm25_top50, vector_top50])[:20]
Реранкер как последний шаг #
После слияния остаётся несколько десятков кандидатов. Их можно передать кросс-энкодеру — модели, которая смотрит на запрос и документ одновременно и выставляет оценку релевантности. Он заметно точнее сравнения эмбеддингов, но и медленнее, поэтому его запускают только на коротком списке.
Что проверить перед запуском #
- Нормализуйте текст одинаково при индексации и при поиске: регистр, «ё» и «е», пробелы внутри чисел, запись единиц измерения.
- Храните коды и артикулы в отдельном поле и ищите по нему точным совпадением.
- Соберите хотя бы сотню размеченных запросов и измеряйте recall@k для каждого поиска отдельно и для их комбинации.
- Смотрите на ошибки глазами: заметная часть проблем RAG обычно прячется в нарезке документов, а не в модели.
Гибридный поиск не делает систему умнее. Он делает её менее хрупкой: запросы, на которых проваливается один метод, часто вытягивает второй.