Как сделать eval критерием релиза AI-фичи, а не оценкой «на демо выглядело нормально»

Demo-grade eval одобряет фичу на тех же примерах, что и питч. Дальше прод показывает длинный хвост. Три слоя — regression из инцидентов, distribution diff к снапшоту, human spot-check — и один named owner. Минимальный harness: github.com/dobryakov/eval-harness.

Как сделать eval критерием релиза AI-фичи, а не оценкой «на демо выглядело нормально»

Сделайте качество AI-вывода release-критерием: фиксированный regression из прошлых инцидентов, distribution-diff к снапшоту прошлого релиза (20–50 реальных входов), human spot-check перед первым продом нового вида вывода — и один человек, который подписывает sign-off. Ниже — антипаттерн demo-grade, три слоя методики и минимальный harness, который падает в CI с понятным exit code.

Большинство AI-фич уезжает в прод в demo grade. Не потому что команда «не заботится» — потому что eval, который одобрил фичу, использовал те же входы, что и питч. Курированный датасет. Контролируемые условия. Длинный хвост реального трафика ни разу не прогоняли.

Потом прод показывает то, что демо не покрыло:

  • RAG корректно достаёт документы на стейджинге с курированным корпусом и галлюцинирует на полном корпусе со stale-записями;
  • классификатор тихо деградирует после смены входной схемы upstream;
  • AI-код проходит автотесты, но вносит security-паттерн, который тесты не покрывали.

Три разных стека. Один failure mode: нет структурированного eval, нет named owner, одобрение «на демо выглядело нормально».

Agent drift — когда поведение модели или агента уезжает от того, что вы считали «проверенным», без красного алерта в момент релиза. Иногда это смена промпта или корпуса. Иногда — тихий сдвиг распределения входов. Симптом один: зелёный eval на демо-наборе, красный прод на живом хвосте.

Когда вообще нужен AI-output review

Не каждый деплой. Триггер простой: меняет ли релиз то, что модель видит, или то, что она выдаёт?

Проверяйте до релиза, если выполняется хотя бы одно:

  • вывод идёт в customer-facing поток (ответ, рекомендация, классификация, поиск);
  • вывод влияет на бизнес-критичное решение (approval, routing, pricing, alert);
  • вывод строится на retrieval (RAG) — риск галлюцинации масштабируется с качеством retrieval;
  • с прошлого релиза сменились модель, промпт или корпус retrieval.

Инфраструктурный деплой, чистый UI, конфиг без влияния на prompt/retrieval — полный eval не обязателен. Экономия здесь нормальна; самообман — когда «инфра» тихо подменяет индекс или system prompt.

Владелец: один человек, не «команда»

Один человек владеет eval. Не «все». Не «в стейджинге выглядело нормально».

Типичная схема:

Роль Что делает
Feature owner (инженер или PM) формулирует «хороший вывод» в бизнес-терминах
ML/AI lead валидирует методику и критерии sign-off
Release approver (EM / tech lead) финальный gate; sign-off нельзя заменить фразой «в стейджинге вроде ок»

Если перед релизом нельзя назвать владельца — это уже первый тревожный признак. Дальше спор «кто должен был поймать» почти гарантирован.

Три слоя минимального eval

Layer 1 — Regression (non-negotiable)

Фиксированный набор reference-кейсов с известным ожидаемым выводом. В наборе обязаны быть:

  • edge-кейсы из прошлых инцидентов;
  • кейсы из демо, которыми одобряли фичу (да — их тоже надо прогонять, но не только их);
  • минимум один adversarial-вход на тип вывода: prompt injection, пустой ввод, malformed.

Критерий: все reference-кейсы в допустимом диапазоне. Любой новый фейл блокирует релиз.

Ключ: кейс рождается из инцидента. Поле origin в YAML — не бюрократия, а отличие regression set от demo-датасета. Входы взяты из того, что уже ломалось в проде, а не из слайда для совета директоров.

Пример из bundled-кейса rag-stale-corpus (репозиторий eval-harness):

id: rag-stale-corpus
origin: incident-2026-03
input:
  query: "действующая ставка по тарифу X"
expected:
  must_contain:
    - "актуальный документ"
  must_not_contain:
    - "archived"
  retrieved_chunks:
    min_count: 1
    must_contain:
      - '"status": "active"'
    must_not_contain:
      - '"status": "archived"'
pass_criteria:
  - no_archived_docs
  - answer_grounded

must_contain / must_not_contain проверяют текст ответа. retrieved_chunks — сериализованные чанки (часто JSON). Needle надо подбирать под реальный формат вашего RAG: строка "status: archived" в лог-формате не найдётся в {"status": "archived"} и тест пройдёт «вхолостую».

В том же репозитории лежат adversarial-кейсы: пустой ввод, malformed, injection (ignore previous instructions…). Их смысл — зафиксировать отказ, а не «красивый ответ на атаку».

Layer 2 — Distribution check

Для RAG и классификаторов. Берёте 20–50 свежих реальных входов, прогоняете новую версию, сравниваете не с «идеалом», а с diff к снапшоту прошлого релиза:

  • длина / формат ответа — выбросы часто значат сломанный промпт или смену схемы;
  • retrieved-чанки — изменились ли, и почему;
  • распределение confidence — сдвиг к краям = хрупкость.

В eval-harness флаги drift зашиты явно:

Сигнал Порог
средняя длина output vs snapshot сдвиг > 30%
std confidence рост > 20%
среднее число chunks сдвиг > 30%
mean confidence к краям < 0.35 или > 0.85 при заметном отличии от baseline

Это не «научная истина порогов» — это рабочий стоп-кран: если распределение уехало без объяснения, релиз стопорится, пока кто-то не разберёт причину.

