Skip to content

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,每个事件有 nameyearsignificance 三个字段。无论模型生成什么内容,输出一定会严格遵循这个结构。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_schematext.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直接获取结构化文本输出
调用外部 APIFunction 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支持 stringnumberintegerbooleanarrayobjectnull
properties定义对象的字段
required指定必填字段(建议列出所有字段)
additionalProperties必须设为 false
items定义数组元素的类型
enum限制可选值范围
$ref / $defs引用和递归 Schema
anyOf联合类型(Union Type)
description字段描述(不影响约束,但帮助模型理解语义)

不支持的特性

以下 JSON Schema 特性在 Structured Outputs 中不被支持,使用它们会导致 API 报错:

  • patternProperties — 正则匹配的属性名
  • if / then / else — 条件 Schema
  • oneOf — 互斥联合类型(请用 anyOf 替代)
  • not — 否定约束
  • minLength / maxLength — 字符串长度约束
  • minimum / maximum — 数值范围约束
  • pattern — 正则表达式约束
  • format — 格式约束(如 emailuri

这些约束需要在你的应用层自行实现。例如,如果你需要验证邮箱格式,可以在收到模型输出后用正则表达式检查。

重要规则

编写 Schema 时有几条必须遵守的规则:

  1. 所有字段必须设为 required 如果某个字段是可选的,将其类型设为 anyOf: [{"type": "string"}, {"type": "null"}],让模型在不需要时填入 null
  2. 必须设置 additionalProperties: false 这在每一层 object 类型中都要设置,包括嵌套对象。
  3. 根级别必须是 object 类型。 不能直接使用 array 或原始类型作为 Schema 的根。
  4. Schema 名称只能包含字母、数字和下划线。 不支持中文或特殊字符。

最佳实践

💡Structured Outputs 实践建议

以下建议可以帮助你更好地使用 Structured Outputs:

  1. Schema 尽量扁平化。 避免过深的嵌套结构。如果 Schema 超过 3-4 层嵌套,考虑拆分成多次请求,或使用 $ref 引用来组织结构。

  2. 善用 description 字段。 虽然 description 不参与格式约束,但它会影响模型填入的内容质量。在 description 中说明字段的含义、格式要求和示例值,能显著提高输出质量。

  3. 在 Prompt 中配合说明期望的输出。 即使 Schema 已经定义了结构,在 Prompt 中简要说明你期望的输出风格(如"每个 significance 字段用一句话概括")仍然有助于提高内容质量。

  4. 缓存 Schema 编译结果。 首次使用某个 Schema 时,API 会有额外的编译延迟(通常几秒)。之后的请求会使用缓存的编译结果,延迟恢复正常。相同的 Schema 只会编译一次。

  5. 处理 refusal 场景。 如果模型认为请求违反内容政策,它可能拒绝生成输出。此时返回的 refusal 字段会包含拒绝原因,而结构化内容为空。你的代码应该检查这一情况。

  6. 控制数组长度。 JSON Schema 的 minItems / maxItems 不被支持,但你可以在 Prompt 中指定数量要求(如"列出恰好 5 个事件"),模型通常能很好地遵循。

实际应用场景

Structured Outputs 在以下场景中特别有价值:

数据提取与标注。 从非结构化文本中提取结构化信息,例如从简历中提取教育经历、工作经验等字段。传统做法需要大量正则表达式和规则引擎,现在只需定义一个 Schema 就能可靠地完成。

内容分类与情感分析。 让模型对输入内容进行多维度分类,返回包含分类标签、置信度和理由的结构化结果。由于输出格式固定,可以直接存入数据库或传给下游系统处理。

API 响应生成。 当你的后端需要返回固定格式的 JSON 给前端时,Structured Outputs 可以确保模型生成的内容一定能被前端正确解析和渲染,不需要额外的格式转换层。

测试数据生成。 批量生成符合特定格式要求的测试数据。定义好 Schema 后,模型可以生成大量多样化但格式统一的测试数据,大幅提高测试覆盖率。