上周处理一个工单,用户贴了一句报错:JSONDecodeError: Expecting value: line 1 column 1 (char 0)。他的代码完全正确——提示词里写了"请只返回 JSON",模型也确实返回了看起来像 JSON 的东西。真正的问题在返回内容的最前面多了六个字符:```json。
这不是个例。把"大模型 JSON 解析失败"当作一个搜索词去看社区提问,你会发现大量开发者卡在同一个认知错位上:以为"让模型输出 JSON"和"让模型按我的 schema 输出 JSON"是同一件事。它们在 2026 年已经是两套完全不同的技术栈,而各家平台的实现口径、参数名、能力边界还都不一样。
这篇文章不讲价格,也不讲省钱。我按各家官方文档逐项对照了一遍结构化输出的真实能力边界,把它整理成一份排错手册:先给症状和根因的对应关系,再给五家平台的能力对照表,然后是 10 个我在实际项目里踩到的坑,最后是一棵按报错信息直接定位问题的决策树和可复用的兜底代码。文中所有参数名和能力限制都来自官方文档,不含推测。
一、先分清两种失败:格式错,还是结构错
排错的第一步不是看代码,是看模型到底返回了什么。把异常现场归到两类,解决方式完全不同:
| 症状 | 典型返回 | 根因 | 正确做法 |
|---|---|---|---|
| 格式错 | ```json
{...}
``` |
没用 response_format,只靠提示词;模型把 JSON 当代码块 | 启用结构化输出,从解码层消除可能 |
| 结构错 | {"name":"张三","age":"28"} |
用了 json_object,字段类型靠提示词约束 | 换 json_schema strict |
| 截断 | {"items":[{"id":1},{"id":2 |
finish_reason 为 length,输出撞上 max_tokens | 调大 max_tokens,不要盲目重试 |
| 拒答 | message.refusal 非空 |
模型基于安全策略拒绝生成 | 走业务兜底,不要重试 |
最后一行的拒答是最容易被忽略的。启用 strict 之后,schema 约束会被安全策略绕过——refusal 发生时没有可解析的 payload,你的代码如果直接访问 .parsed,会在两行之后收到一个莫名其妙的 AttributeError: 'NoneType' object has no attribute ...,排查方向完全跑偏。这一点在 OpenAI 官方文档里写得很明确:always check for it before parsing。
二、五家平台能力对照(按官方文档口径)
先说结论:能真正做 schema 级约束的只有三家半。OpenAI 最成熟,Claude 和 Gemini 各自有清晰实现,通义千问只覆盖部分新模型,DeepSeek 在 Chat Completions 接口上至今没有 schema 约束能力。下表是逐项对照:
| 能力项 | OpenAI | Claude | Gemini | DeepSeek | 通义千问 |
|---|---|---|---|---|---|
| 参数名 | response_format |
output_config.format |
responseSchema |
response_format |
response_format |
| 仅保证合法 JSON | 支持 | 不支持 | 支持 | 支持 | 支持 |
| schema 强约束 | strict:true | json_schema | schema 字段 | 无(Responses 接口有) | strict:true(限部分模型) |
| 可选字段写法 | 类型联合 null | 类型联合 null | 类型联合 null | 靠提示词 | 类型联合 null |
| 拒答信号 | refusal 字段 | 异常/错误 | 安全阻断 | 可能返回空内容 | 可能返回空内容 |
| 额外计费 | 无 | 无 | 无 | 无 | 无 |
几个容易踩的细节值得单独说:
OpenAI:strict 是"编译期约束",不是"校验器"
OpenAI 的 strict 模式走的是约束解码——在每个 token 采样时把不符合 schema 的 logit 屏蔽掉。所以它保证的是"输出一定符合结构",而不是"内容一定对"。官方文档列明的限制有三条最关键:
- 不强制的内容类关键字:
pattern、minLength、maxLength、format、minimum、maximum都不会被模型执行。这些得你在业务代码里自己校验。 - 顶层必须是 object:不能直接返回数组,得包一层
{"items": [...]}。 - 嵌套深度约 5 层,超了要么报错要么被摊平。
还有两个容易漏的:strict: true 会禁用并行工具调用,需要显式设 parallel_tool_calls: false;新 schema 的第一次调用要编译语法,有大约 100~500 毫秒的一次性开销,之后走缓存,影响不大。这个"schema 也算输入 token"的点,在做 API Token 计费原理 那篇里也提过——复杂 schema 定义会带来几百个额外输入 token,量大时不是小数目。
Claude:没有 json_object,prefill 已被废弃
Claude 的结构化输出走 output_config.format,底层复用了工具调用机制:把 schema 当作一个虚拟工具的输入,强制模型调用它,再从 tool_use 块里取结果。2026 年 2 月 4 日起该能力在 Claude Developer Platform 和 Amazon Bedrock 上 GA,覆盖 Sonnet 4.5、Opus 4.5、Haiku 4.5。
两个专属坑:
- 没有 json_object 模式。想要"随便给个 JSON"也必须给完整 schema,没有 OpenAI 那种轻量选项。
- assistant prefill 已废弃。老教程里用
{"role":"assistant","content":"{"}撬开 JSON 开头的那套写法,在 Sonnet 4.6+ 已经失效,官方建议迁移到output_config。如果你在网上搜到 2025 年的教程,大概率还在教 prefill。
另外 Claude 的 schema 编译对复杂 schema 可能要 2~5 秒,且缓存按账号和模型走,schema 内容哪怕只改一个空格都会让缓存失效。生产环境建议启动时发一个预热请求。
Gemini:OpenAPI 子集 + 一个别家没有的字段
Gemini 用 responseMimeType: "application/json" 加 responseSchema。它的 schema 是 OpenAPI 3.0 的子集,不是完整 JSON Schema——这意味着很多 JSON Schema 关键字(anyOf、pattern 等)不被支持,且不支持的字段往往不会明确报错。
Gemini 有一个别家都没有的能力:propertyOrdering。标准 JSON Schema 里键顺序无所谓,但 Gemini 会受顺序影响输出质量,用它可以指定字段输出顺序,比如让模型先给证据再给结论。对纯分类任务还能直接用 responseMimeType: "text/x.enum" 返回单个枚举值,完全不需要后处理。
迁移坑:Gemini 的 type 关键字是大写的(OBJECT、STRING、INTEGER),而 JSON Schema 标准写法是小写。跨平台维护同一套 schema 时,这一个大小写差异就能让你排查半天。Gemini 官方文档自己也建议:即使是合法 JSON,也要始终在应用侧校验字段值。
DeepSeek 与通义千问:能力分界线要看清
DeepSeek 的 Chat Completions 接口在 API Reference 里 response_format.type 只列了 text 和 json_object 两值,没有 json_schema。它的 JSON 模式有三个必须遵守的条件,任何一条不满足都会出问题:
- 提示词里必须出现"json"这个词(大小写不敏感),否则请求直接被拒。
max_tokens要给够,否则 JSON 会被截断在半路,finish_reason变成length。- 官方明确写了"可能偶尔返回空内容",这个问题他们自己也在优化——所以你的代码必须处理空 content。
另外 DeepSeek V4 的思考模式会分开返回 reasoning_content 和 content,解析时只取 content,别去解 reasoning 字段。思考模式默认开启,做结构化抽取时官方建议显式关掉。相关成本细节可以看 推理模型思考 Token 计费规则。
通义千问的 JSON Object 模式覆盖面很广(Qwen、Kimi、GLM、DeepSeek 系列都支持),但 JSON Schema 模式目前只覆盖 Qwen3.8-Max、Qwen3.7-Max、Qwen3.7-Plus 等部分新模型,官方文档明确写着"更多模型即将支持"。还有个关键提醒:官方标注为"非思考模式"的模型在开启思考模式后,结构化输出可能不生效,甚至返回不严格合法的 JSON。如果你走的是思考模型,务必实测验证,别默认它按文档工作。
三、10 个真实踩坑点
下面这些不是理论推演,是几个项目里实际卡住过的点:
- optional 字段在 strict 下报 400。 strict 要求 properties 里所有字段都进 required。可选字段要写成类型联合 null,别用"不写进 required"的方式。
- schema 里用了 pattern 以为是正则约束。 OpenAI 和 Gemini 都不执行 pattern、minLength 这类关键字。长度/格式校验必须自己写。
- 顶层写了 array 被拒。 OpenAI 顶层必须是 object,数组要包一层。
- refusal 没检查,.parsed 是 None 引发 AttributeError。 安全拒答时 schema 约束被跳过,没有 payload 可解析。
- finish_reason=length 当成解析失败无限重试。 这是截断,调大 max_tokens 就行,重试只会重复烧钱。
- DeepSeek 提示词里没写"json"被拒。 错误信息类似 "messages must contain the word 'json' in some form"。
- DeepSeek content 为空没兜底。 官方承认可能返回空内容。
- Claude prefill 在新模型上失效。 2025 年的教程大多是 prefill,2026 年已废弃。
- Gemini schema 用了 anyOf/pattern 被静默忽略。 它是 OpenAPI 子集,不支持的字段不报错,只是没效果。
- schema 每次请求都动态生成。 既失去 grammar 缓存,又把 schema 变成持续输入 token 成本。schema 应当按功能点固定下来复用。
四、按报错信息定位的决策树
排错的时候对着这张表走,比逐个 try 试快得多:
JSON 解析失败 / 返回不稳定
│
├─ 返回带 ```json 围栏?
│ └─ 是 → 没启用 response_format。启用结构化输出,不要靠正则剥围栏
│
├─ finish_reason == "length"?
│ └─ 是 → 输出被 max_tokens 截断。调大上限,不要重试
│
├─ 有 refusal 字段且非空?
│ └─ 是 → 安全拒答,schema 约束被绕过。走业务兜底,不要重试
│
├─ content 为空字符串?
│ └─ 是 → DeepSeek/通义的已知问题。空内容兜底 + 有限重试
│
├─ 400 且报 schema 不合法?
│ └─ 检查:可选字段用了联合 null?顶层是 array?嵌套超 5 层?
│
└─ 解析成功但字段不对?
└─ 用的是 json_object 而非 json_schema。升级为 strict 约束
五、可直接复用的兜底重试代码
无论哪家平台,生产代码都应该有三层防护:空内容兜底 → 按根因分类 → 有限重试。下面是 Python 版的核心骨架:
import json, time
def extract_json(client, prompt, schema, max_retries=2):
"""三层防护:空内容兜底 → 按根因分类 → 有限重试"""
for attempt in range(max_retries + 1):
resp = client.chat.completions.create(
model="gpt-4.1",
messages=[{"role": "user", "content": prompt}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "extraction_result",
"strict": True,
"schema": schema, # 可选字段记得用 ["string", "null"]
},
},
max_tokens=2048, # 给够,避免 length 截断
temperature=0,
)
msg = resp.choices[0].message
finish = resp.choices[0].finish_reason
# 层1:安全拒答 —— schema 约束被绕过,重试无意义
refusal = getattr(msg, "refusal", None)
if refusal:
raise SchemaRefusalError(refusal)
# 层2:截断 —— 调大上限,别在这里重试
if finish == "length":
raise TokenLimitError("输出被 max_tokens 截断,需要调大上限")
# 层3:空内容 —— DeepSeek/通义官方承认的已知问题
raw = msg.content or ""
if not raw.strip():
if attempt < max_retries:
time.sleep(2 ** attempt) # 指数退避
continue
raise EmptyContentError("模型返回空内容")
# 层4:真正需要重试的只有校验失败
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
if attempt < max_retries:
prompt += "\n上次输出不是合法 JSON(%s)。只返回 JSON,不要任何其他文字。" % e
time.sleep(2 ** attempt)
continue
raise
# 内容层校验:strict 不执行 pattern/minLength/format,必须自己查
validate_business_rules(data, schema)
return data
raise RetryExhaustedError("已达最大重试次数")
三点要强调:
- 重试上限必须有。没有上限的重试是账单杀手,配合 API 预算配额管理 里的消费预警才是完整方案。
- 重试要带错误信息回去。把
json.loads的报错原样拼进下一轮提示词,成功率明显高于原样重发。 - 内容校验不能省。schema 约束的是结构,不是内容。价格、数量、日期这些字段的合法性,模型不负责。
六、怎么把这些接进你的工程
结构化输出真正落地,通常还牵扯三件事:
- 工具调用的参数校验——function calling 的参数本质也是 schema 约束。各家 Function Calling 实测对比那篇里有准确率和 token 开销的数据,可以作为工具定义的参考。
- 回归测试——把 schema 变更纳入 CI,用固定输入跑一批样本校验输出。AI API 自动化测试与 CI/CD 集成那篇讲了三层测试金字塔怎么搭。
- 提示词兜底——schema 解决结构,
description字段解决内容质量。字段描述写清楚,比在提示词里写十行都管用。
如果你的服务要同时接多家模型,建议把 schema 定义收敛成一份内部标准,再按各家方言做适配层——包括 DeepSeek 和 Gemini 那些"能跑但有坑"的地方。迁移相关的问题可以看 AI API 迁移完全指南,多模型并行的调度思路在 AI 模型智能路由实战里有完整方案。
七、写在最后
结构化输出这件事,2026 年最大的变化是它从"提示词技巧"变成了"基础设施能力"。但五家平台的能力边界至今没有拉齐:OpenAI 的 strict 最完整,Claude 的 output_config 最优雅,Gemini 的 propertyOrdering 最有想法,DeepSeek 和通义千问还在从 JSON 模式往 schema 约束过渡。
所以我的建议很直接:新项目一律从 json_schema strict 起步,不要从 json_object 起步。json_object 看着简单,但它把"字段对不对"这件事全推给了提示词,而提示词约束在生产环境里的可靠性,远不如解码层约束。已经在用 json_object 的老项目,值得花时间升级一轮。
至于各家文档——本文引用的都是官方页面,建议对照着看,因为各家都在快速迭代:
参考来源
- OpenAI Structured Outputs 官方文档 —— strict 模式约束、不支持关键字清单、refusal 处理
- Claude Developer Platform 结构化输出文档 —— output_config.format、严格工具使用、语法编译缓存
- Anthropic 官方博客:Structured outputs on the Claude Developer Platform —— GA 时间线与模型覆盖范围
- Gemini API 结构化输出文档 —— responseSchema 支持的 OpenAPI 子集、propertyOrdering、text/x.enum
- DeepSeek API JSON Output 指南 —— json_object 三项前提、max_tokens 建议、空内容说明
- Qwen Cloud 结构化输出文档 —— JSON Object 与 JSON Schema 模式对照、支持模型清单、思考模式注意事项
参考资料
- OpenAI Structured Outputs 官方文档(strict 模式约束、不支持关键字清单、refusal 处理):platform.openai.com/docs/guides/structured-outputs
- Claude Developer Platform 结构化输出文档(output_config.format、严格工具使用、语法编译缓存):platform.claude.com/docs/en/build-with-claude/structured-outputs
- Anthropic 官方博客:Structured outputs on the Claude Developer Platform(GA 时间线与模型覆盖):claude.com/blog/structured-outputs
- Gemini API 结构化输出文档(responseSchema 支持的 OpenAPI 子集、propertyOrdering、text/x.enum):ai.google.dev/gemini-api/docs/structured-output
- DeepSeek API JSON Output 指南(json_object 三项前提、max_tokens 建议、可能返回空内容):api-docs.deepseek.com/guides/json_mode
- Qwen Cloud 结构化输出文档(JSON Object 与 JSON Schema 模式对照、支持模型清单、思考模式注意):docs.qwencloud.com/developer-guides/text-generation/structured-output