Как оценить качество RAG-ответов без ручной проверки
Вы поменяли модель эмбеддингов, подправили параметры чанкинга или добавили реранкер — и теперь нужно понять, стало лучше или хуже. Открывать 50 диалогов руками и читать каждый ответ можно один раз, но не после каждого коммита в пайплайн. Дальше расскажу, как разложить оценку RAG на три отдельные измеримые метрики и прогонять их автоматически на небольшом наборе тестовых вопросов — без человека в цикле на каждой итерации.
Содержание
- Почему ручная проверка RAG не масштабируется
- Три слоя качества RAG, которые стоит мерить отдельно
- Метрика поиска: попадание в топ-N (recall@k) — без единого вызова LLM
- LLM-судья №1: обоснованность ответа найденными фрагментами (grounding)
- Golden-набор и регулярный прогон при изменениях пайплайна
- Ограничения LLM-судьи: почему не стоит доверять ему слепо
Почему ручная проверка RAG не масштабируется
RAG-система — это конвейер из двух разных шагов: поиск релевантных фрагментов и генерация ответа на их основе. Каждый шаг ломается по-своему, но при ручной проверке вы видите только финальный текст и оцениваете его целиком — «нравится» или «не нравится». Это создаёт три проблемы.
Во-первых, это не масштабируется по времени. Если у вас 100 тестовых вопросов и вы меняете параметры чанкинга раз в неделю, час-два на прочтение ответов после каждого изменения быстро съедает бюджет времени на улучшения — и через месяц-два вы просто перестаёте тестировать после мелких правок, что и приводит к тихой деградации.
Во-вторых, целостная оценка «нравится/не нравится» не говорит, что именно сломалось. Ответ вышел плохим — потому что поиск не нашёл нужный фрагмент, модель проигнорировала контекст и придумала факт от себя, или текст технически верный, но не отвечает на заданный вопрос? Без разделения на компоненты вы чините наугад: сегодня меняете промпт, завтра — модель эмбеддингов, послезавтра реранкер, и непонятно, что из этого реально помогло.
В-третьих, ручная оценка субъективна и непостоянна: один и тот же ответ два разных человека оценят по-разному. Без числовой метрики невозможно объективно сравнить версию A и версию B пайплайна — только «вроде получше».
Решение — не искать один универсальный «скор качества ответа», а измерять три ортогональных, каждую отдельно и максимально механически.
Три слоя качества RAG, которые стоит мерить отдельно
Разберём RAG-ответ на составляющие, каждую можно проверить независимо от остальных:
- Качество поиска (retrieval) — нашёл ли поисковый движок тот фрагмент документа, где реально лежит ответ на вопрос. Это вопрос к векторной базе, модели эмбеддингов, параметрам чанкинга и реранкеру — генерация текста тут вообще не участвует.
- Фактическая обоснованность (grounding / faithfulness) — действительно ли утверждения в сгенерированном ответе подтверждаются найденными фрагментами, или модель что-то добавила от себя. Это вопрос к промпту генерации и к самой LLM — независимо от того, насколько хорош был поиск.
- Релевантность ответа вопросу — отвечает ли текст на то, что реально спросили, или уходит в сторону, приводит общие рассуждения вокруг темы, отвечает на смежный, но другой вопрос.
Важно: это независимые оси. Можно найти идеальный фрагмент и сгенерировать ответ, полностью на него опирающийся, — и всё равно не ответить на заданный вопрос (низкая релевантность при высоком grounding). А можно ответить точно в тему, но добавить одно выдуманное число, которого не было в источнике (высокая релевантность, но провал по grounding). Разделение экономит часы отладки: retrieval-метрика упала — чинить эмбеддинги и чанкинг, grounding упал при хорошем retrieval — чинить промпт генерации, упала только релевантность — смотреть, как модель интерпретирует вопрос. Подробнее о том, что происходит на каждом шаге между вопросом и ответом — в статье что происходит внутри RAG между вопросом и ответом.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереМетрика поиска: попадание в топ-N (recall@k) — без единого вызова LLM
Это самая дешёвая и надёжная из трёх метрик, потому что она вообще не оценивает текст — только совпадение идентификаторов.
Идея: у вас есть заранее подготовленный набор вопросов, для каждого из которых вы ЗАРАНЕЕ знаете, какой конкретный фрагмент (chunk_id или document_id) документа содержит правильный ответ. Прогоняете вопрос через поисковый движок, смотрите — попал ли известный правильный фрагмент в топ-N результатов поиска. Никакой LLM для этого не нужно — чистая механическая проверка совпадения ID.
Пример golden-набора для retrieval-теста (JSONL, одна строка — один вопрос):
{"question": "Как продлить лицензию SSL-сертификата на сервере?", "expected_chunk_id": "docs/ssl-setup.md#chunk-14"}
{"question": "Какой порт использует Postgres по умолчанию?", "expected_chunk_id": "docs/db-setup.md#chunk-3"}
{"question": "Как включить автопродление баланса VPS?", "expected_chunk_id": "docs/billing.md#chunk-7"}
Скрипт оценки (упрощённо, на Python, векторная база любая — Qdrant, pgvector, Meilisearch):
import json
def recall_at_k(golden_path, retriever, k=5):
total = 0
hits = 0
with open(golden_path) as f:
for line in f:
item = json.loads(line)
total += 1
results = retriever.search(item["question"], top_k=k)
returned_ids = [r.chunk_id for r in results]
if item["expected_chunk_id"] in returned_ids:
hits += 1
return hits / total
score = recall_at_k("golden_retrieval.jsonl", my_retriever, k=5)
print(f"recall@5 = {score:.2%}")
Запускаете эту функцию после каждого изменения — смены модели эмбеддингов, других параметров чанкинга, добавления реранкера — и сравниваете число с предыдущим прогоном. Это секунды на десятки вопросов, без затрат на LLM-вызовы.
Есть грабля: если вы меняете параметры чанкинга (размер чанка, overlap), chunk_id в индексе тоже меняются — старый golden-набор с привязкой к конкретным ID перестаёт быть валидным напрямую. Практичнее привязывать expected не к id чанка, а к диапазону строк документа или к document_id + подстроке, которая обязана присутствовать в найденном фрагменте — тогда переиндексация не ломает тестовый набор. Про то, как чанкинг влияет на итоговое качество ответов: RAG выдавал чушь — проблема была в чанкинге.
LLM-судья №1: обоснованность ответа найденными фрагментами (grounding)
Здесь уже нужна вторая языковая модель — судья, которая проверяет не сам факт (это было бы отдельной, гораздо более сложной задачей верификации истины), а согласованность сгенерированного текста с тем, что реально лежит в найденных фрагментах.
Механика: судье на вход подаётся (а) вопрос, (б) фрагменты, которые вернул поиск, (в) сгенерированный ответ. Задача судьи — разбить ответ на атомарные утверждения и для каждого проверить: подтверждается ли оно текстом фрагментов, противоречит им, или фрагменты вообще не содержат информации по этому утверждению (в последнем случае это обычно означает, что модель дополнила ответ из общих знаний — то есть добавила от себя то, чего не было в источнике).
Пример промпта для судьи (используйте модель, отличную от той, что генерирует ответы — снижает риск, что судья и генератор одинаково ошибаются в одну сторону):
Ты — строгий проверяющий фактической обоснованности ответов.
ВОПРОС ПОЛЬЗОВАТЕЛЯ:
{question}
НАЙДЕННЫЕ ФРАГМЕНТЫ-ИСТОЧНИКИ:
{retrieved_chunks}
СГЕНЕРИРОВАННЫЙ ОТВЕТ:
{generated_answer}
Разбей ответ на отдельные фактические утверждения. Для каждого утверждения укажи:
- SUPPORTED — прямо подтверждается текстом фрагментов
- CONTRADICTED — противоречит фрагментам
- UNSUPPORTED — фрагменты не содержат информации для проверки этого утверждения
Выведи JSON-массив объектов {"claim": "...", "verdict": "..."}.
Не оценивай фактическую правильность самого утверждения вне контекста фрагментов — только соответствие приведённому тексту источников.
Итоговая метрика grounding-score для одного ответа — доля утверждений со статусом SUPPORTED. Усредняете по golden-набору — получаете число, сравнимое между прогонами. Если после смены промпта генерации доля UNSUPPORTED резко выросла — модель начала чаще додумывать, надо вернуть более строгую инструкцию в духе «отвечай только на основе приведённых фрагментов, если информации недостаточно — так и скажи». Механику того, почему языковые модели склонны придумывать факты: почему LLM придумывает факты: механика галлюцинаций.
Держите температуру судьи на 0 (или близко к 0) — это не убирает вариативность полностью, но заметно снижает случайный разброс между одинаковыми прогонами на одном входе.
Golden-набор и регулярный прогон при изменениях пайплайна
Практика внедрения простая, но требует дисциплины.
Соберите golden-набор. Это 30–100 вопросов, отражающих реальные запросы пользователей вашей системы — не абстрактные, а формулировки, которые люди реально пишут (можно взять из логов, плюс добавить руками несколько «сложных» и «пограничных» кейсов: неоднозначные вопросы, вопросы про то, чего в базе знаний нет вообще, вопросы с опечатками). Для каждого зафиксируйте ожидаемый источник (chunk/document) и, если есть время, эталонный ответ или ключевые факты, которые в ответе обязаны быть.
Формат — плоский JSONL или CSV, чтобы его легко было прогонять скриптом и хранить в git рядом с кодом пайплайна:
{"id": "q001", "question": "...", "expected_source": "...", "reference_answer": "..."}
Прогоняйте оценку при каждом значимом изменении: смена модели эмбеддингов, изменение размера чанка или overlap, добавление/замена реранкера, правка системного промпта генерации, смена самой LLM-генератора. Скрипт последовательно считает recall@k, затем для каждого ответа вызывает судью grounding и судью релевантности, усредняет по набору и выводит три числа плюс дельту к предыдущему сохранённому прогону:
recall@5: 0.91 (было 0.86, +0.05)
grounding: 0.88 (было 0.90, -0.02)
relevance: 3.6/4 (было 3.4/4, +0.2)
Храните историю прогонов — простой CSV с датой, версией пайплайна и тремя числами уже даёт график тренда во времени и позволяет увидеть, какое изменение подвинуло метрику не туда. Golden-набор держите небольшим и представительным, а не исчерпывающим — каждый вызов судьи стоит денег и времени, а на 300+ вопросах прогон растягивается на десятки минут и тормозит саму итерацию, ради которой всё затевалось. Полный проход по устройству такого пайплайна с нуля — в статье RAG-пайплайн с нуля: практический пример.
Ограничения LLM-судьи: почему не стоит доверять ему слепо
Здесь важна честность: модель-судья — это тоже языковая модель со всеми её слабостями, а не безупречный эталон истины.
Судья может ошибаться систематически: известная особенность LLM-as-judge — склонность выше оценивать более длинные и «уверенно» написанные ответы независимо от фактической точности, чувствительность к формулировке промпта (небольшая правка инструкции судье может заметно сдвинуть итоговые числа) и непостоянство между запусками даже при температуре 0. Кроме того, судья хорошо ловит явные противоречия и грубые добавления «из головы», но может пропускать тонкие неточности, для распознавания которых нужна глубокая экспертиза именно в вашей предметной области.
Практический вывод: автоматическую оценку нужно периодически калибровать выборочной ручной проверкой. Не после каждого прогона — это снова сведёт всё к ручному труду, — а раз в несколько циклов или после крупного изменения (смена модели-судьи, переписывание промпта оценки): берёте случайную выборку из 15–30 ответов, читаете сами и сверяете свой вердикт с вердиктом судьи. Согласие высокое (условно, 85%+ по grounding-вердиктам) — можно доверять метрике как основному индикатору между калибровками. Согласие низкое или расходится в одну сторону — правьте промпт судьи или критерии, а не игнорируйте расхождение.
Автоматическая оценка LLM-as-judge — это не абсолютный сертификат качества, а компас для относительного сравнения: «после этого изменения стало лучше или хуже», а не «это идеальный ответ». Именно в этой роли — быстрой, регулярной, объективной проверки после каждой правки пайплайна — она и приносит основную пользу.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть ИИ на сервереНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужна ли для судьи отдельная, более мощная модель, чем та, что генерирует ответы?
Не обязательно более мощная, но желательно другая — если судья и генератор одна и та же модель с теми же слабостями, она может систематически не замечать собственные ошибки. Разные модели снижают риск такой корреляции, хотя и не убирают его полностью.
Сколько вопросов должно быть в golden-наборе?
Для старта достаточно 30–50 репрезентативных вопросов, покрывающих типичные и пограничные случаи вашей предметной области. Больше 100–150 обычно уже избыточно для регулярных прогонов — рост числа вопросов увеличивает время и стоимость каждого прогона быстрее, чем растёт точность метрики.
Можно ли использовать готовые библиотеки вроде RAGAS вместо самодельных промптов для судьи?
Да, такие open-source инструменты для оценки RAG существуют и реализуют похожие метрики (faithfulness, answer relevancy, context precision/recall) с готовыми промптами и агрегацией — это разумная отправная точка, но всё равно стоит проверить их промпты на своём домене и откалибровать вручную так же, как и самодельные.
Что делать, если recall@k высокий, а сгенерированные ответы всё равно выглядят плохо?
Значит, проблема не в поиске — фокус отладки нужно смещать на grounding (не додумывает ли модель) и релевантность (отвечает ли она на заданный вопрос), а также перепроверить сам промпт генерации: возможно, модели даётся слишком много нерелевантного контекста, из-за чего она теряется в фрагментах.
Как часто вообще нужно прогонять эту оценку?
После любого изменения, которое теоретически может повлиять на качество: смена модели эмбеддингов или генератора, правка параметров чанкинга, добавление или замена реранкера, изменение системного промпта. На практике это обычно совпадает с ритмом релизов пайплайна, а не с фиксированным расписанием.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →