Claude 结构化输出(Structured Outputs)用法:哪些 schema 会 400
用 output_config.format 传 JSON Schema 即可拿到合法 JSON;经网关实测 minimum 报 400,minLength 被接受却不执行。
文章目录

想让 Claude 返回程序能直接 json.loads 的结果,不要再靠提示词里写"只返回 JSON",改用结构化输出:在 Messages API 的请求体里加 output_config.format,type 写 json_schema,schema 放一份 JSON Schema,Claude 会把符合这份 schema 的 JSON 写进响应 content 里的 text 块。
截至 2026 年 10 月 30 日,结构化输出是正式功能,不需要 beta 请求头;Anthropic 结构化输出文档列出的支持模型包括 Opus 5.5、Sonnet 5.5、Haiku 5.5,以及 Sonnet 4.5、Opus 4.5、Haiku 4.5 之后的各个版本,Claude API、Amazon Bedrock、Google Cloud 和 Microsoft Foundry 上都能用。如果要约束的是工具调用参数而不是回复本身,用的是另一个开关:在工具定义上加 strict: true。
这项功能保证输出能按 schema 解析,不保证每次都能用。2026 年 10 月 30 日经老张API 用 claude-haiku-5-5 测试:schema 里写 minimum 直接返回 400;写 minLength: 12 却被接受,要求返回 "ABC" 时照样给了 3 个字符;max_tokens 不够时返回 200 和半截 JSON;旧教程里的顶层 output_format 也返回 200,但 schema 被悄悄丢掉,回的是普通文字。所以代码里至少要做三件事:先看 stop_reason,再用 Pydantic 或 Zod 在本地校验长度、范围这类约束,换了网关或平台后确认输入 token 确实变多了。
最小可用请求:把 JSON Schema 放进 output_config.format
直连 Anthropic 官方 API 时,下面这个请求就能拿到结构化结果。schema 里每个 object 都写上 additionalProperties: false,需要返回的字段全部放进 required,这两处的原因在后面"哪些写法会 400"和"拿到 200 之后"两节里。
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-haiku-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
}
}
}
}'2026 年 10 月 30 日,这个请求原样发到老张API(laozhang.ai)的 Anthropic 兼容端点 https://api.laozhang.ai/v1/messages,只把地址换掉、x-api-key 换成老张API的密钥,返回 HTTP 200,stop_reason 是 end_turn,耗时 3.61 秒。JSON 在 content[0].text 里,是一个字符串,需要你自己解析一次:
说明:laozhang.ai 是我们自己的服务,其价格是平台报价,不是独立比较的结论。
{
"content": [
{
"type": "text",
"text": "{\"demo_requested\": true, \"email\": \"john@example.com\", \"name\": \"John Smith\", \"plan_interest\": \"Enterprise\"}"
}
],
"stop_reason": "end_turn",
"usage": { "input_tokens": 333, "output_tokens": 46 }
}下文所有"经老张API"的结果都来自这条通道,直连 api.anthropic.com 的行为没有对照。老张API的文档说明,部分 Claude 请求会经过 AWS 官方通道或 Bedrock 相关通道,这些通道对请求体的校验规则可能和 Anthropic 自己的 API 不一样,所以网关上的测试结果只代表网关,和官方文档冲突的地方两边都会写出来。
Python 用 messages.parse,TypeScript 用 zodOutputFormat
用官方 SDK 时不必手写 JSON Schema:Python 把 Pydantic 模型传给 client.messages.parse(),TypeScript 把 Zod schema 包进 zodOutputFormat(),SDK 会转换 schema、发出 output_config.format、校验返回值,结果放在 parsed_output 里。下面的 Python 代码用 anthropic 2.3.0 和 pydantic 2.14 跑过,Lead 故意带了几个 API 不支持的约束:
from typing import Literal, Optional
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"
resp = 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(resp.parsed_output)
# name='John Smith' email='john@example.com' plan='Enterprise' seats=40 phone=None这里的 output_format 是 parse() 方法的参数名,SDK 实际发出去的是 output_config.format;已经弃用的是请求体顶层的同名字段,两者不是一回事。
SDK 发出去之前改了什么,可以用 anthropic.transform_schema(Lead) 在本地看,它只转换、不发请求。2026 年 10 月 30 日的输出里,min_length=2 从约束变成了字段描述 {minLength: 2},ge=1, le=500 变成描述 {maximum: 500, minimum: 1},Optional[str] 变成 anyOf 字符串或 null 且不在 required 里,每个 object 补上了 additionalProperties: false。也就是说,Claude 收到的只是描述里的一句提示,真正执行约束的是你本地的 Pydantic。
同一天经老张API发了一条会违反约束的请求,内容写明对方"现在 0 个席位,请照原样报告"。API 正常返回 seats: 0,messages.parse() 随即抛出 pydantic 的 ValidationError:"seats Input should be greater than or equal to 1"。所以 parse() 要包在 try/except ValidationError 里,违反约束时决定重问、转人工还是丢弃。
TypeScript 的写法来自官方文档,parse() 同样会校验并返回带类型的 parsed_output:
import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";
const ContactInfo = z.object({
name: z.string(),
email: z.string(),
plan_interest: z.string(),
demo_requested: z.boolean(),
});
const client = new Anthropic();
const response = await client.messages.parse({
model: "claude-haiku-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Extract the key information from this email: ..." }],
output_config: { format: zodOutputFormat(ContactInfo) },
});
console.log(response.parsed_output);如果你手里是现成的 JSON Schema 而不是 Zod,jsonSchemaOutputFormat() 只做转换、不校验返回值,校验要自己补。Java SDK 的做法不同:遇到不支持的约束直接在本地报错,而不是挪进描述。
JSON 输出还是 strict 工具:看结构化数据交给谁
判断标准是这份结构化数据最后交给谁。要把 Claude 的回答本身存库、渲染或交给下游程序,用 JSON 输出(output_config.format);要 Claude 调用你的函数、参数直接进入业务逻辑,用 strict 工具(strict: true);Agent 既要可靠地调工具、最后又要一份结构化结论时,两者可以放在同一个请求里。Claude 没有 OpenAI 那种单独的"JSON mode"开关,早年用强制工具调用模拟 JSON 的做法,现在由 JSON 输出直接替代。
| 对照项 | JSON 输出 | strict 工具 |
|---|---|---|
| 约束的对象 | Claude 的回复正文 | 工具名和工具的 input |
| 写在哪里 | 请求体的 output_config.format | 每个工具定义顶层的 "strict": true |
| 结果在响应的位置 | text 块里的 JSON 字符串,要自己解析 | tool_use 块的 input,已经是对象 |
正常的 stop_reason | end_turn | tool_use |
| 适合的任务 | 信息抽取、分类、生成报告 | 预约、写库、下单这类函数调用 |
| 数量限制 | 计入同一请求的 schema 复杂度 | 每个请求最多 20 个 strict 工具 |
两者共用同一套 JSON Schema 限制,下一节的 400 和"不生效"对它们都适用。Anthropic 的 strict 工具文档举的例子是:订票函数要求 passengers 是整数,不开 strict 时 Claude 可能给出 "two" 或 "2",开了以后总是 2。
2026 年 10 月 30 日经老张API,用同一封邮件测了 strict 工具:工具 create_demo_booking 带 "strict": true,plan 和 weekday 用 enum,email 用 format: email,hour_24 是整数,并用 tool_choice 强制调用。返回 stop_reason: "tool_use",input 是 {"email": "john@example.com", "hour_24": 14, "plan": "Enterprise", "weekday": "Tue"},"下午 2 点"被换成了整数 14,"下周二"落在了 enum 里的 Tue。
{
"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
}
}两者一起用时,JSON 输出的约束只作用于 Claude 最后的回复,不管工具调用、工具结果和思考内容;工具参数由 strict 负责。OpenAI 那边的 text.format 和函数工具怎么选,见Structured Outputs 与 Function Calling:怎么选、怎么验收。
哪些 schema 写法会 400,哪些被接受却不生效
能不能用,不能只看有没有报错。官方文档说,用了不支持的关键字会收到带细节的 400;但 2026 年 10 月 30 日经老张API测试,minimum 和递归 $ref 确实返回 400,minLength、maxLength 和省略 additionalProperties 却都返回 200,其中长度约束完全没有执行。下表左边是官方文档的说法,右边是网关上的实际结果,模型都是 claude-haiku-5-5。
| schema 写法 | 官方文档 | 经老张API(2026-10-30) | 你的代码该怎么做 |
|---|---|---|---|
enum、format: email、anyOf 加 null | 支持 | 200,结果符合 schema | 直接用 |
省略 additionalProperties | 必须写 false | 200,输出仍是 4 个字段 | 显式写上 false |
整数上写 minimum | 不支持,返回 400 | 400 | 从 schema 删掉,在本地校验 |
字符串上写 minLength、maxLength | 不支持,返回 400 | 200,但要求 12 位时返回了 "ABC" | 必须在本地校验 |
递归 $ref(节点引用自己) | 不支持 | 400 | 按最大层数展开成固定结构 |
两条 400 的原文如下,type 都是 invalid_request_error。看到 output_config.format.schema: 开头的报错,就去 schema 里找它点名的关键字:
output_config.format.schema: For 'integer' type, property 'minimum' is not supported
output_config.format.schema: Invalid schema: Circular reference detected in schema definitions: node -> node. Self-referencing or mutually-referencing definitions are not supported.minLength 那一行最危险。schema 写着 "minLength": 12, "maxLength": 12,提示词让 Claude 返回代码 ABC,返回的就是 {"code": "ABC"},没有报错,也没有特殊的 stop_reason。官方文档说这种写法会被 400 拒绝,网关上却被接受了,直连官方 API 是哪种结果没有对照;无论哪种,长度和数值范围都要在你自己的代码里检查。用 Python 的 messages.parse() 或 TypeScript 的 zodOutputFormat() 时,SDK 会把这些约束挪进描述并在本地校验,正好补上这一层。
表里没列的几类,官方文档同样列为不支持:maximum 和 multipleOf,enum 里放对象或数组,外部 URL 形式的 $ref,minItems 取 0 和 1 以外的值以及其他数组约束。字符串 format 只支持 date-time、time、date、duration、email、hostname、uri、ipv4、ipv6、uuid 这十种。pattern 正则支持锚点、* + ?、简单的 {n,m}、字符类和分组,不支持反向引用、前后断言和 \b,复杂的正则也可能触发 400。
schema 单个写法都合规,合在一起也可能太复杂。官方文档给的上限按一次请求里所有 strict schema 合计:strict 工具最多 20 个,可选参数(不在 required 里的字段)最多 24 个,用 anyOf 或 ["string", "null"] 这类联合类型的参数最多 16 个;超过内部的文法规模会返回 400 "Schema is too complex for compilation",编译超过 180 秒也会失败。常见的处理是把可选字段改成必填,但如果改成"必填 + 可为 null",用的就是 anyOf,会从可选参数的 24 个额度转到联合类型的 16 个额度,字段多时要两边一起算。
返回普通文字或报 Extra inputs are not permitted:先看调用通道
请求成功却拿回一段普通文字,而且 usage.input_tokens 和不带 schema 时一样,说明格式参数根本没到模型那里,最常见的原因是代码还在用 beta 时期的写法:请求体顶层的 output_format,有时还带着 anthropic-beta: structured-outputs-2025-11-13。按官方迁移说明,这个字段已经弃用,直连官方 API 时不带 beta 头会返回 400,Python SDK 1.0 及以上在 client.beta.messages.create() 里传 output_format={...} 会直接抛 TypeError。
经过网关时可能连报错都没有。2026 年 10 月 30 日经老张API,用顶层 output_format 发了两次,一次不带 beta 头、一次带,都返回 HTTP 200,回复是一段 Markdown 列表,输入 64 个 token,和完全不带 schema 的请求一模一样;换成 output_config.format 后,同一请求是 333 个 token 加合法 JSON。网关丢掉不认识的字段、照常转发,你的程序只会在 json.loads 那一步才发现问题。

