Датасет решает всё

Любая оценка — это функция от двух вещей: метрики (как считаем «хорошо») и датасета (на чём считаем). Про метрики был весь прошлый раздел; здесь — про вторую половину, которую часто недооценивают. А зря: плохой датасет обесценивает любую, даже идеальную метрику.

Две болезни нерепрезентативного набора:

  • Ложная уверенность. Датасет из лёгких, похожих примеров → метрика 95% → релиз → прод падает на реальных трудных запросах, которых в наборе не было. Ты измерил не то.
  • Слепые зоны. Чего нет в датасете — то не проверяется. Нет примеров на другом языке, edge cases, провокаций → регрессии в этих зонах проходят незамеченными.
ℹ️ Датасет — это выборка из реальности

Ключевая идея: твой eval-датасет должен быть маленькой моделью реального трафика. Насколько распределение примеров в наборе похоже на то, что встречают пользователи, — настолько метрике можно верить. Отсюда две цели при сборе: покрытие (все типы запросов, включая трудные и редкие представлены) и репрезентативность (пропорции близки к реальным). Датасет — не «побольше примеров», а «правильная выборка».

Три источника тест-кейсов

Кейсы берут из трёх источников, и у каждого своя роль — они дополняют друг друга, а не конкурируют.

100%
колёсико — масштаб  ·  зажать и тянуть — перемещение
Три источника кейсов → единый eval-датасет ① Ручные эксперт пишет вручную критичные кейсы, edge cases ✓ точные, продуманные ✗ дорого, мало, медленно золотое ядро качества ② Из прод-трейсов реальные запросы юзеров (LangSmith/Langfuse → dataset) ✓ РЕАЛЬНОЕ распределение ✗ нужен прод + разметка + PII репрезентативность ③ Синтетические LLM генерирует примеры масштаб, вариации, редкие ✓ быстро, много, дёшево ✗ «модель проверяет себя», шум объём и покрытие Eval-датасет (курированный) проверенный человеком · размеченный · версионированный ручные = ядро качества · прод = реальность · синтетика = объём/покрытие лучший датасет комбинирует все три — не полагайся на один источник

① Ручные кейсы

Эксперт (разработчик, доменный специалист) пишет примеры руками: вход + ожидаемое поведение. Дорого и медленно, поэтому их немного — но они самые продуманные. Сюда идут критичные для бизнеса сценарии, каверзные edge cases, требования безопасности. Это золотое ядро набора, которому доверяешь больше всего.

② Из прод-трейсов

Самый ценный источник репрезентативности: реальные запросы пользователей, извлечённые из трейсов (LangSmith/Langfuse умеют добавлять трейс в датасет одним кликом — мы про это говорили в уроках observability). Они несут настоящее распределение того, что встречает агент, включая формулировки, до которых ты бы сам не додумался. Минус — нужен работающий прод, разметка ожидаемого выхода и очистка от PII.

③ Синтетические

LLM генерирует примеры по инструкции — быстро, много, дёшево. Отлично для объёма и покрытия: наплодить вариаций, редкие комбинации, стресс-кейсы, которых в проде мало. Но есть фундаментальный риск (ниже) — «модель проверяет саму себя», и качество примеров нужно контролировать.

Генерация синтетических примеров

Синтетика соблазнительна масштабом. Базовый приём — попросить модель сгенерировать входы (и, где возможно, эталонные выходы) по описанию сценариев:

Генерация тест-кейсов LLM (концептуально)
python
GEN_PROMPT = """Сгенерируй {n} разнообразных вопросов пользователей к агенту
поддержки интернет-магазина по теме "{topic}".
Включи: обычные вопросы, пограничные случаи, нетипичные формулировки,
короткие и длинные, вежливые и раздражённые.
Верни JSON-список: [{{"input": "...", "category": "..."}}]."""

def synth_cases(topic: str, n: int = 20) -> list[dict]:
    raw = llm(GEN_PROMPT.format(n=n, topic=topic), temperature=0.9)  # ↑ разнообразие
    return json.loads(raw)

# усиление покрытия: генерировать ПО КАТЕГОРИЯМ, а не одной кучей
topics = ["возврат", "доставка", "оплата", "гарантия", "жалоба"]
cases = [c for t in topics for c in synth_cases(t, n=10)]

# ⚠️ синтетику ОБЯЗАТЕЛЬНО курировать (см. ниже) — не в датасет напрямую
# эталонные выходы для критичных кейсов лучше проверять/писать человеком
⚠️ «Модель проверяет саму себя» — главная ловушка синтетики

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

Покрытие и репрезентативность

«Много примеров» ≠ «хороший датасет». Важнее две характеристики, и их стоит осознанно контролировать:

СвойствоВопросКак обеспечить
ПокрытиеВсе ли типы запросов представлены?Генерация/сбор по категориям, чек-лист сценариев, явные edge cases
РепрезентативностьПохожи ли пропорции на реальные?Прод-трейсы как основа распределения
СложностьЕсть ли трудные, а не только лёгкие?Специально добавлять каверзные и «провальные» кейсы
БалансНет ли перекоса в один класс?Следить за долями категорий
✅ Покрытие даёт синтетика, репрезентативность — прод

Практичная стратегия деления труда: прод-трейсы задают реалистичное распределение (репрезентативность), синтетика добивает покрытие редких/трудных зон, которых в проде мало, а ручные кейсы закрывают критичное и служат эталоном доверия. Затем всё курируется. Так ты одновременно и «как в жизни», и «не пропустил редкое».

Курирование и поддержка

