Claude Structured Outputs: надёжный JSON по схеме и ошибки 400
Схему кладут в output_config.format, JSON приходит в текстовом блоке. minimum и рекурсия дают 400, а minLength шлюз принял, но не соблюдал.
Содержание

У Claude есть structured outputs, и по состоянию на 30 октября 2026 года это общедоступная функция Claude API, beta-заголовок для неё больше не нужен. Чтобы получить JSON, который код разберёт без правок, передайте JSON Schema в поле запроса output_config.format с "type": "json_schema". Готовый JSON придёт в текстовом блоке ответа: обычно это content[0].text, а если включён thinking, то первый блок с "type": "text". Если по схеме должны приходить аргументы вызова инструмента, а не сам ответ, ставьте strict: true в описании инструмента; обе функции можно включить в одном запросе.
Гарантия касается синтаксиса, обязательных полей и типов, но не всего остального. Перед разбором проверяйте stop_reason: при refusal и max_tokens JSON может не совпасть со схемой или оборваться на середине. Ограничения вроде minimum и minLength декодер не соблюдает, поэтому проверять их придётся в своём коде, а SDK делает это за вас только в хелперах messages.parse(). Старый верхнеуровневый параметр output_format из гайдов бета-периода устарел: по документации Anthropic без beta-заголовка он даёт 400.
Через прокси та же ошибка бывает тихой. 30 октября 2026 года запрос со старым output_format через LaoZhang API (Anthropic-совместимый /v1/messages) вернул 200 и обычный текст вместо JSON, а число входных токенов осталось таким же, как без схемы. Поэтому на любом шлюзе сначала убедитесь, что пришёл именно JSON и что usage.input_tokens вырос по сравнению с запросом без схемы. Прямой api.anthropic.com в этих тестах не участвовал, и результаты «через шлюз» ниже относятся только к этому маршруту.
Примечание: laozhang.ai — наш собственный сервис; его цены — наши тарифы, а не независимое сравнение.
Запрос с output_config.format: cURL и где лежит JSON
Минимальный рабочий запрос — обычный вызов Messages API с одним дополнительным полем output_config.format. Внутри лежит JSON Schema, и у каждого объекта в ней должно стоять "additionalProperties": false. Пример ниже взят из документации Anthropic: письмо клиента превращается в четыре поля.
curl https://api.anthropic.com/v1/messages \
-H "content-type: application/json" \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "claude-opus-5-5",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": "Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm."
}],
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan_interest": {"type": "string"},
"demo_requested": {"type": "boolean"}
},
"required": ["name", "email", "plan_interest", "demo_requested"],
"additionalProperties": false
}
}
}
}'Ответ — обычное сообщение, но в content[0].text лежит строка с JSON, которую можно сразу передать в json.loads() или JSON.parse(). 30 октября 2026 года тот же запрос с моделью claude-haiku-5-5 через LaoZhang API (адрес https://api.laozhang.ai/v1/messages, остальное без изменений) вернул за 3,61 с stop_reason: "end_turn" и текст {"demo_requested": true, "email": "john@example.com", "name": "John Smith", "plan_interest": "Enterprise"}. Входных токенов было 333. Тот же промпт без схемы дал markdown-список вместо JSON и 64 входных токена: разница в 269 токенов — это схема и служебный промпт, который Claude получает вместе с ней.
Примечание: laozhang.ai — наш собственный сервис; его цены — наши тарифы, а не независимое сравнение.
Функция работает на моделях от поколения 4.5 и новее: Opus 4.5–5.5, Sonnet 4.5–5.5, Haiku 4.5 и 5.5, Fable 5 и 5.1, Mythos. Claude 3.x и Opus 4.1 в списке поддерживаемых моделей в документации Anthropic по structured outputs нет.
Python и Pydantic: messages.parse() и parsed_output
В Python удобнее всего описать ответ моделью Pydantic и вызвать client.messages.parse(). Хелпер сам превращает модель в схему, отправляет её в output_config.format, проверяет ответ и возвращает готовый объект в response.parsed_output. Аргумент здесь называется output_format, но это имя параметра хелпера, а не устаревшее поле запроса: в API SDK отправит именно output_config.format.
from typing import Literal, Optional
from anthropic import Anthropic
from pydantic import BaseModel, Field
class Lead(BaseModel):
name: str = Field(min_length=2)
email: str
plan: Literal["Starter", "Team", "Enterprise"]
seats: int = Field(ge=1, le=500)
phone: Optional[str] = None
client = Anthropic() # для шлюза: Anthropic(base_url="https://api.laozhang.ai", api_key="...")
response = client.messages.parse(
model="claude-haiku-5-5",
max_tokens=512,
messages=[{"role": "user", "content": "John Smith (john@example.com) wants the Enterprise plan for 40 seats."}],
output_format=Lead,
)
print(response.parsed_output)В модели есть min_length, ge и le, которых API не принимает, и SDK обходит это преобразованием схемы. Его результат можно посмотреть без запроса через transform_schema(Lead). В anthropic 2.3.0 с pydantic 2.14 поле name ушло в API как обычная строка с описанием {minLength: 2}, seats — как integer с описанием {maximum: 500, minimum: 1}, необязательный phone превратился в anyOf из строки и null, а ко всему объекту добавилось additionalProperties: false. Модель видит ограничения только как подсказку в описании, а соблюдение проверяет уже Pydantic после ответа.
Это видно на проверке 30 октября 2026 года через LaoZhang API. Обычный запрос вернул parsed_output с seats=40 и phone=None при 489 входных токенах. Во втором запросе в тексте честно было указано «0 seats»: API вернул "seats": 0, и SDK выбросил pydantic.ValidationError с сообщением Input should be greater than or equal to 1. Значит, messages.parse() нужно оборачивать в обработку ValidationError: схема не мешает модели передать значение, нарушающее ваше правило.
Примечание: laozhang.ai — наш собственный сервис; его цены — наши тарифы, а не независимое сравнение.
В TypeScript то же самое делают через Zod: схема оборачивается в zodOutputFormat() и передаётся в output_config.format вызова client.messages.parse(), результат приходит в parsed_output.
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
const Contact = z.object({ name: z.string(), email: z.string(), plan_interest: z.string(), demo_requested: z.boolean() });
const response = await new Anthropic().messages.parse({
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Extract the key information from this email: ..." }],
output_config: { format: zodOutputFormat(Contact) },
});
console.log(response.parsed_output);Если вместо Zod используете jsonSchemaOutputFormat(), учтите, что он только преобразует схему, а ответ не проверяет. Java SDK поступает наоборот: неподдерживаемые ограничения он не переносит в описание, а отклоняет схему ещё на вашей стороне, до отправки запроса.
JSON outputs или strict: true в инструменте: что выбрать
Выбор зависит от того, что должно соответствовать схеме. Если нужен сам ответ модели в виде данных (извлечение полей, классификация, итоговый отчёт агента), это JSON outputs через output_config.format. Если модель вызывает вашу функцию и её аргументы должны точно совпасть с типами, это strict: true в определении инструмента. В агенте, который и вызывает инструменты, и отдаёт итог в виде JSON, включайте оба: так строит пример и документация Anthropic.
| JSON outputs | Strict tool use | |
|---|---|---|
| Где задаётся | output_config.format в запросе | "strict": true рядом с name и input_schema инструмента |
| Что ограничивает | Текст ответа Claude | Имя инструмента и его input |
| Где результат | Текстовый блок, stop_reason: "end_turn" | Блок tool_use, stop_reason: "tool_use" |
| Типичная задача | Извлечь данные, классифицировать, вернуть отчёт | Вызвать функцию с правильными типами (2, а не "two") |
| Ограничения схемы | Общие: тот же поднабор JSON Schema | Те же, плюс не больше 20 strict-инструментов в запросе |
Без strict-режима модель может передать в целочисленный параметр строку "2" или "two" или пропустить обязательное поле; со strict: true аргументы проходят через ту же грамматику, что и JSON outputs. 30 октября 2026 года через LaoZhang API инструмент create_demo_booking с перечислениями для тарифа и дня недели, целым hour_24 и форматом email при принудительном tool_choice вернул stop_reason: "tool_use" и input {"email": "john@example.com", "hour_24": 14, "plan": "Enterprise", "weekday": "Tue"}. Фраза «next Tuesday at 2pm» превратилась в "Tue" и 14, то есть в значения из схемы, а не в свободный текст.
Примечание: laozhang.ai — наш собственный сервис; его цены — наши тарифы, а не независимое сравнение.
{
"name": "create_demo_booking",
"description": "Book a product demo for a lead.",
"strict": true,
"input_schema": {
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"plan": {"type": "string", "enum": ["Starter", "Team", "Enterprise"]},
"weekday": {"type": "string", "enum": ["Mon", "Tue", "Wed", "Thu", "Fri"]},
"hour_24": {"type": "integer"}
},
"required": ["email", "plan", "weekday", "hour_24"],
"additionalProperties": false
}
}Помечать строгими все инструменты подряд не стоит. Лимиты сложности считаются по всем strict-схемам запроса вместе, поэтому strict: true разумно оставлять там, где неверный тип действительно ломает ваш код. Подробности — в документации Anthropic по strict tool use.
Какие ключевые слова JSON Schema дают 400, а какие не соблюдаются
Structured outputs понимают только часть JSON Schema, и за её пределами возможны три исхода: ошибка 400, молчаливое принятие без соблюдения и нормальная работа. Документация Anthropic обещает 400 для любого неподдерживаемого ключевого слова, но через шлюз 30 октября 2026 года это подтвердилось не для всех. Ниже — что сказано в документации и что вернул LaoZhang API на claude-haiku-5-5; тексты ошибок приведены без перевода, как их вернул шлюз.
Примечание: laozhang.ai — наш собственный сервис; его цены — наши тарифы, а не независимое сравнение.
| Ключевое слово | По документации Anthropic | Через LaoZhang API, 30.10.2026 | Что делать |
|---|---|---|---|
minimum, maximum, multipleOf | Не поддерживаются, 400 | minimum: 1 → 400: output_config.format.schema: For 'integer' type, property 'minimum' is not supported | Убрать из схемы (SDK делает это сам), проверять диапазон в коде |
minLength, maxLength | Не поддерживаются, 400 | Приняты (200); при minLength: 12 и maxLength: 12 на просьбу вернуть код ABC пришло {"code": "ABC"} | Проверять длину в коде: ограничение не работает, хотя ошибки нет |
Рекурсивный $ref | Не поддерживается | 400: Invalid schema: Circular reference detected in schema definitions: node -> node. Self-referencing or mutually-referencing definitions are not supported. | Развернуть дерево до фиксированной глубины |
additionalProperties | Обязательно false у каждого объекта | Без него 200, ответ совпал со схемой | Всё равно ставить false явно: на другом маршруте может быть 400 |
enum | Только строки, числа, булевы и null; регистр не гарантирован | 5 запросов из 5 вернули "Conversation topic 3" точно как в схеме | Сравнивать значения без учёта регистра |
pattern | Простые regex; без lookahead/lookbehind, \b, обратных ссылок и больших {n,m} | — | Сложные проверки переносить в код |
minItems | Только 0 или 1 | — | Проверять число элементов в коде |
Остальное поддерживается: все базовые типы, const, anyOf, allOf (кроме allOf с $ref), локальные $ref и $defs, default, а из форматов строк — date-time, time, date, duration, email, hostname, uri, ipv4, ipv6 и uuid. Вложенные объекты тоже допустимы, так что «только плоские схемы» — это не ограничение API; запрещена рекурсия, а не вложенность.
Отдельно действуют лимиты сложности на весь запрос: не больше 20 strict-инструментов, 24 необязательных параметров и 16 параметров с объединением типов (anyOf или "type": ["string", "null"]), считая все строгие схемы вместе. Даже если они соблюдены, слишком большая грамматика даёт 400 Schema is too complex for compilation, а компиляция прерывается через 180 секунд. Anthropic советует по порядку: оставить strict только для важных инструментов, сделать параметры обязательными, упростить вложенность и разбить запрос на несколько.
Вместо JSON пришёл обычный текст: схема не дошла до модели
Если ответ пришёл обычным текстом, почти всегда схема не дошла до модели, и то, как это проявится, зависит от маршрута. Прямой API Anthropic по документации отвечает 400 на устаревший output_format без заголовка anthropic-beta: structured-outputs-2025-11-13. Через LaoZhang API 30 октября 2026 года тот же запрос и без заголовка, и с ним вернул 200 и markdown-список, причём входных токенов было 64, ровно как у запроса без схемы. Шлюз отбросил неизвестное поле, ничего не сообщив.
Примечание: laozhang.ai — наш собственный сервис; его цены — наши тарифы, а не независимое сравнение.

