Гайд · TNWS AI

Как проверить Recall@3 выдачи документов для RAG на размеченных запросах

6 мин—

Практический расчёт Recall@3 для RAG: эталонные документы, найденные ID, пропуски и проверка воспроизводимости.

Задача и граница применения

Обнаружить, что нужный документ не попал в первые три результата поиска, даже если ответ генератора кажется убедительным. Набор состоит из вручную размеченных пар вопрос → набор релевантных ID. Считайте только поиск до генерации: модель может случайно угадать ответ, хотя выдача плохая, или ошибиться при корректно найденном источнике. В демо одному запросу соответствуют два релевантных документа, поэтому метрика учитывает долю найденных из двух, а не просто факт попадания одного. Пустой эталон не входит в расчёт: для него нужна отдельная политика отказа или поиска ответа вне коллекции. Этот практический сценарий рассчитан на команду, которая контролирует данные для поискового слоя RAG и может хранить синтетические фикстуры в репозитории. Он не требует отправлять реальные клиентские документы стороннему сервису. Все ID, вопросы и численные результаты ниже сконструированы только для демонстрации. Проверка официальной документации Python и синтаксиса GitHub Actions: 19.09.2026; в собственном окружении перепроверьте версии перед запуском.

Как подготовить фикстуру

Вопрос Q-17 «Когда пришлют акт?» имеет эталон {D-1,D-2}; поиск в первых трёх местах вернул D-2,D-5,D-6. Для Q-18 эталон {D-7}, выдача D-7,D-8. Ожидаемые доли: 1/2 и 1. Сначала отделите эталон, согласованный человеком, от фактической выдачи поисковой системы. Не исправляйте gold по результатам модели: иначе тест перестанет быть независимым. Стабильный идентификатор вопроса позволит повторить сбой без публикации текста закрытой заявки. Если документы меняются, записывайте номер версии индекса и дату действия содержимого, иначе повторная проверка через месяц будет сравнивать разные базы.

Шаблон входа для переноса примера на свою коллекцию: для каждого вопроса заведите поля id, question, expected_doc_ids, retrieved_doc_ids и, где требуется, index_version. Согласуйте с предметным владельцем, что именно считается релевантным, доступным и актуальным источником. Это рабочая схема сбора данных, а не универсальный формат API. В таблице ревью отдельно отметьте спорные случаи, которые нельзя разрешить одной лишь строковой операцией.

Пошаговая проверка

  1. Создайте файл scripts/check_retrieval.py; скопируйте Python-шаблон целиком. Использованные названия библиотечных функций сверены с официальными страницами в конце статьи.
  2. Запустите python scripts/check_retrieval.py локально. Сопоставьте консольный вывод с ожидаемым результатом в описании входа; демонстрация обязана проходить без внешнего API и ключей.
  3. Сначала согласуйте релевантность обоих документов Q-17 с человеком, владеющим процессом актов. Сохраните выдачу вместе с ID версии индекса и конфигурацией фильтров: иначе один и тот же запрос на следующей неделе может дать другие результаты. Выполните код и сравните 0.5 с фактической выдачей Q-17: присутствует только D-2. Замените синтетические массивы экспортом поискового слоя, не ответом генератора. Укажите число вопросов и распределение количества эталонных документов рядом с агрегатом; среднее по двум примерам не переносите на живую базу.
  4. Для регрессии положите тот же файл в репозиторий и добавьте workflow из второго блока в .github/workflows/rag-retrieval.yml. Проверка запускается на pull_request с разрешением contents: read; не передавайте в неё настоящие секреты и закрытые документы.
  5. Откройте job retrieval-check в Actions на конкретном коммите. Сравните исходные ID и обнаруженные нарушения с локальным прогоном. При расхождении проверьте версию фикстур и скрипта, а не меняйте эталон до выяснения причины.
# scripts/check_retrieval.py
cases = [
    {"id":"Q-17", "relevant":{"D-1","D-2"},
     "retrieved":["D-2","D-5","D-6"]},
    {"id":"Q-18", "relevant":{"D-7"},
     "retrieved":["D-7","D-8"]},
]
def recall_at_k(case, k):
    expected, found = case["relevant"], case["retrieved"][:k]
    if not expected or len(found) != len(set(found)):
        raise ValueError("empty gold or duplicate result ID")
    return len(expected.intersection(found)) / len(expected)
values = {row["id"]: recall_at_k(row, 3) for row in cases}
assert values == {"Q-17":0.5,"Q-18":1.0}
print(values)
name: RAG retrieval contract
on:
  pull_request:
permissions:
  contents: read
jobs:
  retrieval-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: '3.13'
      - run: python scripts/check_retrieval.py

Реалистичный пример результата

Вход: Вопрос Q-17 «Когда пришлют акт?» имеет эталон {D-1,D-2}; поиск в первых трёх местах вернул D-2,D-5,D-6. Для Q-18 эталон {D-7}, выдача D-7,D-8. Ожидаемые доли: 1/2 и 1. После запуска ожидается исход, явно проверяемый инструкциями assert в коде. Не публикуйте вымышленное значение как измеренную характеристику вашей поисковой системы. Для рабочего отчёта приложите версию индекса, выбранные фильтры, размер размеченного набора, дату фиксации эталона и список ID провалившихся случаев. По каждому ID откройте первичный документ, прежде чем менять конфигурацию ранжирования: неправильно размеченный источник может выглядеть как технический дефект.

Типовые ошибки и ограничения

Recall@3 не говорит, насколько высоко внутри тройки оказался нужный материал, и не измеряет правильность ответа. Разметка должна описывать все подходящие документы: если D-2 забыли в эталоне, оценка окажется неверной. Не подбирайте k после просмотра результатов теста. При расширении теста сначала добавьте отрицательный случай: намеренно испортите один элемент фикстуры и убедитесь, что проверка обнаружила его. Затем верните исходный набор и проверьте положительный случай. Это важнее зелёного CI на данных, где ошибка случайно отсутствует. Не логируйте полный ответ модели, содержимое закрытых документов или персональные сведения только ради диагностики.

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

Чек-лист финальной проверки

  • Задача теста и применимость понятны владельцу корпуса.
  • Входные ID и эталон не взяты из ответа оцениваемой модели.
  • Демо запускается локально и даёт ожидаемый результат.
  • Преднамеренно неверный вход обнаруживается проверкой.
  • Зафиксированы версия индекса, редакция документов и условия доступа.
  • В логах CI нет приватных документов и секретов.
  • Разделены ошибка поиска, ошибка эталона и ошибка генерации.
  • После исправления проверены соседние пограничные случаи.

FAQ

Почему не сравнить готовые ответы?

Здесь изолируется стадия поиска; генерация проверяется отдельно.

Что делать с вопросом без релевантного документа?

Выделить отдельный класс и проверять отказ от ответа, не делить на ноль.

Почему запретить повтор ID?

Дубликат забирает место у другого результата и искажает интерпретацию первых k.

Можно ли сравнить две версии поиска?

Да, на неизменном эталоне и одинаковых запросах; фиксируйте версию индекса и фильтров.

Что делать, если тест не прошёл

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

Официальные первоисточники

Проверено 19.09.2026: документ 1, документ 2, документ 3, документ 4. Описание и примеры написаны самостоятельно; при переносе на другое окружение перепроверьте названия функций и синтаксис workflow.

Читайте также

Комментарии

Пока тихо. Скажите первое слово