Гайд · TNWS AI
Как собрать JSONL-набор для регрессионной проверки AI-ответов
Практическая проверка JSONL-фикстур AI-классификатора: формат строк, ожидаемые метки, Python-скрипт и запуск в GitHub Actions.
Задача и применимость
Редактор заявок хочет фиксировать согласованные примеры обращений и получать ошибку CI, если новая версия классификатора перестала относить срочный платёж к категории urgent. Создайте data/eval.jsonl с одной JSON-записью на строку. Ключ id должен однозначно описывать сценарий, input содержит синтетическое обращение, expected — согласованную метку. Не называйте ожидаемым результатом ответ текущей версии модели без ручной проверки: такой набор просто закрепит её ошибку. Храните изменённые фикстуры в том же PR, что и код, чтобы ревьюер мог проверить новое ожидание. Здесь используется локальная тестовая фикстура: значения приведены как пример входа и ожидаемого поведения, а не как измерение реального сервиса. Если ваш проект вызывает внешнюю модель, проверьте документацию выбранного поставщика отдельно и не считайте локальную симуляцию проверкой доступности API.
Названия методов Python, синтаксис GitHub Actions и используемые версии actions сверены с официальными первоисточниками 19.09.2026. Команды в шаблонах следует запускать в собственном репозитории после проверки путей; никакие цены, тарифы и результаты производительности в статье не предполагаются.
Подготовка данных и критерий ошибки
Перед кодом сформулируйте один проверяемый инвариант из задачи: что конкретно должно пройти и какой специально нарушенный пример обязан завершиться ошибкой. Используйте синтетические обращения и стабильные ID. Данные, попавшие в репозиторий или журнал CI, могут увидеть участники проекта; не копируйте сюда реальные обращения, ключи и закрытые документы. Фикстура не должна зависеть от сегодняшнего ответа модели: ожидаемое значение задаётся после предметной проверки.
Оставьте тестовые файлы рядом с кодом. При изменении бизнес-правила обновляйте его описание и expected осознанно, отдельным ревью. Простой зелёный запуск не является доказательством: сначала проверьте, что отрицательный кейс действительно делает job красным. Ошибка должна сообщать ID или номер строки, но не раскрывать чувствительный исходный текст.
Входная фикстура, которую можно скопировать
Сохраните строки в обозначенный ниже файл; комментарии с путями — пояснение и не входят в JSONL или CSV. Если пример показывает две версии ответа, прогоните их отдельно, как описано в шагах.
{"id":"urgent-payment","input":"Срочно: оплата не проходит","expected":"urgent"}
{"id":"ordinary-receipt","input":"Когда придёт чек?","expected":"normal"}
Пошаговая реализация
- Создайте каталог
scriptsи файлscripts/evaluate.py; при необходимости добавьте каталогdataи пример из предыдущего блока. Убедитесь, что кодировка текстовых файлов — UTF-8. - Скопируйте скрипт ниже и запустите команду
python scripts/evaluate.pyлокально. Для проекта с отдельным модулем замените демонстрационную функцию на импорт приложения. Сначала сохраните исходное проверяемое поведение. - Скопируйте оба блока, сохраняя строки JSONL без внешних квадратных скобок. Выполните
python scripts/evaluate.pyлокально, затем добавьте workflow из третьего блока. Для реального клиента замените функциюclassify, но не печатайте в журнале текст обращения из production. Обрабатывайте некорректный JSON отдельно: сообщениеJSONDecodeErrorс номером строки позволит найти повреждённую фикстуру. Попробуйте намеренно заменить expected у первого кейса на normal: CI обязан показать имяurgent-payment, actual и expected, а затем завершиться с ошибкой. - Добавьте workflow из следующего блока в
.github/workflows/ai-evaluation.yml. Он запускается для pull request и не передаёт секреты коду из PR. Если сетевой интеграционный тест всё же необходим, выведите его в отдельный доверенный workflow и ограничьте доступ к ключу. - Откройте успешный и намеренно неуспешный запуск. Убедитесь, что статус job соответствует коду завершения Python, а сообщение указывает на кейс, который нужно исправлять. После проверки верните корректные данные, не оставляйте искусственный сбой в ветке.
# scripts/evaluate.py
import json
from pathlib import Path
def classify(text):
# Замените на ваш адаптер, сохранив тот же интерфейс.
return "urgent" if text.startswith("Срочно:") else "normal"
failures = []
for number, line in enumerate(Path("data/eval.jsonl").read_text(encoding="utf-8").splitlines(), 1):
if not line.strip():
continue
row = json.loads(line)
if not isinstance(row.get("id"), str) or not row["id"]:
raise SystemExit(f"line {number}: missing id")
if row.get("expected") not in {"urgent", "normal"}:
raise SystemExit(f"line {number}: unknown expected label")
actual = classify(row["input"])
if actual != row["expected"]:
failures.append(f"{row['id']}: {actual} != {row['expected']}")
if failures:
raise SystemExit("\n".join(failures))
print("All JSONL cases passed")
Готовый workflow для проверки в pull request
Файл ниже запускает скрипт через Python. permissions: contents: read оставляет встроенному токену только чтение кода. Если вы уже используете другой CI, сохраняйте тот же негативный тест и ненулевой код выхода, а файл workflow адаптируйте к своей инфраструктуре.
name: AI evaluation
on:
pull_request:
permissions:
contents: read
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: '3.13'
- run: python scripts/evaluate.py
Пример входа и ожидаемого результата
На входе Срочно: оплата не проходит ожидается urgent, на вопросе о чеке — normal; выход скрипта — All JSONL cases passed. При ожидаемой метке normal для срочного платежа появится urgent-payment: urgent != normal. Это наблюдаемое поведение демонстрационного кода, а не отчёт о запуске на внешней модели. В production проверяйте, что конкретный контракт подходит к бизнес-процессу, и сохраняйте вход/ожидание рядом с именем кейса.
Где этот тест может обмануть
JSONL — это не один JSON-массив: каждая непустая строка разбирается отдельно через json.loads. Проверка набора бессмысленна, если PR одновременно без обоснования меняет и функцию, и expected. Ревьюеры должны читать дифф фикстур. Отдельно проверьте отрицательные сообщения: не каждое слово «срочно» означает реальную срочность по бизнес-правилу. Если тест проверяет только формат, не представляйте его как оценку фактической правильности ответа. Если тест проверяет только список ID, не делайте вывода о полноте подтверждающих документов. Разделяйте сбой инфраструктуры, некорректный корпус и нарушение прикладного инварианта: у этих проблем разные действия.
Внимательно смотрите дифф при изменении ожидаемого результата. Ослабление утверждения ради зелёного статуса может скрыть ошибку. В публичных артефактах и summary показывайте только синтетический ID и минимально необходимые поля. При увеличении корпуса проверьте, что новый тестовый случай действительно добавляет неизвестную ранее ветку поведения, а не повторяет существующий текст с другой пунктуацией.
Чек-лист финальной проверки
- У входных данных есть понятный ID и проверенное ожидаемое поведение.
- Успешная фикстура проходит локально, испорченная падает с понятной причиной.
- Скрипт запускается по тому же пути в CI, что и в инструкции.
- Вход, журнал и репозиторий не содержат секретов или реальных персональных данных.
- Сравнивается актуальная версия кода; PR не подменяет эталон без ревью.
- Проверены границы именно этого теста: что он обнаруживает и что не проверяет.
Частые вопросы
Как добавить кейс с переводами строк?
Экранируйте перевод строки внутри JSON-строки как \n; физически запись должна оставаться одной строкой JSONL.
Почему проверяется id?
Имя кейса позволяет понять, какой контракт нарушен, даже если журнал не должен показывать исходное обращение.
Можно ли использовать реальные заявки?
Лучше создавать синтетические или обезличенные случаи, проверяя отсутствие персональных данных до коммита.
Как тестировать нестабильный ответ модели?
Отделите фиксированный парсер и обязательную метку от свободного текста; проверяйте строгий инвариант, а статистическую оценку проводите отдельно.
Как принять изменение после теста
Попросите второго участника проекта воспроизвести пример из статьи на своей ветке и оценить ожидаемый результат независимо от вывода скрипта. Особенно важно проверить, не изменили ли авторы PR эталон одновременно с функцией так, чтобы ошибка стала невидимой. Сохраните ссылку на конкретный запуск рядом с ревью и повторите тест после объединения изменений, если последующие коммиты затрагивают ту же обработку данных. При обнаружении настоящего сбоя сначала добавьте отдельную синтетическую фикстуру, которая воспроизводит проблему, и лишь потом исправляйте код. Так можно проверить, что исправление действительно удерживается, а тест не оказался формальным.
Официальная документация
Источники проверены 19.09.2026: 1, 2, 3, 4. Перед переносом на другой runtime ещё раз проверьте версии и актуальные поля документации.
Читайте также
Как исключить bias из weight decay через Optax
Optax 0.2.6: маска add_decayed_weights применяет decay к kernel и оставляет bias без изменения.
Как использовать Huber loss в Optax для регрессии с выбросами
Optax 0.2.6: Huber loss квадратичен около нуля и линеен после delta; проверяем ошибки 0,5 и 2.
Как использовать sigmoid focal loss в Optax
Optax 0.2.6: focal loss для logits 0 и labels 0/1 при gamma=2; проверка симметричного базового значения.
Комментарии
Пока тихо. Скажите первое слово