Урок курса
Structured Output: JSON и Pydantic для LLM
LLM API с нуля: первый AI-сервис на PythonСвободный текст неудобен для кода: формат может меняться, поле исчезнуть, а число прийти строкой. Structured output задаёт схему результата и позволяет получить объект с предсказуемыми полями. В Python схему удобно описывать Pydantic-моделью.
from typing import Literal
from pydantic import BaseModel, Field
class Ticket(BaseModel):
category: Literal["billing", "technical", "other"]
priority: int = Field(ge=1, le=5)
summary: str = Field(min_length=1, max_length=300)
Названия, типы, ограничения и описания полей становятся контрактом между моделью и приложением. Провайдер или orchestration library может поддерживать schema-native output либо эмулировать его через tool calling.
JSON mode не равен schema enforcement
Режим «валидный JSON» гарантирует синтаксис JSON, но не обязательно нужные поля и типы. Schema-native structured output ограничивает форму ответа сильнее. После получения всё равно полезно прогнать runtime-валидацию Pydantic: она защищает внутренний код и нормализует обработку ошибок.
Схема должна быть небольшой и однозначной. Используйте enum или Literal вместо произвольной строки, задавайте допустимые диапазоны и отличайте nullable поле от необязательного. Не просите модель вычислять поля, которые надёжнее получить программно.
Если провайдер не поддерживает часть JSON Schema, адаптер может использовать tool strategy. Поведение и поддерживаемые ограничения сверяйте для конкретной модели и версии SDK.
Проверка JSON-ответа без платного API
Сначала удобно проверить обработку результата отдельно от вызова модели. Для этого не нужен API-ключ: возьмём две заранее подготовленные строки и пропустим их через Pydantic 2. Установите библиотеку в учебное окружение: python -m pip install "pydantic>=2,<3".
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, ValidationError
class Ticket(BaseModel):
model_config = ConfigDict(strict=True, extra="forbid")
category: Literal["billing", "technical", "other"]
priority: int = Field(ge=1, le=5)
summary: str = Field(min_length=1, max_length=300)
responses = [
'{"category":"technical","priority":2,"summary":"Не работает вход"}',
'{"category":"urgent","priority":"2","summary":"Ошибка","refund":true}',
]
for raw in responses:
try:
ticket = Ticket.model_validate_json(raw)
print("OK", ticket.category, ticket.priority)
except ValidationError as error:
print("INVALID", [item["loc"] for item in error.errors()])
Первый объект проходит проверку: OK technical 2. У второго неправильная категория, строка вместо числового приоритета и лишнее поле refund. Он будет отклонён; программа покажет поля ошибок вместо того, чтобы передавать такой объект дальше.
strict=True для числового приоритета не позволяет молча превратить строку "2" в число. extra="forbid" запрещает лишние поля. Если вашей системе нужна более мягкая нормализация, это должно быть явным решением, а не случайным поведением на границе с моделью.
Где заканчивается проверка схемы
Корректная категория и типы ещё не доказывают, что модель верно поняла обращение. Например, JSON может идеально соответствовать схеме, но ошибочно отнести оплату к технической проблеме. Поэтому разделяйте проверки: синтаксис JSON, допустимость структуры, фактическая правильность и разрешение на действие.
Поле refund в примере специально не входит в схему классификатора. Даже если отдельная модель выдаёт рекомендацию возврата, сам возврат должен проверяться бизнес-правилами приложения. Схема JSON не является правом выполнить операцию.
Как переносить этот пример на живой ответ LLM
Получите текстовый или уже разобранный результат способом, который поддерживает выбранный SDK. Если SDK возвращает строку JSON, проверяйте её через model_validate_json; если словарь — через model_validate. Отдельно обработайте отказ модели, обрыв генерации и сетевую ошибку: они не являются обычным объектом Ticket.
Сохраняйте небольшие тестовые примеры: правильный объект, лишнее поле, неверная категория, отсутствующий приоритет и выход за диапазон. Так можно проверять собственный код без оплаты генерации. Затем добавьте отдельный тест с реальным провайдером: локальная проверка не подтверждает поддержку схемы конкретной моделью.
Следующий шаг — ошибки API, timeout и ограниченный retry. Справка: строгий режим Pydantic и конфигурация модели.
Отказ, ошибка и небезопасное действие
Structured output уменьшает форматные ошибки, но не гарантирует фактическую истинность. Модель может вернуть идеально валидный объект с вымышленными значениями. Для критичных полей нужны источники, business validation и иногда подтверждение человеком.
Обрабатывайте отдельно: отказ модели отвечать, сетевую ошибку, отсутствие результата, schema validation error и нарушение бизнес-правил. Не исправляйте неизвестные данные тихими defaults. Ограниченный retry допустим для случайной форматной ошибки, но не должен бесконечно повторять один и тот же запрос.
Логируйте версию схемы, модель, request id и класс ошибки без секретов и персональных данных. При изменении схемы добавляйте контрактные тесты и совместимость с уже сохранёнными объектами.
Задача 1
Чем schema-native structured output отличается от обычного JSON mode?
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
