Гайд · TNWS AI
Как проверять JSON-схему ответа AI-классификатора без сторонних библиотек
Практическая проверка структуры ответа AI: обязательные поля, типы, допустимые метки, запрет лишних ключей и негативные тесты.
Задача и применимость
Не допустить запись в CRM ответа AI, в котором пропал ticket_id, priority имеет неизвестное значение или confidence пришёл строкой. Зафиксируйте контракт на границе приложения: ticket_id — непустая строка, priority — одна из двух согласованных меток, confidence — число в интервале от нуля до единицы. Не называйте такой валидатор полной реализацией стандарта JSON Schema: это компактная проверка именно трёх полей. Учитывайте, что в Python bool является подклассом int, поэтому True нельзя принять за числовую уверенность. Здесь используется локальная тестовая фикстура: значения приведены как пример входа и ожидаемого поведения, а не как измерение реального сервиса. Если ваш проект вызывает внешнюю модель, проверьте документацию выбранного поставщика отдельно и не считайте локальную симуляцию проверкой доступности API.
Названия методов Python, синтаксис GitHub Actions и используемые версии actions сверены с официальными первоисточниками 19.09.2026. Команды в шаблонах следует запускать в собственном репозитории после проверки путей; никакие цены, тарифы и результаты производительности в статье не предполагаются.
Подготовка данных и критерий ошибки
Перед кодом сформулируйте один проверяемый инвариант из задачи: что конкретно должно пройти и какой специально нарушенный пример обязан завершиться ошибкой. Используйте синтетические обращения и стабильные ID. Данные, попавшие в репозиторий или журнал CI, могут увидеть участники проекта; не копируйте сюда реальные обращения, ключи и закрытые документы. Фикстура не должна зависеть от сегодняшнего ответа модели: ожидаемое значение задаётся после предметной проверки.
Оставьте тестовые файлы рядом с кодом. При изменении бизнес-правила обновляйте его описание и expected осознанно, отдельным ревью. Простой зелёный запуск не является доказательством: сначала проверьте, что отрицательный кейс действительно делает job красным. Ошибка должна сообщать ID или номер строки, но не раскрывать чувствительный исходный текст.
Входная фикстура, которую можно скопировать
Сохраните строки в обозначенный ниже файл; комментарии с путями — пояснение и не входят в JSONL или CSV. Если пример показывает две версии ответа, прогоните их отдельно, как описано в шагах.
{"ticket_id":"T-104","priority":"urgent","confidence":0.82}
{"ticket_id":"T-105","priority":"unknown","confidence":"0.9"}
Пошаговая реализация
- Создайте каталог
scriptsи файлscripts/evaluate.py; при необходимости добавьте каталогdataи пример из предыдущего блока. Убедитесь, что кодировка текстовых файлов — UTF-8. - Скопируйте скрипт ниже и запустите команду
python scripts/evaluate.pyлокально. Для проекта с отдельным модулем замените демонстрационную функцию на импорт приложения. Сначала сохраните исходное проверяемое поведение. - Скопируйте валидатор и замените строки good/bad на безопасные фикстуры из вашего проекта. Перед передачей результата в CRM разбирайте текст ответа через
json.loads, затем вызывайте validate. В тестовом скрипте проверьте сразу положительный и отрицательный пример: недостаточно убедиться, что один хороший JSON проходит. Отдельно добавьте тестconfidence: trueи убедитесь, что он отвергнут. Если продукт допускает дополнительные поля, измените политикуset(value) != EXPECTEDосознанно и опишите её в контракте; не убирайте проверку случайно. - Добавьте workflow из следующего блока в
.github/workflows/ai-evaluation.yml. Он запускается для pull request и не передаёт секреты коду из PR. Если сетевой интеграционный тест всё же необходим, выведите его в отдельный доверенный workflow и ограничьте доступ к ключу. - Откройте успешный и намеренно неуспешный запуск. Убедитесь, что статус job соответствует коду завершения Python, а сообщение указывает на кейс, который нужно исправлять. После проверки верните корректные данные, не оставляйте искусственный сбой в ветке.
# scripts/evaluate.py
import json
EXPECTED = {"ticket_id", "priority", "confidence"}
def validate(value):
if not isinstance(value, dict) or set(value) != EXPECTED:
raise ValueError("wrong set of fields")
if not isinstance(value["ticket_id"], str) or not value["ticket_id"].strip():
raise ValueError("ticket_id must be nonempty string")
if value["priority"] not in {"urgent", "normal"}:
raise ValueError("invalid priority")
confidence = value["confidence"]
if isinstance(confidence, bool) or not isinstance(confidence, (int, float)):
raise ValueError("confidence must be number")
if not 0 <= confidence <= 1:
raise ValueError("confidence outside [0,1]")
good = json.loads('{"ticket_id":"T-104","priority":"urgent","confidence":0.82}')
validate(good)
bad = json.loads('{"ticket_id":"T-105","priority":"unknown","confidence":"0.9"}')
try:
validate(bad)
except ValueError as error:
print("Rejected invalid fixture:", error)
else:
raise SystemExit("Invalid fixture was accepted")
print("Contract verified")
Готовый 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
Пример входа и ожидаемого результата
Первый вход с T-104 принимается. Второй с priority: unknown и текстовой confidence отвергается с сообщением invalid priority, затем демонстрационный скрипт завершает проверку строкой Contract verified: эта строка означает, что плохая фикстура была именно отвергнута. Это наблюдаемое поведение демонстрационного кода, а не отчёт о запуске на внешней модели. В production проверяйте, что конкретный контракт подходит к бизнес-процессу, и сохраняйте вход/ожидание рядом с именем кейса.
Где этот тест может обмануть
Формально корректный JSON ещё не означает корректный бизнес-ответ. Число confidence не говорит, что модель действительно хорошо откалибрована: это только синтаксический и диапазонный контракт. При обработке реального ответа ограничьте размер входа на уровне API и не возвращайте сырой текст модели как доверенные данные в CRM. Если тест проверяет только формат, не представляйте его как оценку фактической правильности ответа. Если тест проверяет только список ID, не делайте вывода о полноте подтверждающих документов. Разделяйте сбой инфраструктуры, некорректный корпус и нарушение прикладного инварианта: у этих проблем разные действия.
Внимательно смотрите дифф при изменении ожидаемого результата. Ослабление утверждения ради зелёного статуса может скрыть ошибку. В публичных артефактах и summary показывайте только синтетический ID и минимально необходимые поля. При увеличении корпуса проверьте, что новый тестовый случай действительно добавляет неизвестную ранее ветку поведения, а не повторяет существующий текст с другой пунктуацией.
Чек-лист финальной проверки
- У входных данных есть понятный ID и проверенное ожидаемое поведение.
- Успешная фикстура проходит локально, испорченная падает с понятной причиной.
- Скрипт запускается по тому же пути в CI, что и в инструкции.
- Вход, журнал и репозиторий не содержат секретов или реальных персональных данных.
- Сравнивается актуальная версия кода; PR не подменяет эталон без ревью.
- Проверены границы именно этого теста: что он обнаруживает и что не проверяет.
Частые вопросы
Почему нужен отдельный json.loads?
Модель обычно отдаёт текст; сначала нужно разобрать JSON, затем проверять типы и значения полей.
Почему True не число?
В Python isinstance(True, int) возвращает True; явная проверка bool исключает эту ловушку.
Что делать с дополнительным полем?
Решите, допускает ли контракт расширение: в данном шаблоне лишние ключи запрещены, чтобы неожиданное изменение было заметно.
Можно ли считать confidence вероятностью?
Без отдельной калибровки и оценки на данных это лишь поле ответа; не делайте статистических обещаний по одному числу.
Как принять изменение после теста
Попросите второго участника проекта воспроизвести пример из статьи на своей ветке и оценить ожидаемый результат независимо от вывода скрипта. Особенно важно проверить, не изменили ли авторы 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; проверка симметричного базового значения.
Комментарии
Пока тихо. Скажите первое слово