Гибридный поиск и реранкинг: как улучшить качество RAG
Продвинутое3 минРедакция Innovialabs
Коротко
Гибридный поиск объединяет точное совпадение слов (BM25) и смысловую близость (векторы), реранкер пересортировывает кандидатов точнее, а переписывание запросов помогает найти то, что пользователь сформулировал неудачно. Каждый шаг стоит включать только после замера на своём тестовом наборе.
Коротко: зачем RAG нужен гибридный поиск
Ответ RAG-системы не может быть лучше найденных документов. Чистый векторный поиск хорошо ловит смысл, но промахивается на точных терминах: артикулах, кодах ошибок, фамилиях, редких аббревиатурах. Классический BM25 находит точные совпадения, но не понимает синонимы и перефразирование.
Гибридный поиск запускает оба метода и объединяет результаты. Затем реранкер внимательно оценивает пары «запрос — документ» и выбирает лучшие для передачи в LLM.
Как устроен конвейер
- Переписывание запроса (опционально) — LLM уточняет или расширяет запрос.
- Два поиска параллельно — BM25 и векторный, каждый возвращает топ кандидатов.
- Слияние списков — например, через reciprocal rank fusion.
- Реранкинг — cross-encoder пересортировывает объединённый список.
- Отбор контекста — несколько лучших фрагментов уходят в промпт.
BM25 против векторного поиска
| Критерий | BM25 | Векторный поиск |
|---|---|---|
| Точные термины, коды, имена | Сильная сторона | Часто промахивается |
| Синонимы и перефразирование | Слабо | Сильная сторона |
| Новые слова без дообучения | Работает сразу | Зависит от модели эмбеддингов |
| Объяснимость | Высокая | Низкая |
Они ошибаются на разных запросах, поэтому вместе дают более полный набор кандидатов.
Reciprocal rank fusion
Оценки BM25 и косинусная близость имеют разные шкалы, складывать их напрямую некорректно. RRF использует только позиции документов в списках:
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) + 1 / (k + rank)
return sorted(scores, key=scores.get, reverse=True)
Документ, который высоко стоит в обоих списках, поднимается наверх. Параметр k сглаживает влияние первых позиций; значение 60 — распространённая отправная точка. Альтернатива — взвешенная сумма нормализованных оценок, но её веса придётся подбирать.
Cross-encoder реранкер
Bi-encoder (обычные эмбеддинги) кодирует запрос и документ отдельно — это быстро, но грубо. Cross-encoder получает запрос и документ вместе и оценивает их соответствие намного точнее, но медленнее.
Поэтому схема такая: дешёвый поиск отбирает несколько десятков кандидатов, реранкер выбирает из них лучшие. Чем больше кандидатов вы отдаёте реранкеру, тем выше шанс найти нужное и тем больше задержка — баланс подбирается замерами.
Переписывание запросов
Пользователи пишут коротко, с опечатками и контекстом из прошлых сообщений. Помогают:
- Переформулировка с учётом истории диалога — «а сколько он стоит?» превращается в полный вопрос.
- Multi-query — LLM генерирует несколько вариантов запроса, результаты объединяются через RRF.
- HyDE — LLM пишет гипотетический ответ, и поиск идёт по его эмбеддингу.
Каждый вариант добавляет вызов LLM, а значит задержку и стоимость.
Как измерить выигрыш
Не верьте чужим цифрам — проверяйте на своих данных:
- Соберите тестовый набор реальных вопросов с отмеченными правильными документами.
- Считайте recall@k (нашёлся ли нужный документ в топ-k) и MRR или nDCG (насколько высоко).
- Сравнивайте конфигурации по одной: только векторы, гибрид, гибрид + реранкер.
- Отдельно замеряйте задержку и стоимость каждого шага.
Частые ошибки
- Складывать сырые оценки BM25 и косинусной близости.
- Отдавать реранкеру слишком мало кандидатов — ему не из чего выбирать.
- Внедрять всё сразу без замеров и не понимать, что именно помогло.
- Игнорировать качество разбиения на чанки — никакой реранкер не спасёт плохие фрагменты.