Reranker для RAG: как проверить, улучшает ли он поиск документов

Reranker для RAG может повысить точность контекста, но добавляет задержку и вычисления. Показываем воспроизводимый тест с CrossEncoder, метриками MRR и nDCG и сравнением BGE Reranker с базовым поиском.

Схема работы BGE Reranker и CrossEncoder в RAG-пайплайне с этапами поиска и переранжирования документов
Схема работы BGE Reranker и CrossEncoder в RAG-пайплайне с этапами поиска и переранжирования документов
femme pacifiste | by machacon | openverse | by

В RAG-системе генератор отвечает только по тому контексту, который получил от поискового слоя. Если нужный фрагмент оказался на 17-м месте, а в промпт попали первые 5 результатов, модель не исправит ошибку самостоятельно. Поэтому в архитектуру часто добавляют второй этап — reranking, или переранжирование найденных документов.

Reranker для RAG не заменяет векторный поиск. Он получает небольшой список уже найденных кандидатов и заново оценивает их связь с запросом. Это может улучшить порядок документов, но одновременно увеличивает задержку и стоимость обработки. Проверять такую связку нужно не по впечатлению от нескольких ответов, а на собственном наборе запросов.

Что именно делает reranker для RAG

Типичный конвейер выглядит так:

Пользовательский запрос преобразуется в эмбеддинг.

Векторная база или полнотекстовый индекс возвращает первые `k` кандидатов.
3. Reranker получает запрос и каждый кандидат как пару текстов.
4. Документы сортируются по новым оценкам.
5. В генератор передаются первые `n` фрагментов.

Разница между первым и вторым этапом поиска принципиальна. Би-энкодер заранее кодирует документы и запросы отдельно, поэтому может быстро искать по большому индексу. Cross-encoder читает запрос и документ вместе. Такой подход обычно лучше учитывает конкретные формулировки, отрицания, названия и отношения между сущностями, но требует отдельного вычисления для каждой пары.

Документация Sentence Transformers описывает CrossEncoder именно как модель, которая принимает пару текстов и возвращает оценку релевантности. В примерах библиотека использует её для переранжирования результатов, а не для первичного поиска по всей коллекции: документация CrossEncoder.

Практический вывод: сначала нужно получить достаточно широкий список кандидатов, например 20–100 документов, а затем сократить его до контекста для LLM. Если первичный поиск не вернул правильный документ вообще, reranker его не найдёт.

Почему сравнивать нужно не ответы модели, а порядок документов

У RAG есть как минимум два разных места отказа:

  • релевантный фрагмент не попал в список кандидатов;
  • фрагмент попал в список, но оказался слишком низко и не дошёл до генератора.

Reranker влияет в основном на вторую проблему. Поэтому оценка должна начинаться с разметки поиска. Для каждого запроса понадобится:

  • сам запрос;
  • идентификатор одного или нескольких релевантных документов;
  • список кандидатов от базового поиска;
  • желаемый размер контекста `n`;
  • при необходимости — шкала релевантности, например от 0 до 3.

Для небольшого внутреннего теста достаточно 30–100 реальных запросов. Их лучше брать из логов приложения, тикетов поддержки или вопросов пользователей, а не придумывать только из названий документов. В наборе должны быть сложные случаи: синонимы, сокращения, отрицания, несколько похожих сущностей и запросы, на которые ответа в базе нет.

Главные метрики:

  • Recall@k — попал ли хотя бы один правильный документ в первые `k` результатов;
  • MRR@k — насколько высоко оказался первый релевантный результат;
  • nDCG@k — учитывает порядок нескольких релевантных документов и разные степени их полезности.

Если задача — передать LLM пять фрагментов, критично смотреть не только на MRR@10, но и на Recall@5. Высокий Recall@50 не поможет, если после переранжирования в контекст всё равно попадают нерелевантные документы.

Для расчёта nDCG можно использовать реализацию из документации scikit-learn. Важно подавать в неё одинаковый набор документов в одинаковом порядке, меняя только оценки конкретного метода.

Воспроизводимый тест на BGE Reranker

Один из доступных локально вариантов — `BAAI/bge-reranker-v2-m3`. В карточке модели на Hugging Face приведён пример использования для оценки пар «запрос — документ», а также указана поддержка многоязычных сценариев. Для русскоязычного проекта это делает модель удобным кандидатом для первичной проверки, но не доказательством качества на конкретном домене.

Ниже — минимальный пример. Он не выполняет первичный поиск: список `candidates` должен прийти от вашей векторной базы или полнотекстового движка.

python
from sentence_transformers import CrossEncoder

query = «Как настроить ротацию API-ключей в агенте?»
candidates = [
«Сервис хранит ключи в переменных окружения и меняет их вручную.»,
«Ротация ключей выполняется через секрет-хранилище. Агент получает короткоживущий токен, а приложение обновляет его без перезапуска.»,
«API ограничивает число запросов в минуту для каждого пользователя.»,
«Логи агента содержат входные и выходные сообщения инструментов.»
]

model = CrossEncoder(«BAAI/bge-reranker-v2-m3»)
scores = model.predict([(query, text) for text in candidates])

ranked = sorted(
zip(scores, candidates),
key=lambda item: float(item[0]),
reverse=True
)

for score, text in ranked:
print(f»{float(score):.4f}\t{text}»)

Для эксперимента нужно сохранить не только отсортированный список, но и:

  • название модели;
  • версию библиотеки;
  • устройство выполнения — CPU или GPU;
  • размер входных текстов;
  • время обработки всего списка;
  • число кандидатов `k`;
  • число документов, передаваемых генератору.

Карточка `bge-reranker-v2-m3` доступна по адресу BAAI/bge-reranker-v2-m3 на Hugging Face. При запуске в продакшене стоит зафиксировать конкретную версию модели и не сравнивать результаты после её замены с прежними метриками без отдельной пометки.

