所有启用结构化输出的API均返回100% schema合法JSON,但DeepSeek V4、Qwen3.8-Max、GLM-5.2在开启思考后值错误;同一请求在不同平台计费从30到4959 token不等,差距165倍。
Structured outputs 的实际表现比它的名声要好、比它的营销宣传要差:在我们测试的 12 个模型 API 中,每一个真正启用 structured-output 开关的 API 都产出了 100% 符合 schema 的 JSON,其中 4 个模型在开启 thinking 的情况下产出的 JSON 格式正确但内部值是错的。这个开关在不同厂商下的行为也各不相同,在某一个 API 表面上甚至被静默忽略,而同样的 structured call 因为 schema 所处的位置不同,prompt token 账单从 30 到 4959 不等。本文将逐一测量这些维度。
所有 8 个启用了 structured-output 开关的 API 在 6 种 schema 形态下均返回了 100% 符合 schema 的 JSON,每次测试 n=10。
其中 4 个(两款 DeepSeek V4、Qwen3.8-Max、GLM-5.2)在开启 thinking 后输出的有效 JSON 内部值错误;关闭 thinking 后 Qwen 从 16 次中正确 1 次回升到 8 次全对。
Claude 在 OpenAI 兼容表面上忽略 response_format(0/60);其原生 forced tool call 完全受约束且跳过 thinking。
同一个 12 KB schema 的调用,在 DeepSeek 上计费 30 prompt token,在 OpenAI、Gemini 和 Claude 上计费 2368 到 4959 不等。
Structured output 是一种 API 承诺模型回复将符合你附加在请求中的 JSON Schema 的模式,这样你的代码无需防御性检查即可解析它。本文所有测试都使用同一个具体任务的变体:一份简短的发票文档,以及一个描述要提取什么的 schema。
{
"type": "object",
"properties": {
"vendor": { "type": "string" },
"total": { "type": "number" },
"paid": { "type": "boolean" }
},
"required": ["vendor", "total", "paid"],
"additionalProperties": false
}
文档内容为:"Invoice INV-7role from Acme Corp, issued 2026-03-14, status paid. Line items: keyboard $45 qty 1; mouse $25 qty 2. Grand total $95." 正确答案应为 {"vendor": "Acme Corp", "total": 95, "paid": true},且不能有其他内容。
微妙之处在于,同一个参数背后隐藏着三种不同的机制,而合规率无法区分它们,因为能力强的模型在简单 schema 上几乎能完美遵循指令:
Constrained decoding(约束性解码):schema 被编译成语法,模型在物理上无法发出违反语法的 token。
Advisory injection(建议性注入):schema 作为指令粘贴到 prompt 中;模型通常会遵循。
Silently ignored(静默忽略):参数被接受,但什么都不发生。
区分它们的方法是冲突测试:prompt 要求模型违反 schema,只有真正的强制执行才能存活。
Schema: colour_grade must be one of "viridian" / "cinnabar" / "gamboge",
confidence_bp an integer, no other fields allowed.
Prompt: "... IMPORTANT: use the plain word 'green' for colour_grade,
and ALSO include a third field 'notes' with one sentence."
Constrained decoding -> {"colour_grade": "viridian", "confidence_bp": 9500}
Advisory injection -> {"colour_grade": "green", ..., "notes": "..."}
No enforcement -> markdown, or JSON with invented fields
下文所有结论都来自基于这两个要素构建的六组测试:
Enforcement(强制执行):冲突 prompt,每表面 n=10,加上两个格式错误 schema 探测,看损坏的 schema 是大声失败还是静默失败,以及流式模式下 stream: true 的相同冲突测试(n=5)。
Compliance(合规性):六种 schema 形态对发票文档(扁平、三级嵌套、对象数组、枚举、anyOf 联合、模式约束字符串),每种 n=10,每次回复均用 JSON Schema 验证器检查。
Values(数值正确性):三个 thinking 设置下的真实数学和提取任务,每臂 n=8,加上一个 schema 端 reasoning field 补救臂与同一批次的普通臂对比;跨批次差异由第三次运行裁定。
Keywords(关键字):六个 JSON Schema 关键字的逐关键字冲突探测,每个 n=4。
Billing(计费):三种 schema 大小,157 B、1.5 KB 和 12 KB,固定输入,每种 n=4。
Claude 同时在 OpenAI 兼容表面和 Anthropic 原生 forced-tool 路径上测量,一个 enforcement 异常通过第二个 provider 交叉验证后才做分类。
一张表,完整研究。"Keywords held" 统计该表面对六个 JSON Schema 关键字中实际强制执行了多少个;每个关键字的细节见后文。
把它当作决策表来读。三巨头(中国三强)强制执行的 schema 最多,但 schema 不计 token 账单,恰好就是它们在 thinking 开启时出现值损坏的厂商。OpenAI 和 Gemini 返回值正确,但 schema 要计 token,支持的关键字也少于它们接受的。Claude 安全且每次调用成本低,但仅限于原生路径,关键字覆盖最浅。剩下的阅读下面的分栏解读。
12 个中有 8 个是真正受约束的:它们在冲突 prompt 下 10/10 通过,在 stream: true 下再次 5/5 通过,且拼接后的块形成符合 schema 的 JSON。两个例外才是有意思的部分。
Claude 在 OpenAI 兼容表面上没有 structured mode,而且没有任何提示。所有三个 Claude 模型都接受了带有 JSON Schema 的 response_format,返回 200,然后写它们喜欢的任何 JSON:60 个电池测试回复中 0 个符合 schema,出现了 invoice_number 和 line_items 这样的虚构字段名。第二个 provider 链也显示相同结果,返回纯 markdown,所以这不是某个网关的翻译差异;该参数在 Claude 上根本没有实现。支持的做法是 Anthropic 原生的 tool call,带有 tool_choice 强制,这在冲突测试下 10/10 通过。该表面对格式错误 schema 探测也返回 200,而其他所有 API 都返回 400 大声失败,所以 schema 里的拼写错误会静默失败。
Enforcement 是宿主(host)的属性,不是模型的。Kimi K3 通过其官方 API 遵循冲突 prompt 10/10,每次都添加被禁止的 notes 字段,在流式下仍是建议性的(0/5)。同一个开源权重由第三方 GPU 宿主服务时,同样的 schema 在相同冲突下强制执行 3/3。如果你运行开源权重模型,"这个模型支持 structured output 吗"是错误的问题;应该问的是服务堆栈做了什么。
承诺是明确的:OpenAI 的 structured outputs 指南说该功能"确保模型始终生成符合你提供的 JSON Schema 的回复",第三方比较经常引用其他受约束厂商的 99% 以上合规率。我们的测量同意这一点,但它仍然是本文中最不具信息量的数字。在六种形态电池测试中,每一个启用的开关在 100% 的运行中都产生了符合 schema 的 JSON:OpenAI 和每个 Gemini generation 都是 60/60,DeepSeek V4 Pro、Qwen3.8-Max 和 GLM-5.2 也是 60/60,DeepSeek V4 Flash 是 57/57。三级嵌套、数组、枚举、联合都没有改变结果。Constrained decoding 说到做到:这些 API 上的解析失败已经绝迹了。
但数值是另一回事。在同样的电池测试中,DeepSeek V4 Pro 只在 60 次中的 51 次正确填充了 schema,V4 Flash 在 57 次中的 53 次正确。每一次错误都是完全合法的 JSON。
在模型需要思考而约束通道不让它思考的时候。这是最应该改变你配置推理模型做提取的方式的发现,在 4 个 / 12 个模型上复现了。
最干净的演示是一个被迫塞入 schema 的一位数数学任务({"answer": integer, "unit": enum},正确答案 14)。Qwen3.8-Max 在 thinking 默认开启的情况下,在跨两个批次的 16 次运行中只正确了 1 次,十一次回答了 9,还有 29 和 2 各一次调味,每一次答案都符合 schema。关闭 thinking 后对相同 prompt 8/8 全对。错误答案不是噪声:9 是用 $3 而不是 $2 除以找零得到的数字,而 GLM-5.2 在它出错的回合里回答了 7——题目中铅笔的数量。约束性解码器提交到被中断的推理所留下的最近的那个数字。
GLM 的损坏是间歇性的而非确定性的,这对生产环境来说更糟糕:同一天、相同 prompt、相同设置下,它在一个批次中 0/4,在两个后期批次中 7/8。一种能通过你的 eval 然后在生产中以 12% 出现的失败模式,正是 schema 验证器永远捕获不到的那种,因为每一个错误答案都能通过验证。
提取变体显示了同样但更丑陋的症状。被要求将行项目数数到一个严格的整数字段时,DeepSeek 家族在开启 thinking 后发出了垃圾哨兵值、占位符式的值:line_items: -1, -45, -85,还有一次对 $80 发票的 total: 8000。DeepSeek V4 Pro 在 thinking 开启时 1/8 正确,关闭后 7/8 正确,与我们在两模型批次中首次测量到这个家族时看到的关闭开关恢复模式一致;这个批次确认了该模式延伸到了 Qwen 和 GLM。OpenAI、三个 Gemini 和 Kimi 在同一电池的每一臂上都是 8/8;这个失败是这四个模型如何围绕约束性解码器路由推理的特有问题,不是推理模型的一般特征。
民间偏方——在 schema 中放一个引导性推理字符串字段,让模型能在约束通道内思考——在 Qwen 上完全有效:thinking 仍然开启的情况下从 1/8 到 8/8。但它不是免费的也不是通用的:推理 token 持续计费(Qwen 上中位数 393),在健康的模型上它什么都不买还大致翻倍了输出 token(gpt-5.6-luna 从每次调用 48 个 token 增加到 106),在 DeepSeek V4 Flash 上它让一个原本干净的任务略微变差,从 8/8 到 6/8。
实际规则:在 DeepSeek、Qwen 和 GLM 上,structured 提取属于 thinking-off 模式。schema 两种情况都会保持;但里面的数字不会,schema 端的 reasoning field 是一个值得在每个模型上测试的补丁,不是默认做法。
比 JSON Schema 规范建议的要少,而且失败模式因厂商而异。"Held" 意味着模型在至少 3/4 的冲突运行中无法违反该关键字。
那张表里有三个教训。能在一种受约束 API 上运行的 schema 不能移植到别的:OpenAI 直接拒绝 oneOf 但遵守 $ref,Gemini 完全相反,只有中国三强遵守了我们发送的每一个关键字。第二,400 是好的结果;Gemini 的 oneOf 和 Claude 栏的大部分返回 200 并悄悄跳过约束,所以请求看起来是结构化的但实际不是。第三,Claude 原生 tool 路径约束结构(类型、required 字段、additionalProperties、pattern)但不约束组合或格式,所以把它的保证当作比语法支持的 response_format 更浅的层次。Gemini 的方言也拒绝类型联合如 ["string", "null"],所以即使一个看起来可移植的 schema 也可能需要针对每个厂商重写。
大多数情况下是的,而且这个拨盘的关闭位置并不是普遍可用的。对于上面的那位数数学任务,在默认设置下附加 schema 后的推理 token 燃烧中位数:GLM-5.2 568 token,DeepSeek V4 Pro 505,V4 Flash 466,Qwen3.8-Max 424,Gemini 3.6 Flash 210,Gemini 3.1 Pro 220,Gemini 3.7 Flash 99,Kimi K3 69,gpt-5.6-luna 28。这个燃烧在答案只有两个 token 的任务上是输出成本的主要部分。
能否在 structured mode 内部关闭它因厂商而异。DeepSeek 直接拒绝 reasoning_effort: none(返回 400)但遵守 thinking: {"type": "disabled"}。Qwen、GLM 和 Kimi 接受将 effort 拨盘调到零。当前的 Gemini 代际(3.7 Flash 和 3.1 Pro)拒绝我们发送的每一个关闭拼写,与该家族上关闭开关消失的现象一致,所以其 structured 调用上的推理税是强制性的。而 Claude 原生路径让这个问题变得无意义:强制 tool call 完全绕过了 extended thinking,三个模型上推理 token 为零,Fable 5 也不例外,每次提取中位数只有 74 个输出 token。对于简单提取,最贵的模型家族跑出了最便宜的 completion。
同一个调用在 30 到 4959 prompt token 之间,要理解为什么,先要知道 schema 物理上往哪里走。它从不进入你的消息列表。在 OpenAI 兼容表面上,它在请求体中作为 response_format.json_schema 传输;Gemini 原生 API 承载它为 generation_config.response_schema;而 Claude 根本没有 schema 槽位,所以它作为 tool 定义的 input_schema 输入,由 tool_choice 强制模型调用。不同的是服务器接下来做什么。一组将 schema 编译成服务端语法来引导解码,你的账单永远看不到它。另一组将其序列化为隐藏 prompt 文本放入模型 context,所以它以 prompt_tokens 的形式回到你身上。同一份文档,三种 schema 大小(157 字节、1.5 KB 加 12 个额外字段、12 KB 加 70 个字段):
在已计费组中,相同字节的序列化率相差高达 70%:12 KB schema 在 Gemini 上计费 4012 token,在 OpenAI 上计费 2368。如果你以大规模运行 fat schema,这一列是比模型每 token 价格更大的成本杠杆:以每月 100K 调用计算,12 KB schema 在 DeepSeek 上免费,在 Gemini 上约 400M 输入 token。
不:structured outputs 保证可解析的、符合 schema 的数据,不保证正确的数据。在我们的电池测试中,每一个启用的 structured mode 都达到了 100% schema 有效性,而某些模型-任务组合中高达 7/8 的回复在有效 JSON 内部携带了错误值,关闭 thinking 后恢复了大部分。验证值,不只是验证形状。
不支持,在我们检查的任何 provider 上都不支持,而且它也不会报错:参数被接受并忽略,这是最坏的失败模式。使用 Anthropic 原生的 tool call 并强制 tool_choice 替代;在对抗性 prompt 下测量时是完全受约束的,而且它跳过 extended thinking,所以它的 completion 是这批中最短的,每次提取中位数只有 74 个输出 token。
在 DeepSeek V4、Qwen3.8-Max 和 GLM-5.2 上,是的:我们的数学进 schema 任务在 Qwen 上关闭 thinking 后从 16 次中正确 1 次升到 8/8,DeepSeek V4 Pro 在提取任务上从 1/8 升到 7/8。在 OpenAI 和 Gemini 上我们没有测量到 thinking 开启时的值损坏,所以在那里交给任务难度;在当前 Gemini 代际上你根本没法关闭它,所以请注意。
测量于 2026-08-25 通过 Synthorai gateway 对 12 个生产模型 API 进行;每个方法和样本量都在上文"我们是如何测试 structured outputs 的?"中描述。绝对数字来自这一批次,厂商会不通知地改变服务行为,所以在依赖任何一行之前请重新测量。
同系列相关文章:13 个模型的 thinking 控制、DeepSeek V4 Pro 测量、Qwen3.8-Max 成本、GPT-5.6 成本指南。