Датасет — не разовый артефакт, а живой актив, который курируют и версионируют, как код. Что входит в курирование:

  • Проверка человеком. Синтетика и прод-кейсы просматриваются: убрать мусор, дубли, некорректные эталоны. Особенно — эталонные выходы критичных кейсов.
  • Разметка ожиданий. К каждому входу — ожидаемое поведение/выход/свойства (для метрик и regression).
  • Версионирование. Датасет меняется во времени; фиксируй версии, чтобы результаты оценки были сравнимы (это делают LangSmith/Langfuse).
  • Рост из прода. Каждый новый баг/интересный трейс → в датасет (связь с regression testing: набор становится историей ошибок).
🔒 Прод-данные в датасете = PII и утечка

Прод-трейсы содержат реальные пользовательские данные: имена, email, номера, содержимое запросов. Прежде чем класть их в датасет (который хранится, шарится, попадает в CI и, возможно, в облако judge'а) — маскируй/анонимизируй PII и учитывай согласие/комплаенс. Это та же осторожность, что в уроках про логи и трейсы: данные из прода не переезжают в тестовый актив «как есть».

⚠️ Держи eval-датасет отдельно от тренировочных данных

Если примеры из eval-набора попали в контекст/промпты/файн-тюн модели — оценка на них становится нечестной (data leakage: система «видела ответы»). Держи оценочный датасет изолированным от того, что используется в самой системе, и обновляй его, если подозреваешь утечку. Оценка имеет смысл только на данных, которых система «не знает».

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

❌ Ошибка 1: маленький лёгкий датасет

10–20 простых похожих примеров дают ложные 95% и слепые зоны. Нужны покрытие (все типы, включая трудные) и репрезентативность (пропорции как в проде).

❌ Ошибка 2: только синтетика

Полностью сгенерированный набор = «модель проверяет себя»: слепые зоны и завышенные оценки. Обязательно вмешивай ручные и прод-кейсы, курируй синтетику.

❌ Ошибка 3: прод-данные без маскирования

PII из трейсов в датасете — утечка при хранении/шаринге/CI. Анонимизируй перед добавлением, учитывай комплаенс.

❌ Ошибка 4: датасет «собрал и забыл»

Статичный набор устаревает: трафик меняется, появляются новые сценарии. Кури, версионируй и пополняй датасет из свежих прод-багов постоянно.

Шпаргалка

Evaluation datasets — всё в одном месте
text
ДАТАСЕТ решает всё: оценка = метрика × ДАТАСЕТ · плохой набор обесценивает метрику
  болезни нерепрезентативности: ложная уверенность (лёгкие 95%→прод падает) · слепые зоны
  датасет = маленькая ВЫБОРКА из реальности → цели: ПОКРЫТИЕ + РЕПРЕЗЕНТАТИВНОСТЬ

3 ИСТОЧНИКА (комбинировать!):
  ① РУЧНЫЕ — эксперт пишет: критичное, edge cases · точные, но дорого/мало → ЯДРО качества
  ② ПРОД-ТРЕЙСЫ — реальные юзеры (LangSmith/Langfuse→dataset) · РЕАЛЬНОЕ распределение → репрезентативность
  ③ СИНТЕТИЧЕСКИЕ — LLM генерит · быстро/много → объём/ПОКРЫТИЕ · но «модель проверяет себя»

СИНТЕТИКА: генерить ПО КАТЕГОРИЯМ, temperature↑ для разнообразия
  ⚠️ ловушка: одна модель генерит+отвечает+судит → мерит согласие с собой, не качество
     → генерить одной/судить другой · вмешивать людей+прод · КУРИРОВАТЬ руками

ПОКРЫТИЕ (все типы?) ← синтетика · РЕПРЕЗЕНТАТИВНОСТЬ (пропорции?) ← прод ·
  СЛОЖНОСТЬ (трудные?) · БАЛАНС (без перекоса)

КУРИРОВАНИЕ (живой актив, как код):
  проверка человеком · разметка ожиданий · ВЕРСИОНИРОВАНИЕ · рост из прод-багов (→ regression)
  🔒 PII из прода → МАСКИРОВАТЬ перед добавлением
  ⚠️ eval-датасет ОТДЕЛЬНО от тренировочных данных (data leakage → нечестная оценка)

ГРАБЛИ: маленький лёгкий набор · только синтетика · PII без маскирования · «собрал и забыл»

Практическое задание

Задание: собери eval-датасет

  1. Для агента из модулей 03–05 составь чек-лист типов запросов (категорий), которые он должен обрабатывать. Это твоя карта покрытия.
  2. Напиши 5 ручных кейсов на критичное и edge cases (вход + ожидаемое поведение). Почему именно их не стоит доверять генерации?
  3. Сгенерируй 15 синтетических кейсов по категориям (temperature повыше). Просмотри их: сколько мусора/дублей? Что выкинешь при курировании?
  4. Возьми 5 «прод»-подобных запросов (или из своих тестов агента). Отличается ли их распределение/формулировки от синтетики? Чем именно?
  5. Найди в своём наборе риск «модель проверяет себя»: где генератор, отвечающий и судья — одна модель? Как разорвать этот круг?
  6. Пройдись по PII: есть ли в прод-кейсах данные, которые нельзя хранить? Замаскируй их.
  7. Со звёздочкой: собери финальный курированный датасет из трёх источников, размести ожидания, заведи его как versioned dataset в LangSmith/Langfuse и прогони на нём offline-метрику из прошлого раздела.

Что дальше

До сих пор мы оценивали результат агента (правильный ли ответ). Но у агента есть ещё и путь к результату — какие шаги он сделал, какие инструменты вызвал, не блуждал ли зря. Оценка этого пути — отдельная дисциплина. Финальный урок раздела — trajectory evaluation: как оценивать последовательность действий агента, а не только финальный ответ.