Structured Outputs不支持oneOf?这些坑文档不会告诉你
上周我们团队在做一个客服对话抽取的功能,Prompt 调了整整两天,模型还是时不时返回多余的废话或者把字段类型搞错。后来切到 Structured Outputs,10 分钟搞定,当时我就想抽自己——早干嘛去了。
所以今天想跟大伙聊聊 OpenAI 的 Structured Outputs,尤其是 JSON Schema 约束这块到底能干啥、有啥坑。官网文档写得挺全,但实际用起来有些细节它不会告诉你。
这玩意儿到底解决了什么问题
先给没用过的兄弟简单说下背景。
以前我们用 GPT-4 返回 JSON,基本靠 Prompt 里写"请返回 JSON 格式",然后在代码里疯狂 try-catch。模型偶尔给你在前面加个 \\\`json,偶尔字段名自己发挥,偶尔把 boolean 返回成字符串 "true"。去年有一次半夜上线,就因为这个格式问题回滚了,记忆犹新。
Structured Outputs 本质上就是把"返回合法 JSON"这件事从"建议"变成了"强制"。你给它一个 JSON Schema,它保证输出的 JSON 完全符合这个 Schema,字段不少、类型不错、没有多余字段。
底层原理 OpenAI 没完全公开,但据我了解大概是把 Schema 约束注入到了 token 采样阶段,直接限制那些不符合 Schema 的 token 被选中。所以它不是事后校验,而是生成时就卡死了。嗯...这个其实挺狠的。
Schema 约束语法:能干啥不能干啥
支持的 JSON Schema 子集
OpenAI 用的不是完整的 JSON Schema 规范,是一个子集。具体来说:
支持的类型:object、string、number、boolean、array、null,以及 enum。
支持的约束:
- `type` 字段(必填)
- `properties` 定义对象字段
- `required` 数组标记必填字段
- `items` 定义数组元素类型
- `enum` 枚举值
- `description` 字段描述
- `additionalProperties: false` 禁止额外字段
不支持的:
- `oneOf`、`anyOf`、`allOf` 这些组合关键字
- `pattern` 正则校验
- `minLength`/`maxLength`、`minimum`/`maximum` 等数值约束
- `$ref` 引用
- 嵌套的 `anyOf` 之类的复杂逻辑
这个限制其实挺大的。比如你想让模型返回"要么是猫要么是狗,猫有 meow 字段,狗有 bark 字段",用 oneOf 是天经地义的做法,但 Structured Outputs 不支持。等等,这里我要更正一下——准确说不是完全不支持,2024 年 8 月之后 gpt-4o-2024-08-06 版本对部分 anyOf 场景有放宽,但 oneOf 的严格互斥逻辑还是不行,别抱太大希望。
一个能跑通的 Schema 长这样
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "用户姓名"
},
"age": {
"type": "number",
"description": "年龄"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "兴趣标签"
}
},
"required": ["name", "age"],
"additionalProperties": false
}注意 additionalProperties: false 一定要加,不然模型可能会自己加戏,返回你没定义的字段。这个坑我后面细说。
我踩过的三个坑
坑一:`additionalProperties: false` 不写就等着哭
第一次用的时候,我定义了一个只有 name 和 score 的对象,没加 additionalProperties: false。结果模型时不时给我返回一个 {"name": "张三", "score": 95, "comment": "表现优秀"}。
按理说 Structured Outputs 应该只返回我定义的字段对吧?但如果你不显式禁止额外字段,OpenAI 的约束并不会强制拦截。它只是"强烈建议"模型遵守 Schema,真正的硬约束只针对你声明了的字段的类型。
加上 additionalProperties: false 之后,多一个字段都给你报错,稳得很。这个设置我觉得应该默认开启才对。
坑二:`required` 字段在 function calling 和直接调用里行为不一样
这个坑我排查了一下午。
场景是我用 response_format 参数直接指定 JSON Schema 让模型返回结构化数据。Schema 里 required 写了 ["name", "age"],但模型有时候返回的 JSON 里没有 age。
后来发现,在非 function calling 模式下,required 的约束力没那么强。模型会尽量遵守,但如果它觉得信息不足,可能就省略了。我当时的日志里看到类似 "age": null 的情况都算好的,直接缺字段才让人崩溃。
解决方案:在 description 里明确写"如果无法确定,请返回 null 或空字符串",而不是指望 required 帮你硬卡。另外,如果你是在 function calling 场景下用,required 的约束会严格很多。大概是因为 function calling 模式下参数校验那层逻辑更完善吧。
坑三:数组元素类型只支持单一类型
有次我需要模型返回一个混合类型的数组,比如 [1, "hello", true] 这种。用 items 只能定义一种类型,不支持 anyOf 做多类型。
// 这样不行
{
"type": "array",
"items": {
"anyOf": [
{"type": "string"},
{"type": "number"}
]
}
}最后的 workaround 是把混合类型改成对象数组,每个对象里用不同字段承载不同类型的数据。丑是丑了点,但能跑。说实话写到这我有点怀念直接用 Pydantic 做校验的日子。
实际性能数据
我们测了 500 条客服对话的结构化提取任务,用的 gpt-4o-2024-11-20 版本,对比三种方案:
| 方案 | 格式合规率 | 字段准确率 | 平均延迟 |
|------|-----------|-----------|---------|
| 纯 Prompt 引导 | 87% | 91% | 1.2s |
| Prompt + 后处理校验重试 | 96% | 93% | 2.8s |
| Structured Outputs | 100% | 96% | 1.5s |
格式合规率直接 100%,这个没啥说的,毕竟硬约束。字段准确率提升主要来自类型强制——以前模型老把数字返回成字符串,现在不会了。
延迟方面,比纯 Prompt 略高一点,但远好于"校验失败重试"的方案。而且省了重试逻辑的代码,维护成本低很多。我记得那天重构完删了将近 200 行 try-catch 和 retry 逻辑,爽。
什么时候该用,什么时候别用
适合用的场景:
- 数据提取(从文本里抽结构化信息)
- 需要严格类型保证的 API 返回
- 多步骤 Agent 之间的数据传递
- 对格式错误零容忍的场景
不太适合的场景:
- 需要复杂条件逻辑的 Schema(`oneOf` 之类的)
- 输出本身就是自由文本,偶尔夹带 JSON
- 需要正则校验字符串格式(比如邮箱、手机号)
- 对延迟极度敏感的场景(多了一层约束处理,会慢一点)
嗯...这个其实还有个边界情况我没想清楚——如果你的 Schema 本身就很复杂,嵌套层级多,Structured Outputs 带来的延迟增加会不会抵消掉重试的成本?这个可能得具体场景具体测。
一些实用 tips
1. description 字段比你想象的更重要。Schema 约束只保证类型,不保证内容质量。想让模型返回高质量的值,description 里写清楚期望。我一般会写得很具体,比如"提取用户提到的产品名称,用中文,如果是英文品牌名保留原文"。
2. strict: true 参数别忘了。调用时在 response_format 里设置 "strict": true,约束会更严格。这个参数在 2024 年 10 月之后的 SDK 版本里才稳定,老版本可能有 bug。
3. enum 是个好东西。如果你知道某个字段只有几种可能值,用 enum 限制,比让模型自由发挥准确得多。
4. 调试时先不加 additionalProperties。先看看模型倾向返回哪些额外字段,这些往往是你 Schema 设计遗漏的信息。
5. 嵌套不要太深。虽然文档说支持嵌套,但超过 3 层的嵌套会让模型表现下降,个人经验。我们试过 5 层嵌套,准确率直接掉了 8 个百分点。
说实话,Structured Outputs 不是什么革命性技术,就是把以前我们需要用各种 trick 才能做到的事情标准化了。但正是这种"基础设施级"的改进,反而最实用。
你们在实际项目里用上了吗?有没有遇到什么奇怪的坑?特别是 oneOf 不支持这个问题,大家有什么优雅的 workaround 没?我最近在尝试用多个 enum 字段模拟互斥逻辑,效果还行但不够优雅。评论区聊聊。
#OpenAI #StructuredOutputs #JSONSchema #LLM #开发经验
读者评论 5