Новый поиск RAG проиграл по метрикам: проверяем, что попало в разметку
Команда меняет поиск по внутренней базе знаний. Старый поиск по ключевым словам хорошо находит точные названия, новый добавляет векторных кандидатов. При сравнении на отложенных вопросах новый проигрывает по Precision@3. Один из его результатов — инструкция, которая прямо отвечает на вопрос. В таблице разметки такой инструкции нет, потому что аннотаторам когда-то показывали только выдачу старого поиска.
У документа три возможных состояния
Документ может быть подтверждённо полезным, подтверждённо бесполезным или ещё не проверенным. Когда реализация метрики подставляет ноль для любого отсутствующего ID, последние два случая сливаются. Для сравнения двух поисковиков на одной глубине полезно рядом выводить долю непроверенных позиций. Если у старой системы она нулевая, а у новой высокая, первая задача — понять, какие источники отсутствуют в эталоне.
Проверьте также единицу сравнения. Если разметка хранит ID раздела инструкции, а новый индекс выдаёт ID чанка, сначала сопоставьте их. Для одинаковых версий одного документа одинаковые фрагменты должны попадать под общее правило учёта. Если ответ требует конкретного абзаца, размечайте абзац и сохраняйте версию документа. Релевантность всего файла может скрыть ситуацию, когда найден правильный PDF, но переданный модели кусок не содержит нужного правила.
Общий пул для двух выдач
Возьмите первые k результатов обеих систем на каждый вопрос. Объедините кандидатов по ID, сохранив пару «вопрос, источник». Эксперту покажите вопрос, источник и критерий полезности. Порядок и название системы скройте, чтобы новая инструкция получила ту же оценку, что старая. Результат разметки примените к обеим выдачам. Так работает пул кандидатов, который позволяет не читать весь корпус при каждом сравнении.
В примере вопрос звучит так: «Можно ли перенести согласованное окно обслуживания?» Выдачи разных поисковиков содержат частично разные инструкции. Известно, что раздел A описывает перенос, а B и C относятся к другим операциям. Систему оценим по трём позициям. Новые разделы D и E первоначально никто не читал.
k = 3
runs = {"старый": ["A", "B", "C"], "новый": ["D", "B", "E"]}
judgments = {"A": True, "B": False, "C": False}
def report(qrels):
results = {}
for name, ranked in runs.items():
top = ranked[:k]
hits = sum(qrels.get(doc) is True for doc in top)
unjudged = sum(doc not in qrels for doc in top)
results[name] = (hits, unjudged)
print(f"{name}: precision {hits}/{k}, неизвестно {unjudged}/{k}")
return results
before = report(judgments)
pool = {doc for ranked in runs.values() for doc in ranked[:k]}
missing = sorted(pool - judgments.keys())
print("На разметку:", missing)
# Результат слепой проверки экспертом для данного вопроса.
reviewed = {"D": True, "E": True}
assert reviewed.keys() == set(missing)
after = report(judgments | reviewed)
assert before["новый"] == (0, 2)
assert after["старый"] == (1, 0) and after["новый"] == (2, 0)
До проверки старый поиск получает 1/3, новый — 0/3 при двух неизвестных результатах. После добавления оценок обеих новых инструкций старый остаётся на 1/3, новый получает 2/3. Мы ничего не меняли в ранжировании. Изменилось то, какие найденные документы человек успел оценить. В живом сравнении слепая проверка может подтвердить и обратное: новые результаты действительно бесполезны.
Что зафиксировать перед следующим релизом
В отчёте храните глубину пула и долю проверенных позиций по системе. Если бюджет разметки позволяет, собирайте пул глубже оцениваемого k: новый запуск той же системы может переместить полезный источник вверх. Если бюджет мал, соберите недостающие оценки в первых позициях и покажите, какая часть сравнения всё ещё опирается на неизвестные документы. Не делайте вывода из одного вопроса. Разметка нужна на выборке разных типов запросов, включая те, где меняется источник ответа.
Полный пул top-k даёт определённый Precision@k для сравниваемых выдач. Знаменатель Recall@k — число всех релевантных документов — из него автоматически не появляется. В корпусе могут быть другие полезные инструкции, которых ни одна система не вернула. Если считаете recall относительно размеченного пула, укажите это в отчёте. Для следующей замены поисковика снова посмотрите долю неизвестных позиций, прежде чем сравнивать итоговые числа.
Практика на Latorn
На платном курсе «RAG на Python» вы настроите гибридный поиск, добавите реранжирование и измерите качество выдачи через Recall@K и nDCG. Пул из этой статьи поможет подготовить сравнение для нескольких вариантов поискового конвейера. Посмотреть программу и практикумы по поиску.
Продолжить на практике
- RAG на Python: векторный поиск и ответы по своим документам ↗
Создавайте поиск и LLM-ассистентов по своим документам. Практический курс RAG на Python: FAISS, Qdrant, Weaviate, гибридный поиск и проверка качества. Научитесь находить нужные фрагменты и подключать их к модели. Есть практикумы и пробные уроки.
