RAG с reranker: как найти этап, на котором пропал нужный документ

Команда добавила модель пересортировки документов: теперь перед ответом RAG-помощник оценивает каждый найденный фрагмент вместе с вопросом. На общей выборке стало лучше, но один важный регламент по-прежнему не попадает в контекст. Замена reranker выглядит естественным следующим шагом. Однако сначала нужно установить, видел ли он этот регламент вообще.

Поиск с пересортировкой состоит из двух решений. Первый этап быстро получает достаточно широкий список кандидатов. Второй внимательно оценивает только этот список и оставляет небольшую выдачу для генератора. Такая архитектура позволяет применять более дорогую модель к десяткам текстов вместо всего корпуса. Одновременно она создаёт жёсткую границу: документ за пределами списка кандидатов второй этап поднять не сможет.

Сохраните вход, а не только итог

Для каждого тестового вопроса запишите стабильные ID кандидатов в исходном порядке, их тексты, оценки второго этапа и окончательный порядок ID. Сопоставляйте результат модели с исходным кандидатом по ID или сохранённому индексу пары. Если после сортировки собрать текст по старым позициям, отчёт покажет одни документы, а генератор получит другие. Такая ошибка похожа на плохое качество модели.

Архитектура Retrieve & Re-Rank подаёт cross-encoder пары «вопрос — документ». Конкретная модель определяет формат входа, поддержку языков и ограничение длины. Сохраните текст после той подготовки, которая выполняется перед моделью: иногда ответ находится в конце длинного фрагмента и отрезается ещё до оценки.

Две метрики для двух мест потери

Пусть для вопроса известен один достаточный источник. Сначала проверим его наличие среди кандидатов: это recall по исходному списку. Затем проверим наличие среди документов, которые уйдут генератору. Дополнительно посчитаем обратную позицию первого релевантного документа — reciprocal rank. Для нескольких вопросов среднее этих значений называют MRR; в примере печатается отдельный RR каждого вопроса.

Следующий скрипт анализирует сохранённый прогон. Он не вызывает модель: его задача — локализовать потерю по границам реального контура. Для нового эксперимента замените runs выгрузкой своих ID и экспертной разметкой. В каждом примере итог ограничен двумя документами, исходный список содержит три.

# Разметка и сохранённые ID до и после reranker.
runs = [
    {"query": "refund", "relevant": {"refund-policy"},
     "candidates": ["shipping", "refund-policy", "exchange"],
     "final": ["shipping", "exchange"]},
    {"query": "invoice", "relevant": {"invoice-guide"},
     "candidates": ["payment", "invoice-guide", "tax"],
     "final": ["invoice-guide", "payment"]},
    {"query": "cancel", "relevant": {"cancel-guide"},
     "candidates": ["shipping", "payment", "tax"],
     "final": ["payment", "shipping"]},
]

for run in runs:
    candidates, final = run["candidates"], run["final"]
    assert len(candidates) == len(set(candidates))
    assert len(final) == len(set(final))
    assert set(final) <= set(candidates), "Потеряна связь с кандидатами"
    relevant = run["relevant"]
    assert relevant
    candidate_recall = len(set(candidates) & relevant) / len(relevant)
    final_recall = len(set(final) & relevant) / len(relevant)
    reciprocal_rank = next((1 / rank for rank, point_id
                            in enumerate(final, 1)
                            if point_id in relevant), 0.0)
    stage = ("retrieval" if candidate_recall == 0 else
             "reranker" if final_recall == 0 else "passed")
    print(run["query"], f"candidate_recall={candidate_recall:.2f}",
          f"final_recall={final_recall:.2f}",
          f"RR={reciprocal_rank:.2f}", stage)

Для refund исходный recall равен 1.00, финальный — 0.00: нужный источник был доступен и исчез на втором этапе. Для invoice обе полноты равны 1.00, а RR — 1.00: релевантный документ стоит первым. Для cancel исходный recall уже равен нулю. Здесь пересортировке нечего исправлять, и эксперимент нужно начинать с получения кандидатов.

Что проверить в каждой ветке

Если документ потерян на первом этапе, выполните тот же вопрос с большим числом кандидатов и посмотрите его позицию. Затем проверьте фильтры, наличие текста в индексе и поиск по словам. Рост числа кандидатов полезен, пока возвращает дополнительные релевантные документы за приемлемое время. Глубину исходного списка и число текстов для генератора меняйте независимо: это два разных бюджета.

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

Проверьте и направление сортировки: некоторые интеграции возвращают оценку релевантности, другие — расстояние или уже готовый порядок. Возьмите две явно различимые пары и проследите весь путь от ответа SDK до массива контекста. Не задавайте общий порог для оценок разных моделей: сначала сравните их распределения на одной размеченной выборке.

Если исходные кандидаты приходят из нескольких поисковых веток, удаляйте повторные ID перед пересортировкой. Иначе одинаковый фрагмент занимает несколько мест и сокращает разнообразие документов, реально доступных второму этапу.

Как сравнить две версии reranker

Подайте обеим версиям одинаковые сохранённые списки кандидатов. Это позволит увидеть эффект самой пересортировки. Потом отдельно выполните весь поиск целиком и измерьте пользовательский результат. Если одновременно менять embedding-модель, глубину поиска и reranker, природу улучшения будет трудно установить.

В отчёте сохраните исходную полноту, финальную полноту, MRR по вопросам и время второго этапа. Отдельно покажите вопросы, для которых кандидат вообще отсутствовал. Большое улучшение ранга на лёгких вопросах не должно скрывать нулевую доступность источника для важного сценария. Для более общего плана пригодится разбор метрик RAG.

В платном курсе Vector DB & RAG Developer вы соедините векторный и лексический поиск, примените реранжирование и измерите качество выдачи. Это помогает собрать оба этапа на своих документах и понимать, где исправлять ошибки. Посмотрите программу курса и открытый урок по Qdrant.