Структурированный вывод LLM в 2026: JSON Schema в OpenAI, Anthropic и Gemini
Как получить гарантированно валидный JSON от LLM в 2026: Structured Outputs OpenAI, tool use в Claude, responseSchema в Gemini, Pydantic через Instructor и эвалы перед продакшном.
Структурированный вывод (Structured Output) в LLM, это режим работы языковой модели, при котором ответ принудительно соответствует заранее заданной JSON Schema. Такой режим устраняет необходимость в эвристическом парсинге свободного текста. В 2026 году все три крупных вендора (OpenAI, Anthropic и Google) поддерживают этот механизм, но реализуют его по-разному. OpenAI и Gemini принимают схему напрямую через response_format и responseSchema, а Claude использует tool use с input_schema. В этой статье я разбираю различия, ограничения и практические паттерны, которые накатал на боевых проектах за последние два года.
OpenAI Structured Outputs (strict mode) гарантируют 100% соответствие JSON Schema на моделях gpt-4o-2024-08-06 и новее, включая gpt-5. Это контрактное обязательство, а не статистика.
Anthropic Claude не имеет отдельного response_format: для структурированного вывода используется механизм tool use с input_schema и tool_choice.
Google Gemini поддерживает responseSchema в generationConfig для моделей 2.0/2.5. Синтаксис ближе к OpenAPI 3.0, чем к чистому JSON Schema.
Strict-режим OpenAI требует ограниченного подмножества JSON Schema: все поля required, additionalProperties: false, не более 100 свойств, максимум 5 уровней вложенности.
Библиотеки instructor и outlines предоставляют единый Pydantic-интерфейс поверх вендорских различий и снимают 80% boilerplate-кода.
Эвалы соответствия схеме, это обязательный шаг перед продакшном: даже strict-режим может вернуть пустой объект при срабатывании safety-refusal.
Что такое структурированный вывод в LLM?
Структурированный вывод, это режим генерации, в котором LLM возвращает данные, гарантированно валидные относительно заданной JSON Schema. До августа 2024 года получить надёжный JSON от модели означало писать промт вида «верни ответ в формате JSON и ничего больше», парсить результат, ретраить при ошибке и закладывать 2–5% брака на боевом трафике. С появлением OpenAI Structured Outputs и последующих аналогов у Anthropic и Google этот класс ошибок исчез как явление. Точнее, перенёсся из рантайма в стадию проектирования схемы.
На практике структурированный вывод нужен везде, где результат модели поступает на вход коду, а не человеку: извлечение сущностей из документов, генерация аргументов для tool calls, классификация с несколькими полями, ETL-пайплайны над неструктурированным текстом, агентские системы с маршрутизацией по типу ответа. Я уже разбирал близкий механизм в материале Function Calling и Tool Use в LLM-агентах: там JSON Schema описывает аргументы инструмента, здесь же схема описывает финальный ответ.
Технически вендоры реализуют гарантию по-разному. OpenAI использует constrained decoding: на каждом шаге сэмплирования логиты токенов, нарушающих схему, обнуляются. Это и есть источник «100% гарантии», ведь модель физически не может сгенерировать невалидный JSON. Open-source аналог, библиотека Outlines от dottxt-ai, которая делает то же самое поверх vLLM, llama.cpp и Hugging Face Transformers.
Чем JSON Mode отличается от Structured Outputs
JSON Mode и Structured Outputs, это два разных уровня гарантии, и путать их дорого. JSON Mode (параметр response_format={"type": "json_object"} у OpenAI, существует с ноября 2023 года) гарантирует только то, что вывод будет синтаксически валидным JSON. Структура, имена полей, типы остаются на совести промта. Модель может вернуть {"answer": "yes"} вместо ожидаемого {"label": "positive", "confidence": 0.92}, и формально это корректный ответ.
Structured Outputs (response_format={"type": "json_schema", ...}), это надмножество JSON Mode плюс жёсткое соответствие конкретной схеме. Модель не просто вернёт JSON, она вернёт именно описанный объект с правильными типами, обязательными полями и допустимыми значениями для enum'ов. По сути JSON Mode сейчас имеет смысл использовать только при работе с моделями старше gpt-4o-2024-08-06 или там, где схему сформулировать невозможно (что почти никогда не верно).
Structured Outputs в OpenAI: strict mode и schema
В OpenAI SDK 2026 года канонический способ запросить структурированный вывод, это вызов client.chat.completions.parse с Pydantic-моделью или явный response_format с JSON Schema. Я предпочитаю первый вариант, потому что схема и тип ответа описываются в одном месте и валидация делается автоматически. Честно говоря, это первое, что я меняю в любом старом коде, который вижу.
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Literal
client = OpenAI()
class Invoice(BaseModel):
invoice_number: str = Field(..., description="Номер счёта как напечатан в документе")
issued_on: str = Field(..., description="Дата выпуска в формате YYYY-MM-DD")
total_amount: float
currency: Literal["RUB", "USD", "EUR"]
line_items: list[dict]
resp = client.chat.completions.parse(
model="gpt-5",
messages=[
{"role": "system", "content": "Извлеки поля счёта из текста."},
{"role": "user", "content": ocr_text},
],
response_format=Invoice,
)
invoice: Invoice = resp.choices[0].message.parsed
if resp.choices[0].message.refusal:
raise RuntimeError(resp.choices[0].message.refusal)
Под капотом SDK конвертирует Pydantic-модель в JSON Schema, отправляет её с флагом strict: true и парсит ответ обратно в Pydantic-инстанс. Подробности есть в официальной документации OpenAI Structured Outputs.
Strict-mode накладывает ограничения на схему. Все свойства должны быть в required (если поле опциональное, используйте Optional[T] и явный null). На каждом объекте обязателен additionalProperties: false. Не поддерживаются minLength, maxLength, pattern, format, minimum, maximum, multipleOf. Валидацию диапазонов придётся делать после парсинга. Максимум 100 свойств в одной схеме, до 5 уровней вложенности, до 500 enum-значений суммарно.
Tool use в Anthropic Claude как способ получить структуру
Anthropic в 2026 году по-прежнему не вводит отдельный response_format. Официальная позиция в том, что tool use с input_schema покрывает все сценарии структурированного вывода. На практике это работает: вы объявляете «инструмент», единственная цель которого вернуть данные нужной формы, и форсируете его вызов через tool_choice.
Принципиальные отличия от OpenAI: схема описывается в JSON Schema без strict-ограничений (можно использовать pattern, format, minimum и подобное, но они подсказывают модели, не валидируют ответ), и в 2026 году Claude всё ещё не делает constrained decoding на стороне сервера, а полагается на сильное обучение моделей семейства Opus/Sonnet 4.x на tool use. На моих эвалах брак по схеме у claude-sonnet-4-6 составляет порядка 0.3% на нетривиальных схемах, у claude-opus-4-8 практически нулевой, но это не контрактная гарантия. Детали есть в документации Anthropic по tool use.
responseSchema в Google Gemini
Google Gemini 2.0/2.5 принимает responseSchema в generationConfig. Синтаксис заметно отличается от обоих конкурентов. Это OpenAPI 3.0-стиль: type: "OBJECT" в верхнем регистре, properties с описаниями, required как массив. JSON Schema-расширения вроде $ref, oneOf, anyOf поддержаны частично.
Альтернативный путь, это передать готовую Pydantic-модель напрямую в response_schema: SDK сконвертирует её сам. Это удобно, но Pydantic-фичи (Field-описания, валидаторы) транслируются не полностью, поэтому для сложных схем я предпочитаю явный types.Schema. Полная справка на странице Gemini API structured output.
Сравнение поддержки у трёх вендоров
Если выбирать вендора под задачу со структурированным выводом, важны не только гарантии формата, но и поддерживаемое подмножество JSON Schema, поведение при refusal и стоимость токенов схемы (она занимает место в контексте). Сводная таблица для моделей флагманской линейки на середину 2026 года.
Параметр
OpenAI (gpt-5, gpt-4o)
Anthropic (Claude 4.x)
Google (Gemini 2.5)
Механизм
response_format: json_schema
tool use + tool_choice
responseSchema
Constrained decoding
Да (strict)
Нет
Частично
Гарантия валидности
100% по контракту
≈99.7% эмпирически
≈99% эмпирически
Подмножество JSON Schema
Ограниченное (strict)
Почти полное
OpenAPI 3.0-стиль
Recursive schemas
Да (через $ref)
Да
Ограниченно
Refusals
Отдельное поле
Через stop_reason
Через safety_ratings
Latency overhead
5–8%
≈0% (это обычный tool use)
3–5%
Если коротко: OpenAI выигрывает по жёсткости гарантии, Anthropic по выразительности схемы (можно использовать pattern и format как подсказки модели), Gemini удобен в мультимодальных пайплайнах, где из изображения сразу извлекается типизированный объект.
Pydantic и Instructor: единый интерфейс
На реальных проектах вендорские различия быстро становятся раздражающим источником багов. Я не люблю менять пять файлов, чтобы переключить пайплайн с GPT на Claude для A/B-теста, поэтому держу слой абстракции на библиотеке Instructor. Она предоставляет единый Pydantic-интерфейс поверх OpenAI, Anthropic, Gemini, Mistral, Cohere и Ollama.
import instructor
from anthropic import Anthropic
from pydantic import BaseModel
class Invoice(BaseModel):
invoice_number: str
total_amount: float
currency: str
# Тот же код работает для OpenAI: instructor.from_openai(OpenAI())
client = instructor.from_anthropic(Anthropic())
invoice = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=[{"role": "user", "content": ocr_text}],
response_model=Invoice,
max_retries=2,
)
Instructor сам конвертирует Pydantic-модель в нужный формат, добавляет retry с feedback при ошибке валидации (модель видит, что именно сломалось, и исправляет), и нормализует refusals. Эту тему я также упоминал в гайде по созданию MCP-сервера и клиента на Python, где схемы инструментов описаны через Pydantic.
Для сценариев self-hosted-моделей (Llama 3.3, Qwen, DeepSeek-V3) разумная альтернатива, это Outlines, который делает настоящий constrained decoding поверх vLLM и Transformers. По сути, та же гарантия 100%, что и у OpenAI, но на ваших весах.
Эвалы схем перед продакшном
Даже с гарантированной валидностью JSON остаётся вопрос семантической корректности: правильное ли поле модель заполнила, верно ли извлекла дату, не перепутала ли валюту. Поэтому я никогда не выкатываю структурированный вывод в продакшн без отдельного эвал-набора. Минимальный пайплайн выглядит так.
Golden dataset на 100–300 размеченных вручную примеров (10–15 рабочих часов, окупается за неделю).
Schema compliance check, отдельная метрика, доля ответов, прошедших Pydantic-валидацию без retry. Должна быть 100% для strict-режима OpenAI; если меньше, у вас проблема со схемой, не с моделью.
Field-level F1 по каждому полю отдельно. Усреднение скрывает систематические косяки: модель может идеально извлекать номер счёта и проваливать валюту.
LLM-as-judge для свободных текстовых полей (описания, комментарии), где точное совпадение бессмысленно.
Конкретные фреймворки и стек я разбираю отдельно в материале оценка LLM в 2026: RAGAS, DeepEval, Promptfoo. Для structured output больше всего подходит Promptfoo с кастомными assertions и DeepEval с готовыми метриками точности по полям.
Подводные камни и ограничения
Несколько граблей, на которые я наступал лично и регулярно вижу у коллег.
Слишком сложные схемы ломают качество
Модель видит JSON Schema как часть системного промта. Схема на 80 полей с глубокой вложенностью съедает 2–3 тысячи токенов контекста и снижает качество извлечения по сравнению с двумя последовательными вызовами на простых схемах. Если у вас больше 40 полей, почти всегда лучше разбить на этапы.
Опциональные поля и null
В strict-режиме OpenAI все поля обязательны. «Опциональное поле» эмулируется через Optional[str] в Pydantic, что транслируется в {"type": ["string", "null"]}. Модель тогда честно вернёт null, но забывает об этом примерно в 1% случаев, так что добавляйте дефолты в Pydantic.
Enum'ы не масштабируются на тысячи значений
JSON Schema формально позволяет любое число enum-значений, но на практике с более чем сотней элементов модель начинает «срезать углы» и выдавать первое подходящее. Для классификаторов с сотнями классов работает лучше двухступенчатый подход: сначала свободный текст, потом отдельный embedding-классификатор.
Refusals можно проспать
Самый тонкий баг: модель срабатывает на safety-фильтре, возвращает refusal (OpenAI) или пустой tool_use (Anthropic), а ваш код продолжает работать с пустым объектом. Контракт API выполнен, ошибки нет, но в БД попадает мусор. Обязательно делайте assertion на наличие обязательных полей с непустыми значениями после парсинга.
Разные tokenizer-стоимости
JSON Schema, отправленная в каждом запросе, оплачивается как входные токены каждый раз. На длинных схемах это заметные деньги. У OpenAI и Anthropic это закрывается prompt caching: схема попадает в кэшируемый префикс и стоит в 10 раз дешевле на повторных вызовах. Включайте кэширование сразу.
Часто задаваемые вопросы
Поддерживает ли Claude structured outputs нативно?
Отдельного параметра response_format, как у OpenAI, у Claude нет. Anthropic рекомендует использовать механизм tool use: объявить инструмент с нужной input_schema и форсировать его вызов через tool_choice: {"type": "tool", "name": ...}. Эмпирически валидность схемы на Claude 4.x составляет 99.5–99.9%, но это не контрактная гарантия.
Чем JSON Mode отличается от Structured Outputs в OpenAI?
JSON Mode (type: "json_object") гарантирует только синтаксически валидный JSON, структура и поля целиком на промте. Structured Outputs (type: "json_schema") дополнительно гарантирует, что объект соответствует переданной JSON Schema: правильные типы, обязательные поля, допустимые значения enum. JSON Mode имеет смысл только для legacy-моделей.
Какие ограничения у strict-режима в OpenAI?
Все свойства должны быть в required, на каждом объекте обязательно additionalProperties: false, максимум 100 свойств, 5 уровней вложенности, 500 enum-значений в сумме. Не поддерживаются minLength, maxLength, pattern, format, minimum, maximum. Эти валидации придётся делать после парсинга в коде.
Как использовать Pydantic с OpenAI Structured Outputs?
Используйте метод client.chat.completions.parse и передайте Pydantic-класс в параметре response_format. SDK сконвертирует модель в JSON Schema, отправит запрос со strict-режимом и автоматически распарсит ответ в типизированный инстанс через resp.choices[0].message.parsed. Не забудьте проверить message.refusal на случай срабатывания safety-фильтров.
Какой вендор лучше для структурированного вывода в 2026?
Для жёсткой контрактной гарантии валидности это OpenAI (strict mode). Для выразительных схем со сложными ограничениями и валидаторами как подсказками модели это Anthropic Claude. Для мультимодальных задач, где из изображения сразу извлекается типизированный объект, это Google Gemini. На реальных пайплайнах разумно держать абстракцию через Instructor и переключать вендора под задачу.
Практическое руководство по prompt caching в OpenAI, Anthropic и Gemini на 2026 год: цены, TTL, cache_control breakpoints, совместимость с tool use и мониторинг hit rate с примерами на Python.