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

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

_Источник: https://sii.sergeivolchkov.ru/materials/quality/evals_



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

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

## Откуда берутся evals: из use cases [#откуда-берутся-evals-из-use-cases]

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

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

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

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

## Golden dataset: 30–50 пар [#golden-dataset-3050-пар]

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

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

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

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

## Метрики: что брать [#метрики-что-брать]

В курсе берём [DeepEval](https://deepeval.com) по одной причине: он
работает поверх 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-определение-сделано]

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

```python title="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.** Красивое число, по которому непонятно,
  какой сценарий сломался.

Дальше — [безопасность](/materials/quality/security): что делать, когда
на вход приходит не пользователь, а атака.

## Источники [#источники]

* [DeepEval — документация](https://deepeval.com/docs/evaluation-introduction)
* [Confident AI: LLM evaluation playbook](https://www.confident-ai.com/blog/the-ultimate-llm-evaluation-playbook)
