为什么结构化输出在你这里总是失败:五个失败模式与修法
结构化输出失败,80%的原因不在模型,而在Schema设计和校验策略。本文给出五个最常见的失败模式、各自的修法,以及一套能显著提升一次通过率的工程做法。
结构化输出(让模型返回符合指定Schema的JSON)看起来是最简单的AI任务之一:给个Schema,要求返回JSON,完事。
实操中它会成为最让人抓狂的部分:同一个Schema,同一个提示词,昨天成功今天失败,大部分请求正常偶尔一个字段错类型。
问题通常不在模型”不够聪明”。结构化输出是一个工程问题,有明确的失败模式和明确的解法。
一、五个失败模式
模式一:Schema太深
三层嵌套加数组加联合类型,模型的出错概率随复杂度非线性上升。
典型症状:顶层字段全对,第三层某个数组里的某个字段缺失或类型错。
修法:扁平化。 大多数场景不需要三层嵌套。把结构压成两层,用 type 字段区分分支,比用联合类型可靠得多。
如果业务上真的需要三层,考虑拆成两次调用:第一次拿主体结构,第二次针对每个子项单独调用。总成本可能更低——因为失败重试的代价通常超过拆分的代价。
模式二:字段语义模糊
字段名叫 status,模型不知道要填 "active" 还是 "正常" 还是 true。
再看字段名 date:是2026-04-26,还是26/04/2026,还是”四月二十六日”?
修法:
- 字段名要具体:
is_active比status清楚 - 枚举值必须在Schema里穷举,别指望模型猜
- 每个字段配一句 description,写清格式要求
- 时间字段明确写格式,最好给出示例
字段定义大概长这样:
- 字段名:
is_active - 类型:boolean
- description:根据原文判断该账号当前是否有效,true=有效,false=已停用或过期
那句 description 里的”根据原文判断”,能省掉大量误判。
模式三:可选字段的陷阱
Schema里标记 required: ["a", "b", "c"],但实际数据里 c 经常没有。
模型面对这种情况有两种反应:编一个值,或者省略字段。两种都会让你的解析失败。
修法: 所有可能缺失的字段都设成可为 null,并在提示词里明确”找不到就填 null,不要猜”。
- 字段名:
renewal_date - 类型:string 或 null
- description:续约日期。原文未提及则必须填 null,禁止推测
“禁止推测”这四个字要写进去。 不写的话,模型会觉得编一个更”有帮助”。
模式四:提示词里的示例和Schema不一致
这是最容易被忽略的坑。Schema说输出 items,示例里写的是 list。Schema说数字,示例里写成字符串。
模型会跟着示例走,因为示例比抽象Schema更具体。于是它输出了一个”符合示例但不符合Schema”的结果。
修法: 示例必须从真实通过的输出里复制,而且要定期验证。改Schema时一定同步改示例。
更好的做法是用代码生成示例——Schema定义和示例从同一个源头来,就不会漂移。
模式五:校验只做了一半
大多数团队做了”是不是合法JSON”的校验,没做”是不是符合Schema”的校验。
于是非法数据流进业务逻辑,在更远的地方炸开,排查成本高得多。
修法: 结构校验必须在入口做,用现成的校验库(Zod、Pydantic、JSON Schema validator),失败就立刻重试或走降级。
二、一次通过率的工程提升
1. 用原生结构化输出接口
如果模型厂商支持”强制JSON Schema”(如 response_format 加 schema 参数),一定用。它不是”提示模型输出JSON”,而是在解码阶段就限制合法token,从机制上保证结构正确。
这一项通常能把结构错误率从5%降到接近0。 但注意:它保证结构,不保证内容。字段填错了它管不了。
2. 分两步:先思考,后输出
对需要推理的任务,不要让它直接吐JSON。两段式:
- 第一次调用:自由文本,让它分析
- 第二次调用:把分析结果作为输入,要求输出严格JSON
或者在同一次调用里用两个字段:
也就是让输出有两个可选字段:一个 reasoning(先写分析过程),一个 result(放最终结构)。
这样模型能”先想清楚再给结论”,字段填对率明显提升。 代价是输出token变多,用推理字段也要注意别让它写太长。
3. 重试要带错误信息
失败重试时,把校验错误原文给它:
你上次的输出校验失败,错误信息:
items[2].price: expected number, got string "¥128"。请修正后重新输出完整JSON。
带错误信息的重试成功率远高于盲目重试。 一般两轮内能解决90%的问题。
4. 设定最大重试次数和降级
三次都不成功,千万别继续重试。走降级:
- 返回部分结果 + 标记不完整
- 转人工
- 用规则兜底
无限重试是成本黑洞。 一个卡住的请求循环五次,成本是一个成功请求的五倍以上。
5. 采样温度设低
结构化抽取任务几乎没有创造性需求。温度设在0~0.2,能显著提升输出稳定性。
三、字段设计的几个经验
- 枚举优于自由文本:能列出来的都列出来,加一个
"other"兜底 - 数字用数字类型:金额、数量、百分比别用字符串。如果原文带单位,让模型输出数值,另开字段存单位
- 避免布尔字段的否定语义:
is_not_expired这种双重否定容易出错,用is_expired - 数组要有最大长度限制:不限制的话,模型可能给你一个50项的列表
- 留一个
confidence字段:让它给自己的抽取打分,低于阈值的结果交给人工复核
最后这一条在业务上非常有用。一个会说”我不太确定”的系统,比一个永远自信的系统更可控。
四、测试怎么做
结构化输出的测试很简单:准备50条真实输入,跑一遍,统计每个字段的准确率。
统计到字段级别,你会发现规律:
- 某个字段准确率特别低 → 是字段定义问题
- 某类输入整体失败 → 是提示词覆盖问题
- 结构错误偶发 → 是Schema复杂度问题
按字段统计,比看整体通过率有用十倍。 整体90%看起来不错,但如果那个90%是靠简单字段撑起来的,你最关心的那个字段可能是60%。
五、一份可用的检查清单
上线前逐条对照:
- Schema是扁平的,嵌套不超过两层
- 所有字段名语义明确
- 所有枚举穷举,带other兜底
- 可能缺失的字段允许null,提示词写明禁止推测
- 示例是从真实输出复制的,和Schema一致
- 使用原生结构化输出接口
- 推理和结果分开字段
- 重试带校验错误信息
- 最多重试2~3次,有降级路径
- 温度≤0.2
- 有字段级别的准确率统计
结语
结构化输出本质上是把”AI的不确定性”压缩到可控边界的工程活。
压缩不靠更长的提示词,靠更清楚的Schema、更明确的约束、更有针对性的校验、更聪明的重试。
真正的护城河不在模型能力,在你能不能把它的不确定性框住。