编程 Structured Outputs 生产笔记:三个静默失效点

2026-09-05 00:04:52

Structured Outputs 生产笔记:三个静默失效点

Structured Outputs(结构化输出)解决的不是“模型能不能输出 JSON”,而是输出能否稳定满足字段、枚举、嵌套、必填这些结构约束。支付回调字段解析、工单分类、表单抽取这类把 LLM 结果直接喂给下游代码的场景,它比传统 JSON mode 可靠两个数量级;但 schema 写得不够严谨时,会以很隐蔽的方式静默失效。

记录三个真正会坑到人的点,以及为什么后端校验一层都不能省。

一、先分清 JSON mode 和 Structured Outputs

JSON mode 只保证合法 JSON;Structured Outputs 进一步保证符合 schema。

启用方式:

JSON mode:

response_format:{type:"json_object"}

Structured Outputs:

response_format:{
  type:"json_schema",
  json_schema:{
    strict:true,
    schema:...
  }
}

底层差异在受约束解码:模型每生成一个 token,解码器先算“当前状态下哪些 token 还能让输出对 schema 合法”,非法 token 在采样前被屏蔽。约束发生在生成过程中,不是事后校验。

所以需要固定“回复数据的形状”用 Structured Outputs;需要模型决定是否去调系统动作用 Function Calling(工具参数同样可开 strict)。两者意图不同,混用只会徒增往返。

二、坑一:嵌套对象漏写 additionalProperties:false,静默退回非 strict

OpenAI strict 模式隐性要求:每一层嵌套 object 都必须显式写 "additionalProperties": false。少写一层 API 不报错,但整个请求静默退回非 strict,模型可以自由添加 schema 外的字段。

Pydantic 的 model_json_schema() 和 Zod 的 toJSONSchema() 默认都不生成这个字段。直接用它们定义完喂给 OpenAI,大概率一直在跑非 strict 而你不知道。

自查:

  • 遍历 schema,确认每个 {type:"object"} 节点下都有 additionalProperties:false
  • 更稳做法:用 Instructor 或 zod-to-json-schemaopenaiStrictMode 转换,别让手写 schema 直接进请求。

三、坑二:optional 字段不能从 required 里删

strict 要求 required 数组包含 properties 下每个字段。把 optional 字段直接从 required 删掉会 400。

正确写法:字段仍在 required,类型允许 nullanyOf string/null)。JSON Schema 没有“可选字段”概念,可空 = 联合类型加 null。

strict 只支持 JSON Schema 子集:

  • 根必须是 object
  • 所有 object 都要 additionalProperties: false
  • minimum / maxLength / pattern 等约束关键字可能被丢弃或直接 400
  • 递归 schema 有属性总数与嵌套深度限制

开 strict 传不支持的 schema 会直接报错,不会悄悄降级。

四、坑三:refusal 和截断都以 200 成功返回

即使开 strict,仍有两种“状态成功但 JSON 不合 schema”的情况:

  • 拒答(refusal)。Chat Completions 在 message.refusal,Responses API 在 output[0].type === "refusal"。跳过检查直接 .parsed,安全审核触发时会报属性缺失。
  • 截断。撞 max_tokens 返回 finish_reason:"length",JSON 残缺。

处理顺序:

  1. 先查 refusal / finish_reason
  2. JSON.parse
  3. 最后用 Pydantic/Zod 或服务端 validator 做 schema 校验。

这层省不掉——它兜的不是格式错误(strict 已兜),而是拒答和截断这两条 strict 覆盖不到的路径。

五、把 schema 当生产接口代码

  • schema 会和下游类型漂移,改字段时 LLM 无感知。用 Pydantic/Zod 做单一来源生成,别手写两份。
  • 字段顺序在 JSON 无意义,但 LLM 从左到右生成时有意义。既有 reasoning 又有 classification 时把 reasoning 放前,让模型先分析再下结论;把 20 个取值的 enum 放第一个字段,会逼模型过早做离散选择。
  • strict 限制 token 生成速度,超长输出延迟明显上升;短结构化抽取才适合。
  • 结构合法不代表业务正确,分项之和、日期先后等规则由后端兜底。

结论

Structured Outputs 是比“请返回 JSON”强得多的接口约束,但它是强类型边界,不是可靠性全部来源。每层补 additionalProperties:false、optional 用 null 联合、refusal 与截断单独处理、后端校验兜底——做完这些才真能进生产。

参考:

推荐文章

程序员茄子在线接单