Сравнение с Cohere Rerank и базовым поиском

Локальная модель удобна, когда данные нельзя отправлять внешнему API или требуется контролировать инфраструктуру. Облачный сервис может быть проще для запуска, особенно если важны масштабирование и отсутствие собственного инференса.

В документации Cohere этап `rerank` описан как переоценка списка документов по отношению к запросу. API принимает запрос, массив документов и возвращает результаты с индексами и оценками. Важное ограничение то же самое: сервис ранжирует переданные кандидаты, а не ищет по всей базе. Официальное описание параметров и поведения находится в документации Cohere Rerank.

Сравнивать варианты следует в трёх конфигурациях:

Базовый поиск — результаты векторного или гибридного индекса без второго этапа.

Локальный reranker — например, `BAAI/bge-reranker-v2-m3`.
3. Облачный reranker — например, Cohere Rerank с зафиксированной версией модели и одинаковым списком кандидатов.

Для честного теста все три варианта должны получать один и тот же запрос и один и тот же список кандидатов. Нельзя сравнивать локальную модель на 50 документах с API, которому передали только 10. Также нужно отделять время первичного поиска от времени переранжирования.

Полезный формат результата — не «модель А лучше модели Б», а профиль:

Метод Recall@5 MRR@5 Средняя задержка Доля запросов с улучшением
Базовый поиск измерить измерить измерить —
Локальный CrossEncoder измерить измерить измерить измерить
Облачный reranker измерить измерить измерить измерить

Числа в такой таблице должны появиться только после запуска на вашем наборе. Бенчмарки из карточки модели или статьи не заменяют проверку на собственных документах.

Как подобрать размер списка кандидатов

Параметр `k` определяет, сколько документов первичный поиск передаст на второй этап. Маленькое значение снижает задержку, но увеличивает риск, что нужный фрагмент не попадёт к reranker. Большое значение повышает шанс обнаружения, однако CrossEncoder должен обработать больше пар.

Начните с трёх конфигураций:

  • `k=10`, в контекст передаются 3–5 документов;
  • `k=30`, в контекст передаются 3–5 документов;
  • `k=50`, в контекст передаются 3–5 документов.

Для каждого варианта измерьте Recall@k у первичного поиска, MRR после переранжирования и полное время до формирования контекста. Если переход с 30 на 50 кандидатов почти не улучшает Recall@5, дополнительные вычисления, вероятно, не оправданы.

Есть и другой риск: слишком длинный кандидат может исказить оценку. Документ на 20 страниц, поданный одним текстом, содержит много нерелевантного материала даже при правильном заголовке. Поэтому размер чанка, перекрытие и правила очистки текста нужно держать неизменными во время сравнения. Иначе эксперимент будет измерять одновременно качество нарезки и качество reranker.

Где переранжирование ухудшает результат

Reranker не является универсальным исправлением RAG. Он может дать слабый результат в нескольких ситуациях.

  • Нужного документа нет среди кандидатов. Смена модели второго этапа не исправит плохой recall первичного индекса. Сначала проверьте, видит ли базовый поиск правильный фрагмент хотя бы в топ-50.
  • Запрос слишком короткий. Поиск по словам вроде «ошибка авторизации» может возвращать много похожих документов. Здесь полезнее добавить в запрос тип продукта, код ошибки или действие пользователя, чем просто увеличить число кандидатов.
  • В корпусе есть дубликаты. Reranker может поднять несколько почти одинаковых чанков, вытеснив разные части ответа. После сортировки иногда нужен этап дедупликации по документу, заголовку или семантическому сходству.
  • Документы требуют свежести. Модель оценивает соответствие текста запросу, но сама по себе не знает, какой документ актуальнее. Для инструкций, API и политик доступа к релевантности нужно добавить фильтр по версии или дату обновления.
  • Стоимость важнее нескольких процентов качества. На небольшом трафике задержка незаметна, но при сотнях запросов в минуту число пар быстро растёт. Измеряйте p50 и p95, а не только среднее время.

Исследование Nogueira и Cho о passage re-ranking показывает, почему совместное чтение запроса и фрагмента может улучшать порядок результатов по сравнению с быстрым первым этапом, но оно также иллюстрирует общую архитектурную идею, а не гарантию для любой современной модели: Passage Re-ranking with BERT.

Контрольный эксперимент перед внедрением

Перед подключением reranker к генератору проведите четыре проверки.

Зафиксируйте набор запросов. Не меняйте его между запусками и храните разметку отдельно от кода.
2. Сравните порядок документов. Посмотрите 20–30 случаев, где новый метод изменил первые пять позиций.
3. Проверьте отрицательные примеры. Добавьте похожие, но неправильные документы: другую версию API, соседний продукт, устаревшую инструкцию.
4. Измерьте полный путь. Учитывайте получение кандидатов, переранжирование, сериализацию и передачу контекста модели.

Результат стоит принимать по заранее заданному порогу. Например, команда может считать внедрение оправданным, если Recall@5 вырос минимум на 10% относительно базового поиска, а p95 задержки увеличился не более чем на установленный лимит. Сам порог зависит от продукта: для интерактивного помощника и ночной пакетной обработки допустимы разные компромиссы.

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

Reranker имеет смысл подключать не потому, что он является обязательной частью RAG, а когда измерения показывают конкретную проблему с порядком найденных документов. Начните с базовой линии, проверьте Recall первичного поиска, затем сравните локальный CrossEncoder и API на одинаковом списке кандидатов. Перед продакшеном отдельно проверьте актуальность документов, дубликаты, p95 задержки и стоимость каждой пары «запрос — фрагмент».

Источники