Appearance
Structured Outputs
Structured Outputs 是 OpenAI API 提供的一项能力,它能保证模型的输出严格符合你定义的 JSON Schema。这解决了 LLM 应用开发中一个长期痛点:模型返回的 JSON 格式不可靠,导致下游解析频繁出错。
在实际开发中,我们经常需要模型以特定的 JSON 结构返回数据 —— 比如提取文章中的实体信息、对用户评论进行分类、或者生成前端组件需要的数据格式。传统的做法是在 Prompt 中描述期望的格式,但模型可能遗漏字段、搞错类型,甚至返回不合法的 JSON。Structured Outputs 通过在解码层面施加约束,从根本上消除了这些问题。
工作原理
ℹ️Schema 约束解码
Structured Outputs 的核心原理是约束解码(Constrained Decoding)。当你提供一个 JSON Schema 后,OpenAI 会在首次请求时将其编译为一个高效的状态机。在模型生成每个 token 时,这个状态机会屏蔽所有不符合 Schema 的 token 选择,确保最终输出 100% 匹配你的 Schema 定义。
这意味着:
- 每个必填字段一定会出现
- 字段类型一定正确(string 不会变成 number)
- 枚举值一定在你定义的范围内
- 不会出现多余的字段(当设置
additionalProperties: false)
这不是"尽力而为"的 Prompt 约束,而是数学意义上的保证。
使用方式
Structured Outputs 有两种使用方式:通过 text.format 参数直接约束文本输出格式,或者通过 Function Calling 的 Strict Mode。两种方式都能提供 100% 的 Schema 合规保证,区别在于使用场景不同。
方式一:response_format(文本输出约束)
当你希望模型直接返回结构化的文本数据时,使用 text.format 参数。适用于数据提取、内容分类、格式化输出等场景。
python
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-4.1",
input="列出三个历史事件",
text={
"format": {
"type": "json_schema",
"name": "historical_events",
"schema": {
"type": "object",
"properties": {
"events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"year": {"type": "integer"},
"significance": {"type": "string"}
},
"required": ["name", "year", "significance"],
"additionalProperties": False
}
}
},
"required": ["events"],
"additionalProperties": False
},
"strict": True
}
}
)
import json
events = json.loads(response.output_text)
for e in events["events"]:
print(f"{e['year']}年 - {e['name']}: {e['significance']}")javascript
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-4.1",
input: "列出三个历史事件",
text: {
format: {
type: "json_schema",
name: "historical_events",
schema: {
type: "object",
properties: {
events: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
year: { type: "integer" },
significance: { type: "string" }
},
required: ["name", "year", "significance"],
additionalProperties: false
}
}
},
required: ["events"],
additionalProperties: false
},
strict: true
}
}
});
const events = JSON.parse(response.output_text);
events.events.forEach(e => {
console.log(`${e.year}年 - ${e.name}: ${e.significance}`);
});在这个示例中,我们定义了一个包含 events 数组的 Schema,每个事件有 name、year 和 significance 三个字段。无论模型生成什么内容,输出一定会严格遵循这个结构。additionalProperties: false 确保不会出现意料之外的字段。
方式二:Function Calling Strict Mode
当你需要模型调用工具并生成结构化的调用参数时,在工具定义中设置 "strict": true 即可启用 Structured Outputs。这种方式在 Function Calling 章节中已有详细介绍。
python
from openai import OpenAI
client = OpenAI()
tools = [{
"type": "function",
"name": "create_event",
"description": "创建一个日历事件",
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "事件标题"},
"date": {"type": "string", "description": "日期,格式 YYYY-MM-DD"},
"duration_minutes": {"type": "integer", "description": "时长(分钟)"},
"participants": {
"type": "array",
"items": {"type": "string"},
"description": "参与者邮箱列表"
}
},
"required": ["title", "date", "duration_minutes", "participants"],
"additionalProperties": False
},
"strict": True
}]
response = client.responses.create(
model="gpt-4.1",
input="帮我创建一个明天下午的团队周会,时长1小时,参与者有 alice@co.com 和 bob@co.com",
tools=tools
)javascript
import OpenAI from "openai";
const client = new OpenAI();
const tools = [{
type: "function",
name: "create_event",
description: "创建一个日历事件",
parameters: {
type: "object",
properties: {
title: { type: "string", description: "事件标题" },
date: { type: "string", description: "日期,格式 YYYY-MM-DD" },
duration_minutes: { type: "integer", description: "时长(分钟)" },
participants: {
type: "array",
items: { type: "string" },
description: "参与者邮箱列表"
}
},
required: ["title", "date", "duration_minutes", "participants"],
additionalProperties: false
},
strict: true
}];
const response = await client.responses.create({
model: "gpt-4.1",
input: "帮我创建一个明天下午的团队周会,时长1小时,参与者有 alice@co.com 和 bob@co.com",
tools
});通过 "strict": true,模型生成的函数参数一定包含所有 required 字段,且类型完全匹配。你可以放心地直接解析参数而不需要编写大量的防御性代码。
Structured Outputs vs JSON Mode
在 Structured Outputs 推出之前,OpenAI 已经提供了 JSON Mode(text.format.type = "json_object")。两者都能让模型输出 JSON,但能力差异很大。
| 特性 | Structured Outputs | JSON Mode |
|---|---|---|
| Schema 保证 | 100% 符合定义的 Schema | 仅保证有效 JSON |
| Schema 定义 | 需要提供 JSON Schema | 不需要 |
| 适用场景 | 需要严格类型的应用 | 灵活的 JSON 输出 |
| 出错率 | 极低 | 可能结构不匹配 |
| 配置方式 | text.format.type = json_schema | text.format.type = json_object |
简单来说:如果你只需要"模型返回一个有效的 JSON",JSON Mode 足够了;如果你需要"模型返回的 JSON 严格符合特定结构",则必须使用 Structured Outputs。在生产环境中,我们强烈推荐使用 Structured Outputs,因为它能从根本上消除格式解析错误。
JSON Mode 的一个常见问题是,模型可能返回一个合法的 JSON,但结构与你预期的完全不同。例如你期望 {"name": "...", "age": 30},模型可能返回 {"user_name": "...", "user_age": "30"}。字段名不同、类型也变成了字符串 —— 这在 JSON Mode 下是"正确"的输出,但对你的代码来说是错误的。Structured Outputs 彻底解决了这个问题。
何时使用 Structured Outputs vs Function Calling
这两个功能有一定的重叠 —— 它们都能产出结构化数据。选择哪个取决于你的具体使用场景。
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 提取/分类数据 | Structured Outputs | 直接获取结构化文本输出 |
| 调用外部 API | Function Calling | 天然适合工具调用场景 |
| 多步骤工作流 | Function Calling | 支持多工具编排 |
| 数据库写入 | Structured Outputs | 严格 Schema 防止写入错误 |
| UI 渲染 | Structured Outputs | 输出直接绑定前端组件 |
一个实用的判断原则是:如果你需要模型"做某件事"(调用 API、执行操作),用 Function Calling;如果你只需要模型"按格式回答"(提取信息、生成结构化数据),用 Structured Outputs。
当然,这两种方式也可以组合使用。例如,你可以通过 Function Calling 让模型调用搜索 API 获取原始数据,然后在第二次请求中使用 Structured Outputs 让模型将搜索结果整理成固定格式。
支持的 JSON Schema 特性
Structured Outputs 支持 JSON Schema 的一个子集。了解哪些特性被支持、哪些不被支持,可以帮助你设计出正确的 Schema。
支持的特性
| 特性 | 说明 |
|---|---|
type | 支持 string、number、integer、boolean、array、object、null |
properties | 定义对象的字段 |
required | 指定必填字段(建议列出所有字段) |
additionalProperties | 必须设为 false |
items | 定义数组元素的类型 |
enum | 限制可选值范围 |
$ref / $defs | 引用和递归 Schema |
anyOf | 联合类型(Union Type) |
description | 字段描述(不影响约束,但帮助模型理解语义) |
不支持的特性
以下 JSON Schema 特性在 Structured Outputs 中不被支持,使用它们会导致 API 报错:
patternProperties— 正则匹配的属性名if / then / else— 条件 SchemaoneOf— 互斥联合类型(请用anyOf替代)not— 否定约束minLength / maxLength— 字符串长度约束minimum / maximum— 数值范围约束pattern— 正则表达式约束format— 格式约束(如email、uri)
这些约束需要在你的应用层自行实现。例如,如果你需要验证邮箱格式,可以在收到模型输出后用正则表达式检查。
重要规则
编写 Schema 时有几条必须遵守的规则:
- 所有字段必须设为
required。 如果某个字段是可选的,将其类型设为anyOf: [{"type": "string"}, {"type": "null"}],让模型在不需要时填入null。 - 必须设置
additionalProperties: false。 这在每一层object类型中都要设置,包括嵌套对象。 - 根级别必须是
object类型。 不能直接使用array或原始类型作为 Schema 的根。 - Schema 名称只能包含字母、数字和下划线。 不支持中文或特殊字符。
最佳实践
💡Structured Outputs 实践建议
以下建议可以帮助你更好地使用 Structured Outputs:
Schema 尽量扁平化。 避免过深的嵌套结构。如果 Schema 超过 3-4 层嵌套,考虑拆分成多次请求,或使用
$ref引用来组织结构。善用
description字段。 虽然description不参与格式约束,但它会影响模型填入的内容质量。在 description 中说明字段的含义、格式要求和示例值,能显著提高输出质量。在 Prompt 中配合说明期望的输出。 即使 Schema 已经定义了结构,在 Prompt 中简要说明你期望的输出风格(如"每个 significance 字段用一句话概括")仍然有助于提高内容质量。
缓存 Schema 编译结果。 首次使用某个 Schema 时,API 会有额外的编译延迟(通常几秒)。之后的请求会使用缓存的编译结果,延迟恢复正常。相同的 Schema 只会编译一次。
处理
refusal场景。 如果模型认为请求违反内容政策,它可能拒绝生成输出。此时返回的refusal字段会包含拒绝原因,而结构化内容为空。你的代码应该检查这一情况。控制数组长度。 JSON Schema 的
minItems/maxItems不被支持,但你可以在 Prompt 中指定数量要求(如"列出恰好 5 个事件"),模型通常能很好地遵循。
实际应用场景
Structured Outputs 在以下场景中特别有价值:
数据提取与标注。 从非结构化文本中提取结构化信息,例如从简历中提取教育经历、工作经验等字段。传统做法需要大量正则表达式和规则引擎,现在只需定义一个 Schema 就能可靠地完成。
内容分类与情感分析。 让模型对输入内容进行多维度分类,返回包含分类标签、置信度和理由的结构化结果。由于输出格式固定,可以直接存入数据库或传给下游系统处理。
API 响应生成。 当你的后端需要返回固定格式的 JSON 给前端时,Structured Outputs 可以确保模型生成的内容一定能被前端正确解析和渲染,不需要额外的格式转换层。
测试数据生成。 批量生成符合特定格式要求的测试数据。定义好 Schema 后,模型可以生成大量多样化但格式统一的测试数据,大幅提高测试覆盖率。