结构化输出为什么总失败:五个最常见的原因和修法
开了 JSON mode 和 schema 校验,模型还是吐坏 JSON。从 schema 设计、字段顺序、长文本陷阱到重试策略,逐条给出修复方案。
结构化输出是工程上最实用的能力之一,也是失败率最难解释的能力之一——明明开了严格模式,还是偶尔出问题。五个原因按实际出现频率排列。
一、schema 太复杂
模型生成长 JSON 时是在逐 token「记忆」自己写过的结构。层级越深、枚举越多,出错概率越高。
典型坏味道:嵌套 4 层以上(data.items[].attributes[].value)、一个对象 20+ 字段、大量 anyOf 或递归结构。
修法:拆成多次调用。先抽主体信息,再针对每个主体单独抽明细。多一次调用的成本,远低于解析失败重试的成本。
二、字段顺序被序列化打乱
如果 schema 从无序 dict/HashMap 生成,每次请求字段顺序可能不同。影响两个:提示缓存命不中(成本),输出稳定性受轻微影响(质量)。
修法:定义时固定顺序,并把它放在提示的固定位置。
三、在长文本里顺手输出结构
「读完这 30 页报告后输出 10 个要点」失败率很高,因为模型要在长上下文末尾精确控制格式,注意力已经分散。两段式:先让它自由总结,再用第二次调用专门格式化。第二次输入短、任务单纯,几乎不出错。适合长文档抽取时,也可以用分块边读边抽再合并。
四、必填字段没有兜底
业务上「这字段必须有」,但数据里就是没有。模型被强要求填,就只能编。
修法:所有可能缺失的字段设计成可空,并在 description 里写明「无法确定时返回 null,不要猜测」,业务层单独处理 null。
这条最常被忽略。强制必填不是提高质量,是提高幻觉率。 宁可让下游收到 null,也不要收到一个看起来很合理的假值。
五、校验和重试策略错了
失败就整段重试是最差的做法——同样输入大概率同样失败。分类型处理:
| 失败类型 | 处理方式 |
|---|---|
| JSON 语法错 | 重试一次,温度降到 0 |
| 缺字段 | 把校验错误信息回灌,让模型补 |
| 字段类型错 | 本地转换(“5” → 5),别浪费调用 |
| 枚举越界 | 映射到最近合法值,或置 null |
关键点:把校验器的错误信息回灌给模型。很多实现只重试不反馈,白白浪费机会。
六、上线前检查清单
- 层级是否 ≤ 3 层?
- 必填字段是否有真实的业务保证?
- 可能缺失的字段是否可空?
- 枚举值是否都写了含义?
- 解析失败是否有结构化回退,而不是直接报错给用户?
做完这五条,稳定性通常能从 95% 提到 99.5% 以上。剩下的 0.5%,靠兜底而不是靠提示词。