Как понять, что RAG-ассистент не выдумывает: 4 метрики и диагностика
Faithfulness, Answer Relevancy, Contextual Precision и Contextual Recall: что измеряет каждая метрика, какие пороги брать за точку старта и как по сочетанию значений понять, что чинить — retriever или промпт.
b9ad47e
Содержание
Коротко
- RAG ломается в двух местах: retriever приносит не то, или модель отвечает не по тому, что принёс retriever. Четыре метрики покрывают оба случая.
- Faithfulness ловит выдумки модели, Answer Relevancy — уход от вопроса, Contextual Precision — плохой порядок найденных фрагментов, Contextual Recall — пропущенное retriever’ом.
- Стартовые пороги, которые я использую: Faithfulness ≥ 0.8, Answer Relevancy ≥ 0.7, Contextual Precision ≥ 0.7, Contextual Recall ≥ 0.6. Это отправная точка, а не истина.
- Начните с 50–100 реальных вопросов с эталонными ответами. Этого хватает, чтобы ловить регрессии.
- Судья в этих метриках — тоже LLM. Ему нельзя верить вслепую: сначала сверьте его оценки с ручной разметкой.
Почему «на глаз» не работает
Обычный код не меняется, пока вы его не тронули. RAG-система меняется, даже когда вы ничего не делали: обновилась база знаний, провайдер выпустил новую версию модели, поменялся порядок чанков в выдаче. Качество может «поплыть» в четырёх слоях: данные, retriever, промпт и модель.
Типичная картина без измерений выглядит так. Команда прогоняет десяток вопросов вручную и решает, что всё хорошо. После смены модели или промпта никто не может сказать, стало лучше или хуже. Результаты лежат в чатах и таблицах, сравнить их между версиями нельзя.
Метрики заменяют ощущение «вроде работает» числом, которое можно сравнить с прошлым прогоном и с порогом.
Как устроена оценка: LLM-as-a-judge
Все четыре метрики считает модель-судья. Наивный вариант — отдать судье вопрос, ответ и контекст и попросить «оцени от 1 до 10». У него несколько проблем:
- Нестабильность. Модель плохо держит абсолютную шкалу: один и тот же ответ получает 7 и 8 в соседних запусках.
- Необъяснимость. «6/10» не говорит, что сломалось и что чинить.
- Смещения судьи. Судьи любят длинные ответы (verbosity bias), первый из вариантов (position bias) и собственный стиль (self-preference).
- Несравнимость. Если каждый пишет свой промпт-судью, цифры из разных экспериментов нельзя сравнивать.
Фреймворки вроде DeepEval и RAGAS решают это декомпозицией: вместо одной оценки судья принимает много маленьких бинарных решений (например, по каждому утверждению ответа: подтверждено контекстом или нет). Бинарные решения LLM принимает заметно надёжнее, чем «поставь балл», а итоговое число получается делением и потому объяснимо: видно, какое именно утверждение не прошло.
Четыре метрики
| Метрика | Порог по умолчанию | Вопрос, на который отвечает | Что ловит |
|---|---|---|---|
| Faithfulness | ≥ 0.8 | Опирается ли ответ на найденный контекст? | Галлюцинации |
| Answer Relevancy | ≥ 0.7 | Отвечает ли система именно на заданный вопрос? | Уход от темы, «воду» |
| Contextual Precision | ≥ 0.7 | Стоят ли релевантные фрагменты выше нерелевантных? | Плохое ранжирование, шум |
| Contextual Recall | ≥ 0.6 | Нашёл ли retriever всё, что нужно для ответа? | Пропуски в поиске |
Пороги — значения по умолчанию в моём инструменте, и их стоит переопределять под проект. Как их выбирать правильно, разберём ниже.
Faithfulness
Судья извлекает из ответа атомарные утверждения и для каждого проверяет, подтверждается ли оно найденным контекстом. Итог — доля утверждений без противоречий.
Тонкость, о которой часто не знают: в DeepEval по умолчанию утверждение, о котором в контексте ничего нет (вердикт «не знаю»), не штрафуется. Получается, что метрика ловит противоречия контексту, а не любые выдумки. Чтобы штрафовать и их, включают penalize_ambiguous_claims=True. На примере из моего конспекта скор при этом падает с 0.80 до 0.60. Если в ответе нет ни одного утверждения (например, «не знаю»), скор равен 1.
Ещё одна оговорка: Faithfulness ничего не говорит о правильности. Ответ может быть верен контексту, а контекст — устареть.
Answer Relevancy
Ответ разбивается на утверждения, и каждое сравнивается с вопросом. Метрика ловит воду, уход от темы и рекламные вставки. Фактическую верность она не проверяет: только то, что ответ — про заданный вопрос.
Contextual Precision
Судья помечает каждый найденный фрагмент как релевантный или нет и считает метрику с учётом позиции — по той же идее, что Average Precision в классическом поиске. Порядок важен, потому что LLM лучше использует то, что стоит в начале контекста.
Contextual Recall
Судья разбивает эталонный ответ (не ответ модели) на утверждения и проверяет, можно ли каждое из них найти в извлечённом контексте. Эта метрика не может быть посчитана без эталона, и это её главное ограничение.
Как читать сочетания метрик
Главная ценность разбивки на четыре метрики — диагностика. Когда упал общий результат, сочетание значений показывает, какой компонент виноват.
| Что видите | Что происходит | Что проверять |
|---|---|---|
| Низкая Faithfulness | Модель «додумывает» | Системный промпт, температуру, инструкцию отвечать только по контексту |
| Низкая Contextual Recall | Retriever не находит нужное | Разбиение на чанки, эмбеддинги, top-k, гибридный поиск, переписывание запроса |
| Высокая Recall, низкая Precision | Нужное найдено, но в хвосте выдачи | Reranker, фильтрация по метаданным |
| Низкая Answer Relevancy | Ответ уходит в сторону | Промпт и то, как формулируется запрос к retriever |
| Retriever в порядке, Faithfulness низкая | Модель игнорирует или искажает контекст | Промпт, модель, меньше шума в контексте |
Простой алгоритм диагностики провального вопроса:
- Откройте провальный кейс и посмотрите, был ли правильный фрагмент в найденном контексте.
- Если нет — проблема в retrieval: чанкинг, эмбеддинги, нужен BM25 или гибридный поиск, reranker, переписывание запроса.
- Если да — проблема в генерации: промпт, модель, шум в контексте.
Отдельный сигнал — Faithfulness 0.95, а пользователи жалуются на неверные ответы. Тогда метрика честно говорит: ответ соответствует контексту. Искать надо в самом контексте: устаревшие документы, плохая база знаний, вопросы, на которые в базе нет ответа.
Offline и online: два режима оценки
Оценить можно готовые ответы из файла или работающую систему.
Offline. Вопрос, эталон, ответ и контекст уже лежат в датасете. Это быстро и дёшево, подходит для сравнения судей и отладки порогов. Минус: вы оцениваете снимок, а не то, что видит пользователь.
Online. Для каждого вопроса вы сами вызываете RAG-сервис, забираете ответ и найденные фрагменты и оцениваете реальное поведение системы целиком — вместе с retriever, промптом, ранжированием и постобработкой. Это ближе к тому, что получает пользователь.
Для online-режима достаточно описать HTTP-контракт: куда отправлять запрос, как подставлять вопрос из датасета и в каких полях JSON-ответа лежат ответ и фрагменты. Доступ к коду сервиса не нужен.
Практическая схема: offline — при подборе метрик и порогов, online — перед релизом и по расписанию.
DeepEval и RAGAS: чем отличаются и зачем два
Оба фреймворка считают одни и те же по смыслу метрики через модель-судью, но реализации и промпты у них разные. Поэтому значения одной и той же метрики в них не совпадут один в один.
DeepEval удобен тем, что встраивается в pytest и CI: есть assert_test, запуск deepeval test run, порог и режим strict_mode на каждую метрику, объяснение вердикта через include_reason=True. RAGAS привычен для исследовательских прогонов и датасетов.
В DeepRisk я запускаю оценку через оба фреймворка из одного интерфейса. Независимая вторая оценка снижает риск, что вы оптимизируете систему под особенности одного судьи. Если два судьи сильно расходятся, это сигнал, что метрика на этих данных требует уточнения, а не слепого доверия.
Минимальный quality gate на DeepEval
Так выглядит проверка, которая гоняет четыре метрики по датасету на каждом pull request:
# test_rag.py — запуск: deepeval test run test_rag.py
import pytest
from deepeval import assert_test
from deepeval.dataset import EvaluationDataset, Golden
from deepeval.metrics import (
FaithfulnessMetric, AnswerRelevancyMetric,
ContextualPrecisionMetric, ContextualRecallMetric,
)
from deepeval.test_case import LLMTestCase
from my_app import rag_answer # ваша функция: вопрос -> (ответ, чанки)
dataset = EvaluationDataset(goldens=[
Golden(input="Сколько дней есть на возврат?",
expected_output="30 дней, бесплатно при наличии чека."),
# ... остальные вопросы из реальных логов
])
JUDGE = "gpt-4.1" # фиксируем судью
metrics = [
FaithfulnessMetric(threshold=0.8, model=JUDGE),
AnswerRelevancyMetric(threshold=0.7, model=JUDGE),
ContextualPrecisionMetric(threshold=0.7, model=JUDGE),
ContextualRecallMetric(threshold=0.7, model=JUDGE),
]
@pytest.mark.parametrize("golden", dataset.goldens)
def test_rag(golden: Golden):
answer, chunks = rag_answer(golden.input)
test_case = LLMTestCase(
input=golden.input,
actual_output=answer,
expected_output=golden.expected_output,
retrieval_context=chunks,
)
assert_test(test_case=test_case, metrics=metrics)
Здесь Contextual Recall получил порог 0.7, а не 0.6 из таблицы выше: подставьте значение, которое получилось на вашей базовой версии.
С чего начать: 50–100 реальных вопросов
Не нужно строить огромный бенчмарк. Соберите 50–100 реальных вопросов с эталонными ответами — из логов поддержки, тикетов, чатов. Этого достаточно, чтобы ловить регрессии, и собирается это за пару дней.
Несколько правил, которые экономят время:
- Берите реальные формулировки пользователей, а синтетику используйте как дополнение.
- Размечайте вопросы по типам: простые, требующие нескольких источников, такие, на которые в базе нет ответа (система должна сказать «не знаю»), провокационные. Смотрите метрики по срезам, а не только среднее.
- Считайте регрессию относительно прошлого прогона, а не только относительно порога.
- Знайте стоимость прогона заранее: число кейсов × число метрик × 2–4 вызова судьи на метрику. Цифры иллюстративные: оценка зависит от судьи и длины контекста.
Как выбрать порог и не обмануть себя
- Прогоните эталонную (текущую рабочую) версию и зафиксируйте базовые значения.
- Задайте пороги немного ниже базовых значений. Цель порога — ловить ухудшения, а не выглядеть красиво.
- Пересматривайте пороги по мере роста датасета, а не «под красивый отчёт».
И про судью. Прежде чем делать метрику частью quality gate:
- Вручную разметьте 50–100 примеров и посчитайте согласие судьи с людьми (accuracy или Cohen’s κ).
- Зафиксируйте модель-судью и версию библиотеки, иначе цифры «поплывут» после обновления.
- Судья не должен быть слабее оцениваемой модели. По возможности берите судью из другого семейства: так меньше self-preference.
Метрики без эталона (Faithfulness, Answer Relevancy, Contextual Relevancy) можно считать и на выборке реального трафика. Это основа для онлайн-мониторинга.
Что дальше
Если у вас нет датасета — начните с него: выгрузите 50–100 вопросов, напишите эталоны и прогоните четыре метрики на текущей версии. Зафиксируйте базовые значения и поставьте пороги чуть ниже. Затем добавьте проверку в CI, чтобы смена промпта, модели или базы знаний сразу показывала регрессию.
Следующий шаг — безопасность: качество ответа ничего не говорит о том, как система ведёт себя под атакой. Об этом — в разборе про red teaming по OWASP.
Платформа, на которой я запускаю всё описанное, — DeepRisk, лицензия MIT. Подробнее: neon-dev.ru.


