L3 Открытый урок · Стажёр
L3.7. Основы оценки: golden set и eval-скрипт
Golden set, детерминированные проверки, hit@k, eval-скрипт с порогом и списком провалов и почему разница в один кейс — не улучшение.
- Объём
- 4 урока · 8 часов
- Компетенции
- EVAL-02·A, PROMPT-05·F
«Стало лучше» — это число
Без измерения вы не знаете, помогла ли правка промпта, новая модель эмбеддингов или другой размер фрагмента. Глаз видит три удачных примера и пропускает два сломанных. Eval — это набор кейсов и скрипт, который выдаёт число, одинаковое при каждом запуске на тех же данных.
Golden set
Golden set (эталонный набор) — 15–20 кейсов на старте: вход и то, что считается правильным. Формат — JSONL, одна строка на кейс:
{"id": "q01", "question": "Какая гарантия на фильтр Аква-3?", "relevant": ["warranty"], "expected_substring": "24 месяца"}
{"id": "q07", "question": "Можно ли вернуть фильтр без чека?", "relevant": ["returns"], "expected_substring": "без чека"}
{"id": "q15", "question": "Продаёте ли вы кофемашины?", "relevant": [], "expected_substring": "нет"}
Как собрать хороший набор:
- из реальных вопросов пользователей, а не придуманных под систему;
- трудные случаи обязательно: синонимы, опечатки, вопрос вне базы (правильный ответ — «не знаю»), вопрос с кодом товара;
- эталон проверяет человек, не модель;
- набор не используется для подбора промпта и примеров (вспомните вопрос 10 квиза 1).
Детерминированные проверки
Начинайте с проверок, которые не требуют модели-судьи:
| Проверка | Пример | Подходит для |
|---|---|---|
| точное совпадение | category == "брак" |
извлечение полей, классификация |
| подстрока | "24 месяца" in answer |
факт в ответе |
| попадание в top-k | нужный doc_id среди первых 5 | поиск |
| валидность схемы | Complaint.model_validate не упал |
структурированный вывод |
| числовой допуск | abs(price - 1490) < 1 |
извлечение чисел |
hit@k — доля запросов, у которых хотя бы один релевантный документ попал в первые k результатов. Метрики полноты и порядка (recall@k, MRR, nDCG) — на L4.7.
Eval-скрипт
import json
import pathlib
import sys
THRESHOLD = 0.8 # ниже — код возврата 1: в CI это блокирует изменение (L6.2)
def hit_at_k(retrieved: list[str], relevant: set[str], k: int) -> float:
if not relevant: # вопрос вне базы: поиск не оцениваем здесь
return 1.0
return 1.0 if relevant & set(retrieved[:k]) else 0.0
def main() -> int:
cases = [json.loads(l) for l in pathlib.Path("evals/golden.jsonl").read_text(encoding="utf-8").splitlines() if l.strip()]
scores, failures = [], []
for c in cases:
retrieved = [doc_id for doc_id, _ in search_doc_ids(c["question"], k=5)]
s = hit_at_k(retrieved, set(c["relevant"]), k=5)
scores.append(s)
if s == 0:
failures.append((c["id"], c["question"], retrieved[:3]))
value = sum(scores) / len(scores)
print(f"hit@5 = {value:.2f} на {len(cases)} кейсах")
for f in failures:
print(" FAIL", *f)
return 0 if value >= THRESHOLD else 1
if __name__ == "__main__":
sys.exit(main())
Что важно в этом скрипте: число на выходе, список провалов (по нему вы чините систему), порог и код возврата. Эталон референса — evals/run_evals.py.
Маленький набор — большая погрешность
На 20 кейсах один кейс — это 5 процентных пунктов. Разница 0,80 против 0,85 — это один вопрос, и она может быть случайной (особенно если модель отвечает по-разному от запуска к запуску). На L3 правило простое: изменение меньше 2 кейсов не считаем улучшением; прогоняйте генеративные проверки 2–3 раза. Доверительные интервалы — на L6.2.
Оценка промпта (PROMPT-05·F)
Цикл улучшения промпта: прогнать eval → посмотреть провалы → понять причину → изменить промпт → снова прогнать весь набор (правка для одного кейса часто ломает другой) → записать результат в CHANGELOG. Подбор «на глаз» — антипаттерн из технологического радара курса.
Практикум: eval-скрипт на 15–20 кейсах
Golden set для своего поисковика из L3.6: 15–20 кейсов, из них минимум 3 трудных и 2 вопроса вне базы. Скрипт выводит hit@1, hit@5, список провалов и возвращает код 1 ниже порога. Прогоните для двух моделей эмбеддингов из L3.6 и запишите числа. Обоснуйте порог: почему именно такой для вашего домена.
