为什么结构化输出在你这里总是失败:五个失败模式与修法

📅 2026/4/26 ✍️ 小文 📖 约 1 分钟

结构化输出失败,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、更明确的约束、更有针对性的校验、更聪明的重试。

真正的护城河不在模型能力,在你能不能把它的不确定性框住。

📤 分享到