Python: от данных до своей сводки Урок 50 из 56
Структурированный ответ: JSON от модели и его проверка
Сорок девятый урок курса по Python. Просим модель вернуть не свободный текст, а JSON по заданной схеме, разбираем его стандартным модулем `json` и проверяем библиотекой `jsonschema`. Три отдельные границы — синтаксис, структура и факты — не дают красивой строке незаметно попасть в отчёт.
Зачем это нужно
В уроке 48 мы сохранили точный запрос. Но даже повторяемый запрос может вернуть ответ, который программа не умеет использовать: модель назвала поле иначе, записала число строкой или добавила пояснение вокруг JSON.
Фраза «верни JSON» — пожелание. JSON Schema — договор, который умеет проверить код. Он задаёт обязательные поля, их типы, допустимые значения и запрет лишних полей.
У проверки три ступени:
json.loadsотвечает: это вообще JSON?jsonschemaотвечает: у него ожидаемая форма?- правила проекта отвечают: числа действительно взяты из наших данных?
Сегодня строим первые две ступени. Третью добавим в следующем уроке.
Сразу целиком
Установите закреплённую версию:
python -m pip install jsonschema==4.26.0
Файл structured.py работает с сохранённым ответом, поэтому Ollama для запуска не нужна.
"""Урок 49: разобрать и проверить структурированный ответ."""
import json
from jsonschema import Draft202012Validator
OUTPUT_SCHEMA = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"country": {"type": "string", "minLength": 1},
"year": {"type": "integer", "minimum": 2000, "maximum": 2100},
"value": {"type": "number"},
"unit": {"type": "string", "enum": ["percent"]},
},
"required": ["country", "year", "value", "unit"],
"additionalProperties": False,
}
def parse_and_validate(raw):
"""Вернуть только JSON, соответствующий нашему договору."""
payload = json.loads(raw)
Draft202012Validator(OUTPUT_SCHEMA).validate(payload)
return payload
raw = '{"country":"Казахстан","year":2025,"value":11.4,"unit":"percent"}'
result = parse_and_validate(raw)
print("страна:", result["country"])
print("год — целое:", isinstance(result["year"], int))
print("значение — число:", isinstance(result["value"], (int, float)))
print("единица:", result["unit"])
print("лишних полей нет:", set(result) == {"country", "year", "value", "unit"})
Вывод:
страна: Казахстан
год — целое: True
значение — число: True
единица: percent
лишних полей нет: True
Сначала разбор, потом доверие
json.loads превращает JSON-строку в значения Python. Неверная запятая или текст до фигурной скобки вызывает JSONDecodeError. Не вырезайте фигурные скобки регулярным выражением: так можно случайно принять обломок ответа за целый документ.
Лучше потребовать от API чистый структурированный результат. В запросе Ollama схему можно передать полем format:
request["format"] = OUTPUT_SCHEMA
Это заметно снижает число неправильных ответов, но не отменяет проверку на вашей стороне. Модель и сеть находятся за границей доверия.
Что обещает схема
type: object требует объект. required перечисляет поля, без которых ответ неполон. additionalProperties: False запрещает незаявленные поля — опечатка county не спрячется рядом с правильными.
Типы важны: JSON-число 11.4 и JSON-строка "11.4" — разные значения. Схема не преобразует одно в другое и не должна угадывать намерение модели.
enum ограничивает единицу словом percent. Так программа не смешает %, доля и процент. Диапазон года ловит очевидный мусор, но не доказывает, что 2025 год есть в исходной таблице.
Перед использованием схемы в приложении полезно один раз проверить и её саму:
Draft202012Validator.check_schema(OUTPUT_SCHEMA)
Ошибка — часть результата
На границе приложения перехватывайте только ожидаемые ошибки и не продолжайте конвейер с пустым словарём:
from json import JSONDecodeError
from jsonschema import ValidationError
try:
result = parse_and_validate(raw)
except JSONDecodeError:
print("Ответ не является JSON")
except ValidationError:
print("JSON не соответствует схеме")
else:
save_for_review(result)
Сообщение об ошибке можно записать в журнал без полного пользовательского текста. Сам неправильный ответ сохраните в закрытом месте для разбора, если в нём нет данных, которые хранить нельзя.
Чего схема не знает
Схема подтвердит, что value — число. Она не знает, было ли в таблице 11.4, не перепутала ли модель страну и не выдумала ли причину. Правильная форма не означает правильный факт.
Не используйте результат модели в SQL, HTML или команде оболочки только потому, что он прошёл схему. Экранирование, параметризованные запросы и предметные ограничения остаются обязательными.
Карта урока
Восстановите опорный сигнал: сырой ответ → json.loads → JSON Schema → данные для проверки фактов.
Скажите своими словами
- Почему просьба «верни JSON» не заменяет проверку?
- Чем
JSONDecodeErrorотличается отValidationError? - Зачем одновременно нужны
requiredиadditionalProperties: False? - Почему прошедшее схему число ещё нельзя публиковать?
Разминка
1. Предскажите. Пройдёт ли строка проверку типа number?
import json
value = json.loads('{"value": "11.4"}')["value"]
print(isinstance(value, (int, float)))
2. Заполните пропуск. Запретите поля, которых нет в договоре.
schema = {"type": "object", ...: False}
3. Почините. Сейчас ошибка структуры превращается в якобы пригодный ответ.
try:
result = parse_and_validate(raw)
except Exception:
result = {}
save_for_review(result)
Задание
Обязательное. Напишите check_many(raw_items). Для каждой строки отдельно посчитайте один из трёх исходов: valid, invalid_json, invalid_schema. Используйте только JSONDecodeError и ValidationError; неправильные ответы не должны попадать в список принятых.
принято: 1
не JSON: 1
не по схеме: 2
годы: [2025]
На своих данных. Добавьте схему результата для одной таблицы сводки. Назовите единицу через enum, запретите лишние поля и придумайте четыре ответа, которые проверяют каждую границу.
По желанию. Передайте эту же схему в format запроса Ollama. Сравните десять запусков со схемой и без неё, заранее определив метрику: доля ответов, прошедших локальную проверку.
Куда это встанет в проекте
Теперь ответ модели может попасть только в список структурно проверенных результатов. В уроке 50 дадим модели числа нашей сводки и сверим каждое возвращённое число с источником. Только после этой проверки появится текст для отчёта.
Ответы
Показать ответы
- Модель может добавить пояснение, пропустить поле или вернуть число строкой; пожелание не является проверкой кода.
- Первая означает неверный синтаксис JSON, вторая — правильный JSON неправильной формы.
- Первое ловит пропуск, второе — опечатку или незаявленное поле.
- Схема знает тип и диапазон, но не источник и истинность числа.
False
- Ключ —
"additionalProperties".
schema = {"type": "object", "additionalProperties": False}
print(schema["additionalProperties"])
False
- Перехватите ожидаемую ошибку, запишите отказ и не вызывайте следующий этап.
from jsonschema import ValidationError, validate
raw = {}
try:
validate(raw, {"type": "object", "required": ["year"]})
except ValidationError:
print("отклонено")
else:
print("принято")
отклонено
Источники
- JSON Schema: начало работы — объект, обязательные и дополнительные свойства.
- Документация
jsonschema— валидаторы и проверка схемы. - Ollama structured outputs — передача JSON Schema в поле
format. - Python
json— разбор JSON иJSONDecodeError.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.