Claude API 结构化输出:约束响应、校验工具入参,以及取代 prefill 的做法
人们说「Claude API 结构化输出」时,其实指的是两个不同的问题,而它们对应两套不同的机制。一个是约束模型响应的形状;另一个是保证 Claude 传给你工具的参数一定符合你的 schema。用错机制——或者用了一个已经从 API 里移除的技巧——占了这个领域里绝大部分挫败感的来源。
约束响应:output_config.format
要让响应本身符合某个 schema,在 messages.create() 上传入带 format 对象的 output_config。这是常规 Messages API 上的一个参数——一般情况下既没有独立端点,也不需要 beta 请求头。
推荐路径是 client.messages.parse():它发送请求并替你按 schema 校验响应,而不是把一个字符串丢给你、由你自己解析再自己检查。
有一处需要明确纠正,因为大量旧示例代码里还在这么写:顶层的 output_format 参数已废弃。改用 output_config: {format: {...}}。这是一个通用 API 变更,不针对某一代模型。
有一个不兼容值得提前知道:结构化输出不能与引用(citations)同时使用。如果你在 document 块上设了 citations: {enabled: true} 又同时要求输出格式,请求会返回 400。
校验工具入参:strict
第二套机制是 strict: true,它解决的是另一个问题——保证 tool_use 块上的 input 对象严格符合该工具的 schema。
这里绝大多数失败来自两个细节:
strict 放在工具定义上,不是放在 tool_choice 上。 它是与 name、description、input_schema 平级的顶层字段。放到 tool_choice 上不会起任何作用。
schema 本身有要求。 它必须设置 additionalProperties: false,并且必须带 required 数组。缺了这两样的 schema 不满足严格模式。
严格模式并非与一切兼容。它不能与「代码执行内调用自定义工具」的用法、disable_parallel_tool_use、强制 tool_choice,或 MCP 工具同时使用。如果你需要其中之一,就得换一条保证可靠性的路——通常是在响应侧用结构化输出,或者在你这一侧做校验。
永远解析工具入参,不要做字符串匹配
这一条可以侥幸很久,然后在生产环境里炸。
当前这批模型——Fable 5 与 5.1、Opus 5,以及 4.6 / 4.7 / 4.8 家族——在工具调用的 input 字段里可能产出不同的 JSON 字符串转义,包括 Unicode 转义和被转义的正斜杠。值本身是对的,但它的序列化形态在不同模型之间并不稳定。
所以要用真正的 JSON 解析器去解析工具入参——Python 里的 json.loads()、JavaScript 里的 JSON.parse()——然后从解析出的对象上读字段。任何对序列化后的入参做子串匹配或正则抽取的代码,都是在依赖一个被明确允许变化的转义细节;换个模型它就会失败,而且没有其他症状。
取代 prefill 技巧的做法
过去强制拿到 JSON 的标准技巧是 assistant prefill:在 messages 数组末尾放一轮只含一个左花括号的 assistant 消息,让模型只能在一个 JSON 对象内部继续写下去。
这个技巧现在返回 400。 Assistant 消息 prefill 在 Fable 5 与 5.1、Opus 5、Sonnet 5,以及 Opus 4.6、4.7、4.8 和 Sonnet 4.6 上都已移除。它不是「已废弃但还能用」,它是一个错误。
替代方案,按直接程度排序:
- 结构化输出——
output_config.format。prefill 技巧本来就是在近似它,现在有了正规做法。 - 系统提示里描述所需形状,适用于 schema 机制不好表达的格式。
还有一个相关的移除值得知道,如果你曾经把「强制工具调用」当作抽 JSON 的技巧:在 Claude Fable 5.1 与 Claude Mythos 5.1 上,tool_choice: {type: "any"} 与 {type: "tool", name: ...} 返回 400,token 计数与 Batches API 上同样如此。如果那个强制工具调用只是为了拿回结构化数据,结构化输出就是它的直接替代。如果你确实想让模型调用某个特定工具,用 tool_choice: {type: "auto"} 配一条点名该工具的明确指令,并加上 strict: true 来保证参数符合 schema。{type: "none"} 不受影响,disable_parallel_tool_use 与 auto 仍可配合使用。
两套机制怎么选
问自己一个问题:拿到结果之后你要拿它做什么。
当模型的答案本身就是数据时,用结构化输出。 分类、抽取、打分——凡是你想把响应反序列化成一个带类型的对象然后继续往下走的场景。client.messages.parse() 把校验作为调用的一部分交给你。
当模型是在调用你的代码时,用 strict 工具。 schema 就是你的函数签名,而严格模式正是让你不必在每个调用点写防御性解析的东西。
在既做结构化工作、又要返回结构化最终答案的 agent 里,两个都用。 它们作用在交互的不同部分,不冲突。
如果你想要结构的理由是「模型有时候把 JSON 包在一段散文里」或者「有时候它加了一个 markdown 围栏」,那正是结构化输出要解决的事情。不要用「把提示写得更严厉」加一条正则去解决它。
关于 schema 设计的一点补充
schema 不只是校验,它同时是指令。模型会读字段名和描述,所以一个叫 t、没有描述的字段,产出的结果会比一个叫 sentiment、带一行说明的字段更差——尽管两者的校验行为完全相同。
在数据允许的范围内让 schema 尽量扁平。深层嵌套和很长的联合类型,模型更难正确填写,你在某个字段回来是空的时候也更难排查。并且让 required 保持诚实:把每个字段都标成必填,会逼模型给那些它根本没有依据的东西编一个值,而这比一个你能检测到的缺失字段更糟。
常见问题
怎么让 Claude API 返回 JSON?
在 messages.create() 上传带 format 对象的 output_config,或者用 client.messages.parse() 一步完成发送与校验。旧的顶层 output_format 参数已废弃。
还能用 assistant prefill 强制 JSON 吗?
不能。Prefill 在所有当前模型上都返回 400。结构化输出取代了这个技巧,schema 表达不好的格式则退回到系统提示指令。
strict 应该放在工具上还是 tool_choice 上?
放在工具定义上,作为与 name、description、input_schema 平级的顶层字段。同时 schema 必须设 additionalProperties: false 并带 required 数组。
为什么不同模型返回的工具入参看起来不一样?
工具调用入参内部的 JSON 字符串转义在不同模型之间会变化——Unicode 转义和被转义的正斜杠都是允许的。用 JSON 解析器解析入参,而不是匹配序列化后的字符串。
结构化输出能和引用一起用吗?
不能。在 document 块上启用 citations 的同时要求输出格式会返回 400。每次请求二选一。
tool_choice: any 为什么不能用了?
强制工具调用在 Claude Fable 5.1 与 Claude Mythos 5.1 上已被移除——any 与 tool 在那里都返回 400。改用 auto 加一条点名工具的指令,加 strict: true 保证参数合法;如果那个强制调用本来只是为了拿 JSON,就换成结构化输出。
一句话版本
两套机制,两件事:output_config.format 约束响应,工具定义上的 strict: true 约束工具参数。前者优先用 client.messages.parse(),并记住 output_format 是已废弃的写法。Prefill 已经没了——它返回 400 而不是警告——最新模型上强制 tool_choice 也没了,所以「我需要拿回 JSON」的直接答案现在就是结构化输出。工具入参要用 JSON 解析器解析,永远不要用正则。并且把 schema 当成提示来写:字段名要有描述性、能扁平就扁平,required 只标在那些「你宁可要一个错值也不要缺失」的字段上。