После принятого выката snapshot обновляют отдельно (--update-snapshot). В release pipeline этот флаг не передают — иначе baseline «подстроится» под деградацию.

Layer 3 — Human spot-check

Процесс, не код. Обязателен перед первым проддеплоем нового вида вывода: доменный человек читает 10–20 реальных выводов с конкретными вопросами:

  • нет ли данных, к которым модель не должна иметь доступ;
  • счёл бы эксперт это корректным;
  • нет ли adversarial-эксплуатации.

На последующих релизах spot-check включается при смене модели, промпта, корпуса или после инцидента. Без этого слоя вы автоматизируете слепоту: regression зелёный, distribution «в допуске», а смысл ответа уже мусор.

Антипаттерн demo-grade

Demo grade — вывод, который выглядит корректным в контролируемых условиях и падает на реальном распределении входов.

Как это обычно уезжает в прод:

  1. фичу пилят и «тестируют» на примерах из брифа;
  2. бриф писали, чтобы показать capability, а не stress-test;
  3. стейджинг кормят курированным или синтетическим датасетом;
  4. ревьюер одобряет по «выглядит разумно»;
  5. прод открывает длинный хвост, которого в демо не было.

Симптомы, что вы релизите demo-grade:

  • входы eval = примеры из питча или sprint review;
  • до релиза фичу не гоняли на реальных production-входах;
  • «eval» — это стейкхолдерское демо, а не математика;
  • pass criteria формулируют после прогона («ну вроде ок»).

Фикс короткий и неприятный: demo dataset ≠ eval dataset. Демо показывает лучший случай. Eval меряет реальное распределение.

Артефакт: eval-harness в CI

Минимальный harness под Layer 1 + Layer 2: github.com/dobryakov/eval-harness. Python, YAML-кейсы, JSONL входов, snapshot, adapter interface.

Exit codes — чтобы gate был машиночитаемым:

Exit code Значение
0 можно релизить
1 упал Layer 1 (regression)
2 упал Layer 2 (distribution drift)

Быстрый старт:

pip install -r requirements.txt

python run_eval.py --layer regression
python run_eval.py --layer distribution --inputs inputs.jsonl
python run_eval.py --all --inputs inputs.jsonl

Свою модель / агента подключают через adapter.py:

def run(input: dict) -> dict:
    return {
        "output": str,        # текст ответа
        "chunks": list,       # retrieved chunks
        "confidence": float,  # 0..1
    }

В CI достаточно:

- run: pip install -r requirements.txt
- run: python run_eval.py --all --inputs inputs.jsonl

Каждый инцидент, который не должен повториться, становится permanent regression-кейсом в cases/. Иначе вы «починили» один раз и ждёте тот же баг под другим промптом.

Структура репозитория:

eval-harness/
  run_eval.py
  adapter.py
  layers/
    regression.py
    distribution.py
  cases/
    *.yaml
  snapshots/
    latest.json
  inputs.jsonl

Layer 3 в код не входит — и это правильно: human spot-check нельзя «закрыть зелёной галочкой скрипта».

Чеклист перед релизом, который меняет AI-вывод

  • [ ] Owner назван: кто подписывает AI-output quality
  • [ ] Regression set прогнан: все reference-кейсы зелёные
  • [ ] Нет новых failure modes относительно прошлого релиза
  • [ ] Distribution check сделан (для RAG / классификаторов): нет необъяснённого сдвига
  • [ ] Human spot-check сделан, если: новая capability / смена модели / промпта / был инцидент
  • [ ] Eval dataset ≠ demo dataset
  • [ ] Pass criteria зафиксированы до прогона, не после

Ограничения метода

  • Пороги distribution — эвристика. 30% / 20% ловят грубый drift; тонкую смысловую деградацию при стабильной длине ответа Layer 2 может пропустить — поэтому остаётся Layer 3.
  • Пустой origin и кейсы «с потолка». Если regression собран из «красивых» примеров, вы снова получили demo-grade, только в YAML.
  • Vacuous pass на чанках. Неверный формат needle в retrieved_chunks даёт ложный зелёный. Сначала сериализуйте реальный chunk так же, как adapter, потом пишите кейс.
  • Нет owner — нет gate. Харнесс в CI без человека, который отвечает за интерпретацию красного, превращается в шум, который научатся обходить (continue-on-error: true).
  • Snapshot после деградации. --update-snapshot в момент «ну ладно, так и будет» закрепляет плохое как норму.
  • Не каждый деплой. Раздутый обязательный eval на любой hotfix убивает adoption; держите триггер «меняет ли это ввод/вывод модели».

Для кого

Для CTO, Head of AI и tech lead, у которых уже есть пилоты и вопрос совета директоров звучит не «есть ли у нас AI», а «что у нас с AI-качеством». Ответ «тестировали» здесь слабый. Ответ «вот regression и distribution diff к прошлому релизу» — рабочий.

Соседние слои той же дисциплины: периметр агента без опоры на system prompt (архитектурный least privilege) и онбординг скиллов (поведение ассистента до того, как оно разъехалось). Eval — gate на вывод модели; они — на доступ и на введение нового поведения.

Итог

Если eval совпадает с питчем — вы проверяете не продакшн, а презентацию. Разделите demo и eval, соберите regression из инцидентов, меряйте distribution diff, назначьте одного owner и не выпускайте фичу, пока три слоя не прошли по критерию — не потому что «выглядит хорошо».

Репозиторий: github.com/dobryakov/eval-harness

Howto: dobryakov.com/howto/eval-harness-agent-drift.html

Leave a Reply

Your email address will not be published. Required fields are marked *