← AI evals engineering: testing systems that never repeat themselves
04 / 07 · 50 min

Regression pipelines and CI gates

The prompt is code: an eval on every PR, promptfoo in GitHub Actions, absolute thresholds and non-regression, a smoke set on PRs and a full nightly run.

Lesson content is currently available in Russian.

What you will learn

  • →Проводить каждое изменение промпта и модели через eval-прогон в CI.
  • →Настраивать promptfoo в GitHub Actions со smoke- и full-наборами.
  • →Проектировать гейты: абсолютные пороги, непрегрессию и пороги по сегментам.

Промпт — это код

Изменение промпта меняет поведение продакшена так же, как изменение кода, — но во многих командах промпты живут в админке или в head разработчика и деплоятся мимо любых проверок. Первое решение курса на уровне процесса: промпт проходит тот же цикл, что и код. Промпты лежат в репозитории файлами, меняются через PR, каждый PR автоматически прогоняет eval, ревьюер видит не только дифф текста, но и дифф метрик. Версия промпта пишется в каждый продакшен-трейс — иначе инцидент нельзя привязать к изменению.

Что считается «изменением, требующим эвала»: текст промпта, модель и её версия, temperature и другие параметры сэмплинга, состав инструментов, логика retrieval, формат few-shot примеров. Смена gpt-4o на gpt-4o-mini «для экономии» без прогона — классический способ тихо уронить качество на проценты, которые никто не заметит до жалоб пользователей.

Экономика вопроса снимает главное возражение. Прогон 200 кейсов на недорогой модели плюс judge на модели среднего класса стоит десятки центов — порядки дешевле часа инженера, разбирающего продакшен-инцидент. Матрица прогона описывается декларативно: промпты × провайдеры × датасет, и это буквально формат конфига promptfoo. Единственный реальный расход — латентность CI, и она решается разделением наборов, о котором ниже: лёгкий smoke на каждый PR, полный — по расписанию.

promptfoo в GitHub Actions

Рабочий workflow: eval запускается только когда PR трогает промпты или конфиг, результат публикуется комментарием в PR:

name: prompt-eval
on:
  pull_request:
    paths:
      - "prompts/**"
      - "promptfooconfig.yaml"
jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: promptfoo/promptfoo-action@v1
        with:
          config: promptfooconfig.yaml
          openai-api-key: ${{ secrets.OPENAI_API_KEY }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

Ключевые детали эксплуатации. Кэш: promptfoo кэширует ответы провайдеров — неизменённые кейсы не перезапрашиваются, повторный прогон почти бесплатен; в CI кэш сохраняется через actions/cache. Детеминированность: для CI-прогонов ставьте temperature 0 и фиксируйте версию модели (не gpt-4o, а конкретный снапшот) — это не убирает всю вариативность, но убирает управляемую её часть. Секреты — только через secrets, ключи в конфиге не хардкодятся.

Выход прогона — не только pass/fail: promptfoo eval --output results.json даёт машиночитаемый отчёт для собственных гейтов (следующая секция), а веб-вью показывает по-кейсовый дифф двух прогонов — незаменимо на ревью, когда нужно понять, какие именно кейсы перевернулись и почему.

Гейты: абсолютные пороги и непрегрессия

Гейт — это правило, при нарушении которого CI краснеет и мерж блокируется. Практичная система — три уровня. Первый: абсолютные пороги на критичных подмножествах. Регрессионные кейсы инцидентов — порог 100%: выученная ошибка не имеет права повториться. Критичный сегмент (например, всё про деньги и юридические обещания) — 95–100%. Второй: непрегрессия против базовой ветки — pass rate PR не ниже, чем на main, минус допуск ε (обычно 1–2 п.п. на небольших наборах — это защита от шума, а не индульгенция). Третий: пороги по сегментам, а не только по датасету целиком: усреднение маскирует провал — система может подрасти на 30 лёгких кейсах и полностью сломать сегмент из 10, показав «рост» общей метрики.

Флейк — главная боль eval-гейтов. Источники: сэмплинг модели, нестабильный judge, сетевые таймауты. Приёмы: temperature 0 и зафиксированный снапшот модели; для кейсов, которые всё равно мигают, — прогон n=3 с вердиктом большинства; кэш убирает сетевой флейк повторных прогонов; и главное — статистическая трезвость: на 30 smoke-кейсах шаг метрики — 3.3 п.п., поэтому маленькие наборы годятся только для грубых регрессий, а тонкие сравнения делаются на полном сете (об этом урок про значимость).

Разделение наборов по скорости: smoke (~30 кейсов, каждый PR, до 5 минут, только ассерты и быстрый judge), full (весь golden set, ночью и перед релизом), critical (регрессионные и денежные кейсы — входят в оба). Тэги кейсов в metadata делают это разделение декларативным.

Наблюдаемость эвалов и связь с продом

Один прогон отвечает на вопрос «можно ли мержить этот PR». Ряд прогонов во времени отвечает на более важный: «куда движется качество системы». Поэтому результаты каждого прогона — pass rate общий и по сегментам, стоимость, латентность, версия датасета, промпта и модели — сохраняются как временной ряд: в Langfuse (datasets + experiments), в БД или хотя бы в артефакты CI. Без этого команда каждый раз спорит о «кажется, стало хуже» вместо того, чтобы посмотреть на график.

Второй контур — онлайн. Offline-эвал проверяет систему на прошлом, но продакшен постоянно подбрасывает новое. Замкните цикл: сэмпл живого трафика (1–5%) асинхронно скорится тем же калиброванным judge, деградация скора относительно скользящего среднего поднимает алерт. Каждый подтверждённый онлайн-fail проходит знакомый маршрут: трейс → разбор → новый кейс в golden set → отныне этот класс ошибок ловится в CI до деплоя. Это та самая копилка выученных ошибок в действии.

Третий элемент — отчёт для ревьюера. Хороший eval-комментарий в PR отвечает на три вопроса за десять секунд: изменился ли общий pass rate и по каким сегментам; какие конкретно кейсы перевернулись (со ссылками на дифф ответов); сколько прогон стоил. Перевернувшиеся кейсы важнее среднего числа: +2 п.п. в среднем при трёх новых fail в денежном сегменте — это не улучшение, а регрессия, замаскированная усреднением.

Practice

Красный CI за плохой промпт

Настройте GitHub Actions workflow с promptfoo для своего проекта: триггер по путям промптов, smoke-набор до 5 минут, кэш, комментарий с результатами в PR. Добавьте тэги smoke/critical в датасет и два гейта: 100% на critical-кейсах и непрегрессия против main с ε = 2 п.п. Затем откройте PR с намеренно испорченным промптом (удалите инструкцию про формат) и убедитесь, что CI красный, а из отчёта понятно, какие кейсы упали.

Deliverable

Ссылка (или скриншоты) двух PR: зелёного с безобидной правкой и красного с испорченным промптом, плюс файл workflow и фрагмент конфига с гейтами.

Checkpoint

Опишите свой регрессионный пайплайн: что триггерит eval, какие наборы гоняются когда, какие гейты блокируют мерж и как результаты связаны с продакшеном.

  • ✓ Любое изменение промпта/модели/параметров идёт через PR с автоматическим eval-прогоном и диффом метрик.
  • ✓ Наборы разделены: smoke на PR, full по расписанию, critical со 100%-м порогом; гейты включают непрегрессию по сегментам.
  • ✓ Результаты хранятся как временной ряд, онлайн-сэмпл скорится judge, продакшен-инциденты пополняют golden set.

Check yourself

Какие изменения должны триггерить eval-прогон в CI?

01Какие изменения должны триггерить eval-прогон в CI?

Почему pass rate нужно считать по сегментам, а не только по датасету целиком?

02Почему pass rate нужно считать по сегментам, а не только по датасету целиком?

Какой порог правильно ставить на регрессионные кейсы из продакшен-инцидентов?

03Какой порог правильно ставить на регрессионные кейсы из продакшен-инцидентов?

Smoke-набор из 30 кейсов показал падение pass rate на 3 п.п. Что это значит?

04Smoke-набор из 30 кейсов показал падение pass rate на 3 п.п. Что это значит?

Sources

Regression pipelines and CI gates · AI evals engineering: testing systems that never repeat themselves · VORCL