JSON mode 怎么用才不会解析失败?结构化输出的原理和常见坑有哪些?
JSON mode 是把模型输出约束为合法 JSON 的解码或提示机制,它只保证语法能被解析,不保证字段名和类型符合你的 schema。要稳定用起来,需要模型侧约束解码与调用侧 schema 校验、失败重试、流式增量解析配合,缺一就容易出现“能解析但字段对不上”。
JSON mode 是把模型输出约束为合法 JSON 的机制,但它只保证语法能被解析,不保证字段名、字段类型和枚举值符合你定义的 schema。因此,信息抽取、分类打标这类可以容忍少量字段偏差的场景可以直接开;涉及金额、状态机、下游强类型入参的场景,必须再加一层 schema 校验。调用侧还要区分“解析成功”和“业务成功”:解析器不报错只代表文本符合 JSON 语法,不代表业务字段齐全。把这两件事分开,才能定位问题发生在模型侧还是调用侧。
结构化输出的本质是把模型的自由文本收敛成程序可直接消费的键值结构,常见用途是实体抽取、意图识别、工具参数生成和中间结果落库。它的适用边界很清楚:输出需要长篇解释、推理过程本身比字段更重要时,硬套 JSON 反而会把信息压碎。判断标准是下游有没有程序消费这份输出,有才值得上结构化。如果下游只是人读,或者只需要一段自然语言摘要,就不必为了形式统一而强行 JSON。
最简单的一层实现是提示词约束,在 system 里写清字段名、类型、是否必填,模型按概率生成对应结构。这条路线兼容性最好、没有额外延迟,但稳定性随 schema 变长而下降,字段一多就开始漏字段、换字段名或自造嵌套。它适合字段不超过十几个、且对失败可以重试的场景。做法上要给最小示例、枚举值列表和字段描述,把可选字段尽量收敛成明确的 null 语义,并把提示词与 schema 一起版本化,避免线上悄悄漂移。
更可靠的一层是约束解码,把 JSON Schema 或语法规则编译成有限状态机,在每一步解码时把无法构成合法 JSON 的候选 token 概率压到极低。它能把语法错误率压得很低,代价是首 token 延迟上升,并且编译出的状态机需要缓存复用,否则每次请求都重编译会明显拖慢吞吐。工程上要按 schema 指纹缓存编译结果,对动态 schema 谨慎使用,因为频繁编译会吃掉吞吐收益。
第三层是后处理修复,模型吐出文本后用正则或修复逻辑补全括号、剥掉代码块包裹、替换中文标点。它只能当兜底,不能当主方案,因为修复过程会掩盖模型真实的输出偏差,让线上问题从“解析失败”变成更难排查的“字段静默错值”。修复逻辑必须记录修复前后的原文、修复类型和命中率,修复率上升时说明模型或 schema 需要调整,而不是继续加正则。
JSON mode 和 Function Calling 不是一回事,前者只约束语法合法,后者把工具名和参数结构一起约束;strict 类结构化输出才进一步保证字段齐全、类型正确。选错层级最典型的症状就是解析成功但字段对不上,调用侧拿到一个合法 JSON 却读不到想要的键。工具调用场景应优先使用工具参数约束,不要在外层再包一套自定义 JSON 解析,否则会重复校验并放大失败面。
第一个高频坑是输出被 max_tokens 截断,表现为右括号缺失、字符串没有闭合,而解析器报的是位置偏移错误,很容易被误判成模型不会写 JSON。正确做法是给输出预留足够的 token 余量,并检查返回的结束原因是不是长度截断,是就单独走重试而不是原地修复。重试时可以调高上限或减少输入,对长输出可以分片生成再合并,避免在截断文本上做不可靠的补全。
第二个坑是转义与类型转换。换行、双引号、反斜杠在字符串里必须正确转义,否则解析直接失败;大整数在部分实现里会被浮点精度吃掉;布尔值和字符串“true”也经常被混用。在 schema 里显式声明类型、枚举和数值范围,能把这些模糊地带提前收紧。日期时间要统一格式,金额可用字符串或最小货币单位整数,明确 null 与缺失字段的区别,避免下游把空值当零值。
第三个坑是 schema 嵌套过深、字段过多。层级越深,模型越容易漏掉某一层或临时编造字段,尤其是可选字段和 oneOf 分支叠加时。工程上的做法是尽量扁平化,把可选字段收敛成明确的 null 语义,分支类型控制在少数几个以内。把大 schema 拆成多次调用、每次只抽一部分,并用必填字段锚定结构,通常比一次性生成深层嵌套更稳。
第四个坑出现在流式返回场景。流式接口每次推送的是 JSON 片段,直接对片段做 json.loads 必然抛错,这不是模型的问题而是解析时机的问题。可行做法是用增量解析器边收边构建,或者累积到括号闭合再整体解析,代价是首字节可用时间被推迟。增量解析还要处理字符串内部的括号和转义,不能简单按括号计数;对嵌套结构,通常只对顶层对象做增量,内部字段等闭合后再校验。
调用侧的处理链路基本固定:解析后用 JSON Schema 校验器或类型模型做一次结构校验,失败时把原始输出和具体报错一起回填到重试提示里,同时限制重试次数并设置整体超时。校验不通过就重试,能显著降低下游拿到脏数据的概率,但重试本身会放大 token 成本。重试提示要带失败原因,比如缺少某字段、类型应为整数,而不是原样重复问题;失败样本要留档,用于迭代 schema 和提示词。
选型上要做的是成本与稳定性的权衡:约束解码会牺牲生成速度,schema 越长占用的输入 token 越多,而提示词方案省成本但需要调用侧兜底。字段少且 schema 稳定时用提示词加校验就够;字段多、下游强依赖类型时,把预算放在约束解码和缓存编译结果上更划算。还要建立监控指标,至少覆盖解析失败率、schema 校验失败率、重试率、截断率和修复率,用指标决定是否升级约束层级。