把 LLM 接进生产系统的人,迟早会在深夜遇到同一个报错:JSON.parse 炸了。模型昨天还乖乖返回结构化数据,今天突然多包了一层 markdown 围栏、少了一个闭括号,或者干脆用散文向你道歉。这篇文章系统梳理了这个问题的全部已知解法——各家厂商的原生能力(海外与国内)、约束解码的原理与边界、TypeScript 生态的应用层方案、修复策略与生产可靠性工程——最后给出一套可以直接抄的「分层防御」架构。
response_format: json_object 在所有厂商语义下都只保证「输出是语法合法的 JSON」,完全不保证符合任何 schema。OpenAI 官方原话:「尽可能始终使用 Structured Outputs 而非 JSON mode」。json_schema + strict:true(beta,官方推荐但提示生产慎用);DeepSeek、通义千问、智谱 GLM 的官方 API 至今只有 json_object,schema 只能写进 prompt,遵从靠模型自觉。市面上所有做法按「保证从哪来」分成三档,强度差异是机制性的,不是程度性的。
| 档位 | 机制 | 保证内容 | 典型形态 |
|---|---|---|---|
| ① Prompt 引导 | 把 schema/示例写进提示词,靠模型自觉 | 无保证。OpenAI 内部评测 gpt-4-0613 仅 <40% 合规 | schema-in-prompt、few-shot、prefill |
| ② JSON mode | 约束输出为合法 JSON 语法,不看 schema | 仅语法合法;截断时连这条也失效 | json_object(DeepSeek/Qwen/GLM 仅有此档) |
| ③ 约束解码 | schema 编译为文法,逐 token 掩码非法 token | 结构 100% 合规(截断/拒答除外);值的语义不保证 | OpenAI/Anthropic/Gemini/方舟 strict、自托管 XGrammar 等 |
OpenAI 的公开描述:把提交的 JSON Schema 转换成上下文无关文法(CFG),推理时每一步用文法计算「当前位置合法的 token 集合」,把非法 token 的采样概率置 0。选 CFG 而不是正则/FSM 的原因是 FSM 无法表达递归结构(深层嵌套的括号匹配),CFG 可以支持 $ref: # 递归 schema。OpenAI 官方致谢了 outlines、jsonformer、instructor、guidance、lark 等开源项目作为灵感来源。
工程代价是首次编译延迟:新 schema 第一次请求要预处理(OpenAI 2024 年数字:典型 schema 10 秒内,复杂 schema 最长 1 分钟),之后缓存复用零额外延迟。Anthropic 的文法编译产物自最后使用起缓存 24 小时,schema 结构或请求内工具集一变缓存即失效——这意味着 schema 版本化要克制,频繁变更 schema 等于持续吃首编译延迟。
OpenAI 复杂 schema 遵循内部评测:纯 prompt(gpt-4-0613)<40% → 纯训练改进(gpt-4o-2024-08-06)93% → 同模型 + 约束解码 100%。OpenAI 自己的评价:93%「仍不满足开发者构建健壮应用所需的可靠性」。注意 100% 仅指结构合法,不含值正确性;该数字出自厂商发布博客,置信度标为 medium。
全部结论基于 2026-07-07 实时抓取的厂商一手文档,每条经 3 票对抗验证。国内厂商的第三方站(SEO 中转页/API 分销商)宣称的能力一律以官方文档为准核实过。
| 厂商 / 接口 | schema 级强制 | 可用模式 | 关键限制与坑 |
|---|---|---|---|
| OpenAI | 支持 | json_schema strict(2024-08 GA)+ 旧 json_object |
仅支持 JSON Schema 子集(全字段 required、additionalProperties:false 等);strict 与并行函数调用有兼容性限制(建议 parallel_tool_calls:false);首请求编译延迟 |
| Anthropic | 支持(GA) | output_config.format(JSON outputs)+ strict tool use(strict:true),可独立或组合用 |
不支持:递归 schema、min/max、minLength/maxLength、正则反向引用/前瞻;上限 20 个 strict 工具、24 个可选参数、16 个 anyOf,超限 400;SDK 会自动剥离不支持的约束改为客户端校验(意味着数值/长度约束并不受文法强制);文法缓存 24h |
| Google Gemini | 支持 | responseSchema(Gemini 3 系为 responseJsonSchema);支持流式结构化输出 |
不支持的 schema 关键字被静默忽略(对比其他家显式 400,是独有陷阱);复杂/深嵌套 schema 可能 400;官方明文要求应用侧校验值:「schema-compliant but semantically incorrect」被列为常态失败模式 |
| 火山方舟(豆包) | 支持,beta | json_schema + strict:true(官方定位为 json_object 的演进版并明确推荐)+ json_object |
官方原话「受资源与平台负载影响,服务可用性可能随访问情况产生波动,请谨慎在生产环境使用」;不承诺 100% 正确;仅限文档列出的模型;Responses API 下参数名是 text.format;文档自己推荐用 Zod/Pydantic 做客户端校验 |
| DeepSeek | 无 | 仅 json_object(API 枚举只有 text / json_object) |
prompt 必须含 "json" 一词并给出格式示例;官方自曝已知问题:可能偶发返回空内容,缓解手段仅为改 prompt → 空内容检测 + 重试必须自备;变通:beta 的 strict tool calling 可强制函数入参 schema |
| 阿里云百炼(通义) | 无 | 仅 json_object(5 个官方页面 grep json_schema 零命中) |
System/User 消息里必须出现 "JSON" 关键词,否则直接 400(有生产事故佐证);官方生产指南点名 Ajv 校验 + 失败重试/换模型修复;开源 Qwen 权重在 vLLM 等宿主上可获文法级约束(宿主能力,非平台能力) |
| 智谱 GLM(Z.AI / bigmodel) | 无 | 仅 json_object(API 将 response_format 封闭枚举为两值) |
schema 须写进 system prompt;官方文档自己开出多层防御处方:「多层验证:Schema 验证 + 业务逻辑验证」「准备简化的备用 Schema」「详细记录错误信息」,官方示例同时 catch JSONDecodeError 和 ValidationError |
只有 json_object 的三家国内厂商(DeepSeek/通义/智谱),官方文档反而写得最像「防御性工程指南」——等于厂商以文档形式承认:json_object 的输出不能直接信任。这也是为什么应用层防御无论对接哪家都不能省。
这是跨 OpenAI / Anthropic / Google / 方舟四家一致的结论,四家文档都给出了对应的检测手段——意味着这些分支在任何厂商下都必须写。
| 失败模式 | 表现 | 检测方法 |
|---|---|---|
| max_tokens 截断 | 输出是不完整、违反 schema 的 JSON(连 JSON mode 的语法保证也一并失效) | OpenAI:finish_reason === "length"(Responses API 为 status incomplete);Anthropic:stop_reason === "max_tokens" |
| 安全拒答 | 拒答文本优先于 schema 约束;仍返回 200 且照常计费 | OpenAI:独立的 refusal 字段;Anthropic:stop_reason === "refusal" |
| 值级 / 语义错误 | 结构完全合法,但字段内容是错的(约束解码原理上无法防止) | 业务规则校验(Zod refine)、下游一致性检查、evals |
| schema 不被支持 | 用了厂商子集之外的关键字:或显式 400(OpenAI/Anthropic/方舟),或被静默忽略(Gemini) | 上线前对照厂商 schema 支持清单;Gemini 需专门测试约束是否真的生效 |
| beta 可用性波动 | 方舟 json_schema 官方明示可用性随负载波动 | 保留 json_object 降级开关,灰度上线 |
「OpenAI Structured Outputs 保证输出符合 schema,因此不再需要校验和重试循环」——把它和四家厂商文档逐条对照就会发现:strict 只消灭了「schema 违规」这一类失败,截断、拒答、值级错误仍需应用层兜底,OpenAI 自己的文档甚至给出了 finish_reason 和 refusal 的官方处理代码。任何「开了 strict 就可以裸 parse」的架构,都建立在一个厂商自己都不认的前提上。
自己部署开源模型时,schema 强制能力来自推理框架而非模型。这条路能把 json_object-only 的开源权重(Qwen、GLM、DeepSeek 蒸馏系)升级成文法级强制。
guided_json,现行参数是 structured_outputs;v0.10 时代 batch ≥ 8 开启约束解码有显著性能衰减,而 SGLang 把 CPU 端掩码生成与 GPU 推理重叠执行几乎隐藏了开销——框架集成方式是自托管选型的决定性因素。基准数据两面都要看:重复性 schema(book-info)上,无约束最高只有 72% 结构正确率,开 XGrammar / LLGuidance 后都到 100%;但动态 schema 测试(github_easy)里 XGrammar 仍产出 2.21% 非法 JSON、LLGuidance 0.12%。选型经验:重复固定 schema → XGrammar(吃文法缓存红利);动态复杂 schema → LLGuidance。无论哪个,业务层解析校验兜底不能拆。
四个代表性方案,各自代表一种思路:SDK 抽象、重试循环、容错解析、类型即 schema。
generateText/streamText 上的 output 属性(Output.object/array/choice/json),抽象掉各厂商的结构化输出差异(generateObject 已不再是文档主推)。AI_NoObjectGeneratedError,附带原始文本、响应元数据、token 用量与底层原因——是现成的监控埋点。elementStream 例外——每个元素完整后单独校验。response_model 挂在 chat.completions.create 上,底层走 function calling(TOOLS 模式,默认推荐)而非裸 JSON prompt。max_retries:校验失败时自动把 Zod 校验错误回填进下一次请求让模型改正,直到通过——包括 refine 自定义业务规则。这是「validate-then-retry-with-error-feedback」模式的标杆实现。用 TypeScript 类型定义充当 schema,校验失败后携带类型错误自动修复重试。工程上已被上面几家超越,但「以类型系统为契约 + 校验修复循环」的范式来自这里。
在重新调用模型(贵、慢)之前,确定性修复(快、免费)应该先上。
```json ... ``` 包裹输出,parse 前先剥;更稳的写法是取第一个 { 到最后一个 } 的切片。JSONRepairError,可直接接 fallback 分支。有流式变体 jsonrepairTransform(默认 64KB 缓冲;单个超长字符串会触发 Index out of range,长文本字段要调大缓冲)。max_tokens 要给输出 schema 的最坏情况留出余量;修复截断 JSON 只该是最后的补救,finish_reason 检测 + 合理预算才是正解。用了 strict 也别删这一层:它决定「值」的质量,而且是向 json_object-only 厂商迁移时的可移植性保障。
messages must contain the word json in some form)。写 prompt 模板时把这当成 API 契约。{ 开头预填,模型被迫从 JSON 内部续写,天然消掉开场废话(Anthropic 经典技巧;注意 Anthropic 上 prefill 与其结构化输出功能不兼容,二选一)。reasoning 字段放在结论字段之前,等于强制先想后答;反过来把答案放前面,推理字段就成了事后编造。jsonrepair → 再 parse。零成本零延迟。每一层只兜住上一层明确漏掉的失败模式;去掉任何一层,该层对应的失败就直达用户。
async function callStructured<T>(schema: z.ZodType<T>, body: ChatBody): Promise<T> {
for (let attempt = 0; attempt <= MAX_RETRY; attempt++) {
const res = await chat(body); // L1: json_schema strict(方舟 beta,失败自动回落 json_object)
const choice = res.choices?.[0];
if (choice?.finish_reason === "length") { metric("llm_truncated"); continue; } // L2: 截断门禁
const raw = choice?.message?.content ?? "";
if (!raw.trim()) { metric("llm_empty"); continue; } // L2: 空内容(DeepSeek 已知问题)
let parsed: unknown;
try { parsed = JSON.parse(stripFences(raw)); }
catch { try { parsed = JSON.parse(jsonrepair(raw)); } // L3: 确定性修复
catch { metric("llm_unparseable", { raw }); continue; } }
const v = schema.safeParse(parsed); // L2: schema + refine 业务规则
if (v.success) return v.data;
body = withErrorFeedback(body, raw, v.error); // L3: 纠错重试(Instructor 模式)
metric("llm_schema_fail", { issues: v.error.issues });
}
throw new StructuredOutputError(); // 调用方接 L3 兜底:缓存 last-good / 降级隐藏
}
所有厂商能力结论均于 2026-07-07 对官方文档实时抓取验证;第三方宣称(如「DeepSeek/GLM 已支持 json_schema」的 SEO 站)已核实为不实或非官方能力。