Eval как release-критерий: как ловить agent drift до прода, а не после | Григорий Добряков

Григорий Добряков

Howto · разбор

Разбор 10

Eval как release-критерий: как ловить agent drift до прода, а не после

AI-фича, одобренная на тех же входах, что и питч, ломается в проде на длинном хвосте реальных случаев. Eval-harness делает набор проверок критерием релиза и гоняет фичу против этого хвоста: regression из прошлых инцидентов, distribution-diff и human spot-check — до деплоя, а не после инцидента.

CTO Head of AI Tech Lead

Проблема

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

Методика

Сделать качество AI-вывода release-критерием, а не ретроспективной находкой. Минимальный eval при релизе — три слоя.

  1. 1. Layer 1 — Regression (non-negotiable). Фиксированный набор reference-кейсов с известным ожидаемым выводом: edge-кейсы прошлых инцидентов, кейсы из демо (которым одобряли фичу), минимум один adversarial-вход на тип вывода (prompt injection, пустой, malformed). Любой новый фейл блокирует релиз.
    # cases/rag-stale-corpus.yaml
    id: rag-stale-corpus
    origin: incident-2026-03   # RAG галлюцинировал на полном корпусе
    input:
      query: "действующая ставка по тарифу X"
    expected:
      must_contain:
        - "актуальный документ"
      must_not_contain:
        - "status: archived"
      retrieved_chunks:
        min_count: 1
        must_contain:
          - "active"
        must_not_contain:
          - "archived"
    pass_criteria:
      - no_archived_docs
      - answer_grounded

    Кейс рождается из инцидента (origin) — это и отличает regression set от demo-датасета: входы взяты из того, что уже ломалось в проде.

  2. 2. Layer 2 — Distribution check (RAG/классификаторы). 20–50 свежих реальных входов через новую версию. Проверка не на «идеал», а на diff против снапшота прошлого релиза: распределение длины/формата, изменение retrieved-чанков, сдвиг confidence (сдвиг к краям = хрупкость).

    Механика — не статтест, а пороги на сводных статистиках против снапшота:

    # layers/distribution.py
    LENGTH_DRIFT_THRESHOLD = 0.30          # rel-change среднего > 30% → флаг
    CONFIDENCE_STD_DRIFT_THRESHOLD = 0.20  # рост std confidence > 20% → хрупкость
    
    std_growth = (current_std - baseline_std) / baseline_std
    if std_growth > CONFIDENCE_STD_DRIFT_THRESHOLD:
        flags.append(f"confidence std grew {std_growth:.1%} — fragility signal")
    
    # сдвиг среднего confidence к краям [<0.35 | >0.85] при движении > 0.15
    edge_shift = abs(current["confidence_mean"] - baseline_mean)
    if edge_shift > 0.15 and (mean < 0.35 or mean > 0.85):
        flags.append("confidence mean shifted toward edges")

    Почему пороги, а не KS/PSI: на 20–50 сэмплах двухвыборочный Колмогоров-Смирнов недомощен и даёт ложные тревоги; пороги на mean/pstdev — дешёвый гейт для такого N. Плата — не ловятся изменения формы (бимодальность, утолщение хвоста): грубые сдвиги среднего/разброса видны, тонкие — нет. Путь апгрейда, когда набор растёт: PSI или two-sample KS на распределениях длины и confidence.

  3. 3. Layer 3 — Human spot-check. Обязателен перед первым проддеплоем нового вида вывода: доменный человек читает 10–20 реальных выводов с конкретными вопросами (нет ли данных, к которым модель не должна иметь доступ; счёл бы эксперт это корректным; нет ли adversarial-эксплуатации).
  4. 4. Owner. Один человек подписывает sign-off. Если перед релизом нельзя назвать владельца — это уже первый тревожный признак.

Артефакт

Минимальный harness под этот чеклист: Python-скрипт, который прогоняет regression set против snapshot и сравнивает distribution (Layer 1 + Layer 2). Система под тест подключается через тонкий адаптер — harness не знает про модель, только про контракт:

# adapter.run(input) -> {"output": str, "chunks": list, "confidence": float}
response = adapter.run(raw_input)

Wiring как release-gate — через exit-коды, чтобы CI мог блокировать мёрж:

python run_eval.py --all --inputs inputs.jsonl
#   exit 0  — оба слоя прошли, релиз разрешён
#   exit 1  — regression упал  → жёсткий блок (known-bad вернулся)
#   exit 2  — distribution drift → мягче: сигнал на ревью, не автоблок

Разделение неслучайно: regression — это «известное сломалось», однозначный стоп; distribution drift — «поведение поехало», повод для человека, а не автоматический отказ. Снапшот обновляется явным --update-snapshot, то есть baseline двигается осознанно, а не сам собой.

Где ломается

Для кого и почему

Риск: eval зелёный, прод красный — стандартная история. Узнают после релиза, чинят дороже, чем если бы поймали до.

Решение: фича не уходит в прод, пока три слоя eval не прошли — не потому что «выглядит хорошо», а потому что по критерию.

Метрика: борд спрашивает «что у нас с AI-качеством» — отвечаешь не «тестировали», а показываешь regression и distribution diff.

Ближе всего к playbook: артефакт уровня «как устроена практика» для Head of AI и технического CTO. Вайб-кодинг harness'а делает из методики воспроизводимый код-артефакт — закрывает code-modus, не только playbook.

Об авторе

Григорий Добряков — fractional / interim eng + AI leadership

Хотите построить eval-процесс для AI-фич в вашей команде?

Постановка eval как release gate: от regression set и distribution check до sign-off процесса. Не теория — воспроизводимая практика с checklists и harness.

Обсудить fractional-аудит Написать на почту

Другие разборы

Серия инженерных разборов: реальная задача → методика → работающий артефакт → честный разбор, где он ломается.

К серии →