К содержимому
МАТЕРИАЛЫ КУРСА
НАВИГАЦИЯ

Evals: качество, доказанное цифрами

Golden dataset из use cases, метрики DeepEval, пороги по baseline и регрессия в CI

Обычный тест отвечает «да» или «нет»: функция вернула то, что ожидали, или нет. С моделью так не выходит — один и тот же вопрос даёт разные формулировки, и обе могут быть правильными. Поэтому качество AI-продукта меряется не равенством строк, а оценкой по критериям на наборе примеров.

Позиция курса жёсткая: регрессионные тесты и evals в CI — не опция, а определение «сделано». Пока нет прогона, «работает» — это ощущение, а не факт. Отсюда и порядок: evals пишутся до кода, а не после.

Откуда берутся evals: из use cases

Evals не придумываются отдельно. Они выводятся из того же артефакта, из которого выводится всё остальное — из use case, а конкретно из его раздела «Как поймём, что работает».

Строчка UC → проверка в наборе:

Строчка в UCЧто становится eval
«каждый вопрос со ссылкой на фрагмент источника»проверка: в ответе есть цитата из контекста
«вопросы по выбранной теме»проверка: ответ не уходит за рамки темы
«ответ в формате списка из 5–20 пунктов»проверка структуры и границ

Если в UC нет ни одной измеримой строчки — проверять нечего, и это дефект UC, а не evals. Работает и в обратную сторону: не получается придумать проверку — значит требование сформулировано слишком расплывчато.

Golden dataset: 30–50 пар

Golden dataset — набор пар «вход → эталонный ответ», на котором гоняются метрики. Это главный артефакт роли Quality.

Правила сборки:

  • 30–50 пар на MVP. Меньше — статистика не значима, больше — не успеете поддерживать в семестре.
  • Покрывать не только счастливый путь. Треть набора — краевые случаи из раздела UC «Альтернативы и исключения»: пустой ввод, мусорный ввод, тема за пределами материалов, слишком короткий контекст.
  • Эталон пишет человек. Сгенерировать эталоны той же моделью, которую проверяете, — это померить модель по ней самой.
  • As-code. Набор лежит в репозитории (evals/dataset.yaml или json), версионируется вместе с кодом. Правка набора — такой же PR, как правка кода.

Каждая пара — это минимум: вход, эталон, и какой UC она проверяет. Привязка к UC потом показывает, какой сценарий проседает.

Метрики: что брать

В курсе берём DeepEval по одной причине: он работает поверх pytest, а значит evals попадают в CI тем же способом, что и обычные тесты — без отдельной инфраструктуры и без новой команды для студента. Метрик в нём много, но брать все не нужно и вредно: чем больше метрик, тем меньше понятно, что именно сломалось.

Набор, которого хватает проекту курса, — и не больше:

МетрикаЧто ловитКогда обязательна
Answer Relevancyответ не по вопросувсегда
Faithfulnessвыдумывание того, чего нет в контекстеесли есть RAG
Contextual Precision / Recallретривер принёс не то или не всёесли есть RAG
Hallucinationфакты, противоречащие источникуесли продукт про факты
G-Eval (свой критерий)всё специфичное для вашего продуктапочти всегда

G-Eval — главный для авторских требований: критерий описывается обычным текстом. «Ответ содержит ссылку на фрагмент материала», «тон нейтральный, без оценочных суждений» — это ваши критерии из UC, и их не покрывает ни одна готовая метрика.

Важное про LLM-судью: он тоже модель и тоже ошибается. Поэтому судья — инструмент сравнения версий («стало лучше или хуже»), а не абсолютная истина. Спорные случаи смотрит человек.

Пороги: сначала замер, потом планка

Частая ошибка — назначить «точность 90%» до первого запуска. Порядок обратный:

  1. Собираете golden dataset.
  2. Гоняете baseline — самое простое решение, которое вообще работает (принцип «картину добывай быстрой пробой»: baseline в ноутбуке за полдня, а не неделя раздумий).
  3. Смотрите цифры baseline — это ваша точка отсчёта.
  4. Ставите порог чуть выше и записываете рядом с набором, чем за него платите: скоростью, стоимостью запроса, покрытием. Порог без этой строчки через месяц никто не сможет ни защитить, ни подвинуть.

Порог, взятый с потолка, ломает CI на ровном месте и обесценивает всю затею — команда просто отключает джоб.

Регрессия в CI: определение «сделано»

Прогон в CI отвечает на один вопрос: новое не сломало старое.

evals/test_questions.py
import pytest
from deepeval import assert_test
from deepeval.metrics import AnswerRelevancyMetric, FaithfulnessMetric
from deepeval.test_case import LLMTestCase
from deepeval.dataset import EvaluationDataset

dataset = EvaluationDataset()
dataset.add_test_cases_from_json_file(
    file_path="evals/dataset.json",
    input_key_name="input",
    expected_output_key_name="expected",
    context_key_name="context",
)

@pytest.mark.parametrize("golden", dataset.goldens)
def test_answer(golden):
    case = LLMTestCase(
        input=golden.input,
        actual_output=generate(golden.input),   # ваша функция
        expected_output=golden.expected_output,
        retrieval_context=golden.context,
    )
    assert_test(case, [
        AnswerRelevancyMetric(threshold=0.7),   # порог из baseline
        FaithfulnessMetric(threshold=0.8),
    ])

Запуск локально и в CI — deepeval test run evals/. В GitHub Actions это обычный шаг: упал порог — красный PR.

Практика, которая экономит деньги и время: в CI на каждый PR гоняется мини-набор (5–10 показательных пар), полный набор — по расписанию или перед релизом. Полный прогон на каждый коммит съедает токен-бюджет.

Типичные ошибки

  • Evals после кода. Набор, написанный под уже готовые ответы, ничего не проверяет — он их описывает.
  • Только счастливый путь. Продукт разваливается на краевых случаях, а набор их не видит.
  • Эталоны от той же модели. Замер превращается в самоподтверждение.
  • Один прогон = вывод. Модель недетерминирована; сравнивать версии нужно на одном и том же наборе и лучше по нескольким прогонам.
  • Метрика без привязки к UC. Красивое число, по которому непонятно, какой сценарий сломался.

Дальше — безопасность: что делать, когда на вход приходит не пользователь, а атака.

Источники