Skip to content

引言

2024 年 8 月 6 日,OpenAI 发布了一个看起来不起眼的 API 更新:Structured Outputs

没有发布会,没有直播,只有一篇技术博客。但这个更新解决了一个困扰开发者一年多的痛点——终于可以 100% 保证模型输出符合指定的 JSON Schema

为了让你理解这意味着什么,看看 OpenAI 官方的数据:

在之前,即使使用 JSON Mode + 详细的 prompt,模型输出与开发者指定 schema 的匹配率也只有约 85%。使用 Structured Outputs 后,匹配率达到 100%。

从 85% 到 100%,这 15 个百分点意味着什么?意味着你可以省掉所有解析错误处理代码、所有重试逻辑、所有正则表达式修复

2022GPT-4 发布,输出纯文本,格式不可控
2023.6JSON Mode 上线,保证合法 JSON 语法
2023-11Function Calling 引入 JSON Schema 约束(部分)
2024.8Structured Outputs 发布,100% schema 匹配
2024.8Claude 推出 tool_use 结构化输出方案
2024.10Gemini 支持 JSON Schema 约束输出

Structured Outputs 是什么?

一句话解释

你给模型一个 JSON Schema,模型保证输出的 JSON 严格符合这个 Schema——正确的字段名、正确的类型、正确的嵌套结构、正确的枚举值。

完整代码示例

python
from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()

# 方式 1:使用 Pydantic 模型定义 schema
class MovieReview(BaseModel):
    title: str
    year: int
    rating: float
    sentiment: str
    summary: str

response = client.beta.chat.completions.parse(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "评论电影《星际穿越》"}
    ],
    response_format=MovieReview,  # 直接传 Pydantic 模型
)

review = response.choices[0].message.parsed
print(f"电影: {review.title}")
print(f"年份: {review.year}")
print(f"评分: {review.rating}")
print(f"情感: {review.sentiment}")

方式 2:直接使用 JSON Schema

python
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "user", "content": "分析以下评论的情感"}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "sentiment_analysis",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "sentiment": {
                        "type": "string",
                        "enum": ["positive", "negative", "neutral"]
                    },
                    "confidence": {
                        "type": "number"
                    },
                    "keywords": {
                        "type": "array",
                        "items": {"type": "string"}
                    }
                },
                "required": ["sentiment", "confidence", "keywords"],
                "additionalProperties": False
            }
        }
    }
)

关键参数解释

Schema 中的关键字段:

  type: "json_schema"        ← 使用 schema 约束模式
  strict: true               ← 严格模式,100% 保证合规
  additionalProperties: false ← 不允许额外字段

  JSON Schema 的能力:
  ├── 字段名约束:必须叫 "sentiment",不能叫 "情感"
  ├── 类型约束:confidence 必须是 number
  ├── 枚举约束:sentiment 只能是三个值之一
  ├── 数组约束:keywords 是 string 数组
  ├── 嵌套约束:支持多层嵌套对象
  └── 必填约束:所有 required 字段必须存在

Constrained Decoding:背后的原理

为什么 Structured Outputs 能 100% 保证格式?

普通生成过程:
  每一步从所有 token 中选一个
  → 模型自由发挥 → 可能输出任何格式

Constrained Decoding:
  每一步只从「符合 schema 的 token」中选
  → 格式被硬约束 → 输出一定符合 schema

具体来说:
  要生成 {"sentiment": "positive"}
  Step 1: 只能选 {(其他 token 被屏蔽)
  Step 2: 只能选 "(JSON key 必须是字符串)
  Step 3-12: sentiment(schema 指定的 key)
  Step 13: 只能选 ":
  Step 14: 只能选 "(value 必须是字符串)
  Step 15-22: positive(enum 中的一项)
  ...以此类推

每一步的可选 token 被语法树约束,不可能输出非法 JSON。

和 JSON Mode 的区别

维度JSON ModeStructured Outputs
语法保证合法 JSON ✅合法 JSON ✅
Schema 保证❌ 不保证✅ 100% 保证
字段名可能变必须正确
值类型可能错必须正确
枚举约束
额外字段可能有不允许
输出可靠性~85%100%

其他提供商的方案

Claude 的结构化输出

python
# Claude 通过 tool_use 实现结构化输出
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=[{
        "name": "sentiment_analysis",
        "description": "分析文本情感",
        "input_schema": {       # ← JSON Schema
            "type": "object",
            "properties": {
                "sentiment": {
                    "type": "string",
                    "enum": ["positive", "negative", "neutral"]
                },
                "confidence": {"type": "number"}
            },
            "required": ["sentiment", "confidence"]
        }
    }],
    messages=[{"role": "user", "content": "分析:这家餐厅太好吃了!"}]
)

# 模型会生成一个 tool_use block,其中 input 就是结构化输出
for block in response.content:
    if block.type == "tool_use":
        result = block.input
        print(result)  # {"sentiment": "positive", "confidence": 0.9}

Gemini 的结构化输出

python
# Gemini 通过 response_mime_type 和 response_schema 实现
import google.generativeai as genai

# 定义 schema
response_schema = {
    "type": "object",
    "properties": {
        "sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]},
        "confidence": {"type": "number"}
    },
    "required": ["sentiment", "confidence"]
}

# 调用
model = genai.GenerativeModel(
    'gemini-2.5-pro',
    generation_config={
        "response_mime_type": "application/json",
        "response_schema": response_schema
    }
)

趋势:JSON Schema 成为通用标准

共同特点:
  OpenAI    → JSON Schema(response_format)
  Claude    → JSON Schema(tool_use 的 input_schema)
  Gemini    → JSON Schema(response_schema)

JSON Schema 正在成为大模型结构化输出的通用语言。

这意味着:
  ✓ 你写的 schema 可以跨模型复用
  ✓ 切换模型不需要重写格式定义
  ✓ 生态工具(如 Pydantic)可以通用

本节小结

概念要点
Structured Outputs2024.8 发布,100% 保证输出符合 JSON Schema
核心原理Constrained Decoding——在 token 生成时约束可选项
vs JSON ModeJSON Mode 保证语法,Structured Outputs 保证结构
通用标准JSON Schema 被所有主流 API 提供商采用
工程意义省掉所有解析错误处理代码,极大简化开发

思考题

  1. Structured Outputs 使用 Constrained Decoding 约束输出格式。这种约束会不会影响模型回答的准确性?为什么?
  2. 为什么 JSON Schema 成为了所有 API 提供商的共同选择,而不是各家定义自己的格式语言?
  3. 如果你想让模型输出一个「包含任意 key-value 的动态对象」,JSON Schema 还能约束吗?这会带来什么问题?