Обратная ситуация — громкая ошибка там, где запрос правильный. Каналы, которые проверяют тело запроса по правилам Bedrock, отвечают 400 ValidationException с текстом Extra inputs are not permitted на поля, которых они не принимают; встречается, например, output_config effort extra inputs are not permitted. Об этом прямо пишет справка LaoZhang API об ошибке 400 ValidationException, а разбор других текстов 400 есть в статье «Claude в AWS Bedrock отвечает 400: причина по тексту ошибки».
Когда что-то из этого случилось, пройдите по порядку:
- Убедитесь, что схема лежит в
output_config.format, а не в верхнеуровневомoutput_formatиз старых примеров и не вresponse_format: это имя поля из API OpenAI, в Messages API Anthropic его нет. - Сравните
usage.input_tokensс тем же запросом без схемы. Если число не изменилось, схема до модели не дошла, даже если статус 200. - Посмотрите, начинается ли
content[0].textс{и проходит лиjson.loads(). Markdown или текст с пояснениями означает, что формат не применился. - Если запрос идёт через обёртку (LiteLLM, фреймворк агентов, собственный прокси), проверьте в логах исходящий запрос: передаёт ли она
output_configдальше без изменений. - При 400
Extra inputs are not permittedуберите из запроса поля, которые канал не принимает, или переключитесь на маршрут, который передаётoutput_configполностью.
Где «всегда валидный JSON» не гарантирован: отказ, max_tokens, ваша проверка
Грамматика гарантирует форму ответа, только если генерация закончилась нормально, то есть с stop_reason: "end_turn" для JSON outputs или "tool_use" для strict-инструментов. Во всех остальных случаях JSON может не совпасть со схемой, а ограничения, которые API не поддерживает, вы проверяете сами. Код, который сразу делает json.loads() и кладёт результат в базу, рано или поздно на этом сломается.
- Проверяйте
stop_reasonдо разбора. ПриrefusalClaude отказался отвечать по соображениям безопасности: статус 200, токены списаны, а текст отказа важнее схемы. Не делайте поле вида «пошаговое рассуждение» — оно может вызвать отказreasoning_extraction; просите короткое объяснение. - Ловите
max_tokens. 30 октября 2026 года через LaoZhang API запрос 15 идей приmax_tokens: 30вернул 200 и текст{"ideas": ["REST vs GraphQL vs gRPC: How to Choose the, который не разбирается. Повторяйте с бо́льшим лимитом, а не пытайтесь «дочинить» строку. - Валидируйте значения после ответа: длины, диапазоны, регулярные выражения, число элементов массива. Пример с
"seats": 0в разделе про Python показывает, что модель вернёт значение, нарушающее ваше правило, если так написано во входных данных. - Не полагайтесь на порядок ключей. Документация обещает порядок схемы с обязательными полями впереди, но во всех ответах через LaoZhang API 30 октября 2026 года ключи шли по алфавиту. Обращайтесь к полям по имени.
- Считайте необязательное поле «отсутствующим или
null». В том же тесте полеphoneс типомstringне было ни обязательным, ни nullable, но пришло как"phone": nullвместо того, чтобы пропасть из ответа. - Сравнивайте
enumбез учёта регистра и не заводите значения, которые отличаются только регистром. Anthropic предупреждает, что вместо"Conversation topic 3"может прийти"Conversation Topic 3"без всякой ошибки.
Примечание: laozhang.ai — наш собственный сервис; его цены — наши тарифы, а не независимое сравнение.
Ещё две несовместимости дают 400 сразу. Citations вместе с output_config.format API не принимает. Предзаполнение ответа { в последнем сообщении assistant тоже не работает: на claude-haiku-5-5 через шлюз пришло This model does not support assistant message prefill. The conversation must end with a user message. Как заменить prefill на новых моделях, разобрано в статье «Ошибка 400 prefill в Claude: на каких моделях и чем заменить».

Все проверки помещаются в одну функцию-страховку. Она работает с сырым messages.create(), поэтому годится и там, где messages.parse() недоступен:
from anthropic import Anthropic, transform_schema
from pydantic import ValidationError
client = Anthropic()
SCHEMA = transform_schema(Lead) # модель Lead из раздела про Python
class NotJSON(Exception):
pass
def extract_lead(text: str) -> Lead | None:
resp = client.messages.create(
model="claude-haiku-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": text}],
output_config={"format": {"type": "json_schema", "schema": SCHEMA}},
)
if resp.stop_reason == "refusal":
return None # отказ: записать в лог, не разбирать
if resp.stop_reason == "max_tokens":
raise NotJSON("ответ обрезан: увеличьте max_tokens и повторите")
raw = next(b.text for b in resp.content if b.type == "text")
if not raw.lstrip().startswith("{"):
raise NotJSON("схема не применилась: проверьте output_config на маршруте")
try:
return Lead.model_validate_json(raw) # длины, диапазоны, значения enum
except ValidationError as err:
raise NotJSON(f"JSON не прошёл проверку: {err}") from errЕсли в модели есть Literal, расхождение в регистре тоже даст здесь ValidationError. Когда "Conversation Topic 3" и "Conversation topic 3" для вас одно и то же, приводите такие поля к нужному написанию валидатором с mode="before" до основной проверки.
Сколько стоит формат: +269 входных токенов на запрос
Structured outputs не тарифицируются отдельно, но Claude получает дополнительный системный промпт с описанием формата, и эти токены оплачиваются как обычный вход. В замере через шлюз из раздела про cURL схема из четырёх полей добавила 269 входных токенов на запрос. Для расчёта ниже эти 269 токенов умножены на прайс Anthropic за входные токены: 269 × 1 млн запросов = 269 млн токенов, умноженные на цену за 1 млн.
| Модель | Цена входа за 1 млн токенов | Надбавка на 1 млн запросов |
|---|---|---|
| Claude Haiku 5.5 (промпт до 100 тыс. токенов) | $0,10 | $26,90 |
| Claude Sonnet 5.5 | $2 | $538 |
| Claude Opus 5.5 | $4 | $1 076 |
Цены взяты со страницы цен Anthropic по состоянию на 30 октября 2026 года; в Batch API они вдвое ниже. Расчёт верен для одной схемы и одного промпта: схема побольше добавит больше токенов. Выход в том же примере, наоборот, сократился с 91 токена прозы до 46 токенов JSON. На Sonnet 5.5 это экономит около $0,00045 на вызов при надбавке на входе около $0,000538, так что итог зависит от того, насколько многословен ваш ответ без схемы. Сравнение цен Claude с другими API — в статье «Claude API или OpenAI API: где дешевле при вашей нагрузке».
Две вещи влияют на счёт косвенно. Смена output_config.format сбрасывает кэш промпта для этой переписки, так что схему лучше не менять от запроса к запросу. Первый запрос с новой схемой, по документации, идёт медленнее из-за компиляции грамматики, которая потом кэшируется на 24 часа с последнего использования. Через шлюз эта задержка не выделилась: новая схема и два повтора ответили за 4,23, 3,33 и 4,28 с, и разброс сети оказался больше.
Частые вопросы о Claude structured outputs
Работают ли structured outputs в Amazon Bedrock и Google Cloud?
Да. По документации Anthropic на 30 октября 2026 года функция общедоступна в Claude API, Claude Platform on AWS, Amazon Bedrock (включая InvokeModel), Claude на Google Cloud (Vertex AI) и Microsoft Foundry. Недоступна она только в Managed Agents. Если облако или шлюз отвечает 400 на output_config, смотрите раздел про текст вместо JSON выше.
Можно ли включить structured outputs со streaming и Batch API?
Да, по документации Anthropic оба режима совместимы: ответ стримится как обычно, а в Batch API запросы со схемой стоят вдвое дешевле. Подсчёт токенов через token counting тоже работает и компиляцию схемы не запускает. Учтите, что при стриминге JSON становится валидным только в конце, поэтому разбирайте его после завершения потока.
Совместимы ли structured outputs с extended thinking?
Да. Грамматика ограничивает только итоговый ответ, а не блоки thinking, вызовы инструментов и их результаты. 30 октября 2026 года через LaoZhang API claude-sonnet-5-5 с thinking: {"type": "adaptive"} и output_config: {"effort": "low", "format": ...} вернул один текстовый блок с валидным JSON; блока thinking при низком effort не было. Поэтому JSON ищите по типу блока, а не по индексу.
Есть ли у Claude JSON mode, как у OpenAI?
Отдельного JSON mode у Claude нет: его роль выполняет output_config.format со схемой, а режим «просто валидный JSON без схемы» не предусмотрен. Как то же самое устроено у OpenAI, разобрано в статье «Structured Outputs или Function Calling в OpenAI: что выбрать».
Structured outputs в Claude Code и Agent SDK — это то же самое?
Нет, это другой механизм: схема передаётся в query() через outputFormat (TypeScript) или output_format (Python), SDK сам проверяет итог агента, при несовпадении просит переделать и возвращает данные в structured_output. Подробности — в документации Claude Agent SDK по structured outputs.
Можно ли класть персональные данные в JSON-схему при ZDR или HIPAA?
В саму схему — нет. По документации Anthropic промпты и ответы со structured outputs не хранятся при Zero Data Retention, но схема кэшируется до 24 часов с последнего использования и не получает той же защиты. Поэтому имена свойств, значения enum и const и регулярные выражения в pattern не должны содержать PHI, то есть защищённые медицинские данные пациентов; им место только в тексте сообщений. По той же причине не стоит класть в схему и другие персональные данные.
Источники4
Внешние страницы, на которые ссылается это руководство, в порядке упоминания. Последнее обновление: 30 окт. 2026 г..
Источники4
Внешние страницы, на которые ссылается это руководство, в порядке упоминания. Последнее обновление: 30 окт. 2026 г..
- 1.документации Anthropic по structured outputsplatform.claude.com/docs/en/build-with-claude/structured-outputs
- 2.документации Anthropic по strict tool useplatform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use
- 3.страницы цен Anthropicplatform.claude.com/docs/en/about-claude/pricing
- 4.документации Claude Agent SDK по structured outputscode.claude.com/docs/en/agent-sdk/structured-outputs