另一种情况是直接报 400 Extra inputs are not permitted,完整报错常见的样子是 "Api error 400 output_config effort extra inputs are not permitted"。这类报错来自对请求体做严格校验的通道:老张API的文档写明,走 AWS 官方通道或 Bedrock 相关通道时,通道不接受的字段会返回 400 ValidationException 或 Extra inputs are not permitted。同一天在老张API上用 Sonnet 5.5 同时发 output_config.effort 和 output_config.format,返回的是 200,所以会不会报这个错,取决于你的请求最后落在哪条通道。Bedrock 上其他 400 报错原文的对照,见AWS Claude 报 400 怎么办:按报错原文逐条排查。
换平台、换网关或升级 SDK 之后,按这几项确认格式参数真的生效了:
- 请求体里是
output_config.format,不是顶层的output_format,也不需要任何 beta 请求头。 - 模型 ID 在官方支持列表里;Claude 3.x 和 Opus 4.1 不在列表中。
- 对同一个提示词,带 schema 的
usage.input_tokens明显高于不带 schema,4 个字段的 schema 大约高 269 个。 text块能被json.loads解析,而不是 Markdown 或自然语言。- 报
Extra inputs are not permitted时,逐个去掉output_config.effort、beta 请求头这类附加字段,找出通道不接受的那一个。 - 请求里没有同时开启 Citations,两者一起用时官方 API 返回 400。
拿到 200 之后还要检查:拒答、截断、null、键顺序和 enum
HTTP 200 加上结构化输出,仍然有五种情况会让你拿到不能直接用的数据,官方文档点名了其中三种,另外两种是 2026 年 10 月 30 日经老张API测出来的。逐项写进代码比事后排查便宜得多。
stop_reason是refusal:Claude 出于安全原因拒答,状态码仍是 200,生成的 token 照样计费,内容可能不符合 schema。官方文档还提醒,schema 里放一个"逐步推理过程"字段可能触发reasoning_extraction拒答,要解释就改成"简短说明"。stop_reason是max_tokens:输出被截断。经老张API把max_tokens设成 30、让 Claude 列 15 个选题,返回 200,text是{"ideas": ["REST vs GraphQL vs gRPC: How to Choose the,没法解析。调高max_tokens重试;走老张API时单次输出上限是 32,768 tokens。- 可选字段返回
null:schema 里phone是普通字符串、不在required里,邮件中也没有电话,经老张API返回的是"phone": null,而不是省略这个键。可选字段要按"可能缺失,也可能是 null"处理,Pydantic 里写成Optional[str] = None。 - 键顺序和 schema 不同:官方文档说输出按 schema 顺序、必填字段排在前面;经老张API的每一次 JSON 输出都是按字母排序(
demo_requested、email、name……)。任何时候都按键名取值,不要按位置。 - enum 大小写不一致:官方文档说 enum 和 const 的大小写不受保证,
"Conversation topic 3"可能回成"Conversation Topic 3",响应正常结束、没有报错。经老张API连续 5 次都原样返回,但比较时仍然要忽略大小写,也不要设计只差大小写的 enum 值。

这些检查可以收进一个函数。下面的写法用 transform_schema() 把 Pydantic 模型转成 API 接受的 schema,自己发请求,所以能在解析之前先看 stop_reason,最后再用原模型做完整校验:
import logging
import anthropic
from anthropic import transform_schema
from pydantic import ValidationError
log = logging.getLogger(__name__)
client = anthropic.Anthropic()
LEAD_SCHEMA = transform_schema(Lead) # 上一节定义的 Lead
def extract_lead(text: str, max_tokens: int = 1024) -> Lead | None:
resp = client.messages.create(
model="claude-haiku-5-5",
max_tokens=max_tokens,
messages=[{"role": "user", "content": text}],
output_config={"format": {"type": "json_schema", "schema": LEAD_SCHEMA}},
)
if resp.stop_reason == "refusal":
log.warning("refused: %s", resp.id) # 不要当 JSON 解析,记录后转人工
return None
if resp.stop_reason == "max_tokens":
if max_tokens >= 8192:
raise RuntimeError("输出太长:拆分任务或精简 schema")
return extract_lead(text, max_tokens * 2)
raw = next(b.text for b in resp.content if b.type == "text")
try:
return Lead.model_validate_json(raw) # min_length、ge、le 在这里执行
except ValidationError as err:
log.warning("schema ok but constraint failed: %s", err)
return None预填充 { 来逼出 JSON 的老办法不能和结构化输出一起用:同一天在 Haiku 5.5 上把 assistant 消息 { 和 output_config.format 一起发出,返回 400 "This model does not support assistant message prefill. The conversation must end with a user message."。哪些模型拒收预填充、原来的用途改用什么,见Claude 预填充(prefill)400 报错:4.6 起模型全拒收,按用途替换。
结构化输出多花多少 token:4 个字段多 269 个输入 token
开启结构化输出后,Claude 会额外收到一段说明输出格式的系统提示,这段提示和你的 schema 一起按输入 token 计费。2026 年 10 月 30 日经老张API,同一句抽取请求在 Haiku 5.5 上不带 schema 时输入 64 个 token,带上前面那份 4 个字段的 schema 是 333 个,多出 269 个。按截至同日的 Anthropic 价格页标准输入价算,每 100 万次请求多出 269 × 1,000,000 = 2.69 亿个 token,也就是 269 MTok:
| 模型 | 输入标价(每 MTok) | 每次请求多付 | 每 100 万次请求多付 |
|---|---|---|---|
| Haiku 5.5 | $0.10(提示不超过 10 万 token) | $0.0000269 | $26.90 |
| Sonnet 5.5 | $2 | $0.000538 | $538 |
| Opus 5.5 | $4 | $0.001076 | $1,076 |
这组数字只对应一份 4 个字段、没有描述的 schema。字段越多、描述越长,多出来的 token 越多;同一封邮件改用 strict 工具抽取时,输入是 682 个 token,工具定义本身也要计费。输出那边反而可能变少:同一个样本里,普通文字回复是 91 个输出 token,JSON 只有 46 个。以 Sonnet 5.5 每 MTok $10 的输出价算,少的 45 个输出 token 约省 $0.00045,和多付的 $0.000538 输入费差不多抵消,但这取决于你的提示词原本会让 Claude 写多少废话,只能用自己的请求量去算。
还有两处会影响账单。修改 output_config.format 会让该对话已有的提示缓存失效,所以同一条对话里不要来回切换 schema。官方文档说结构化输出可以配合批处理接口享受 5 折,但老张API的文档写明不提供 Message Batches 和 /v1/messages/count_tokens 接口,要用批处理得直连官方或其他支持批处理的平台。Claude 和 OpenAI 按任务算的整体价格差,见Claude 与 OpenAI API 价格对比:按任务算谁更省。
Claude 结构化输出的常见问题
开启思考模式时还能用结构化输出吗?
能。官方文档说明,结构化输出的约束只作用于 Claude 最后的回复,不作用于思考内容,所以思考过程可以自由展开,最终回复仍按 schema 输出。2026 年 10 月 30 日经老张API,在 Sonnet 5.5 上同时设置 thinking: {"type": "adaptive"}、output_config.effort: "low" 和 output_config.format,返回 200,响应里只有一个 text 块(低 effort 下没有出现思考块),内容是合法 JSON。
流式输出能用结构化输出吗?
能。官方文档列明结构化输出支持流式,按普通流式响应接收即可;完整 JSON 要等流结束后再解析,中途的片段本身不是合法 JSON。
Amazon Bedrock 和 Google Cloud Vertex AI 上能用 Claude 结构化输出吗?
能。截至 2026 年 10 月 30 日,Anthropic 文档把 Claude API、Claude Platform on AWS、Amazon Bedrock(包括 InvokeModel)、Google Cloud 上的 Claude 和 Microsoft Foundry 都列为正式可用;Claude Managed Agents 不支持。不同平台对请求体的校验不一样,迁移后按前面"调用通道"一节的清单确认一次。
Claude Code 和 Agent SDK 的 JSON 输出也是 output_config 吗?
不是。Agent SDK 用的是另一套机制:给 query() 传 outputFormat(TypeScript)或 output_format(Python),SDK 校验结果、不符合时让 Claude 重写,结果放在 structured_output 里,具体见 Claude Agent SDK 结构化输出文档。
第一次用一个新的 schema 会更慢吗?
官方文档说会:每个 schema 第一次使用时要编译成文法,之后缓存 24 小时(从最后一次使用算起),改了 schema 的结构或工具集合才会重新编译,只改 name 和 description 不会。2026 年 10 月 30 日经老张API发了一份从没用过的 schema,再原样重复两次,耗时分别是 4.23、3.33 和 4.28 秒,经网关时这点差异被网络波动盖住了,看不出来。
零数据保留(ZDR)和 HIPAA 场景下,schema 会被保存吗?
会短期保存 schema,但不保存内容。按 Anthropic 文档,开启零数据保留时提示和输出都不存储,只有 JSON schema 会被缓存,最长 24 小时(从最后一次使用算起)。结构化输出符合 HIPAA 要求,但受保护的健康信息不能出现在 schema 的属性名、enum 和 const 值或 pattern 正则里,只能放在消息内容中。
参考来源4
本文引用的外部页面,按正文出现顺序排列。最后更新于 2026年10月30日。
参考来源4
本文引用的外部页面,按正文出现顺序排列。最后更新于 2026年10月30日。
- 1.Anthropic 结构化输出文档platform.claude.com/docs/en/build-with-claude/structured-outputs
- 2.Anthropic 的 strict 工具文档platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use
- 3.Anthropic 价格页platform.claude.com/docs/en/about-claude/pricing
- 4.Claude Agent SDK 结构化输出文档code.claude.com/docs/en/agent-sdk/structured-outputs





