结构化输出控制:JSON Mode 与 Schema 约束(2026 年版)
AI 导读
结构化输出控制:JSON Mode 与 Schema 约束(2026 年版) 1. 引言 大语言模型最初被设计为"自由文本生成器",其输出是非结构化的自然语言。然而在工程实践中,我们几乎总是需要结构化的输出:JSON 对象、数据库记录、API 响应、表格数据。如何可靠地从 LLM 获得结构化输出,是 Prompt 工程中最关键的实用技能之一。 2024-2026...
结构化输出控制:JSON Mode 与 Schema 约束(2026 年版)
1. 引言
大语言模型最初被设计为"自由文本生成器",其输出是非结构化的自然语言。然而在工程实践中,我们几乎总是需要结构化的输出:JSON 对象、数据库记录、API 响应、表格数据。如何可靠地从 LLM 获得结构化输出,是 Prompt 工程中最关键的实用技能之一。
2024-2026 年间,主流模型提供商相继推出了原生的结构化输出支持:OpenAI 的 Structured Outputs(JSON Schema 约束)、Anthropic 的 Tool Use、Google 的 Response Schema。本文系统化梳理各种结构化输出技术的原理、用法和最佳实践。
2. 结构化输出的三个层次
层次 1: Prompt 约束(不可靠)
"请以 JSON 格式返回结果"
→ 模型可能返回额外文字、格式错误
层次 2: JSON Mode(基本可靠)
response_format: { type: "json_object" }
→ 保证返回有效 JSON,但不保证 Schema
层次 3: Schema 约束(完全可靠)
response_format: { type: "json_schema", schema: {...} }
→ 保证返回符合指定 Schema 的 JSON
| 层次 | 格式保证 | Schema 保证 | 需要后处理 | 适用模型 |
|---|---|---|---|---|
| Prompt 约束 | 不保证 | 不保证 | 需要解析和验证 | 所有模型 |
| JSON Mode | 有效 JSON | 不保证 | 需要 Schema 验证 | GPT-4o, Claude, Gemini |
| Schema 约束 | 有效 JSON | 保证 | 不需要 | GPT-4o (Structured Outputs) |
3. 各平台的结构化输出方案
3.1 OpenAI Structured Outputs
2024 年 8 月,OpenAI 推出了 Structured Outputs 功能,这是目前最完善的结构化输出方案。
3.1.1 基本用法
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
location: str | None = None
is_recurring: bool = False
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "提取日历事件信息。"},
{"role": "user", "content": "明天下午 3 点和张三、李四在会议室 A 开产品评审会"}
],
response_format=CalendarEvent
)
event = response.choices[0].message.parsed
print(event.name) # "产品评审会"
print(event.date) # "2026-03-01T15:00:00"
print(event.participants) # ["张三", "李四"]
print(event.location) # "会议室 A"
3.1.2 JSON Schema 直接定义
response = client.chat.completions.create(
model="gpt-4o",
messages=[...],
response_format={
"type": "json_schema",
"json_schema": {
"name": "calendar_event",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "事件名称"},
"date": {"type": "string", "description": "ISO 8601 格式"},
"participants": {
"type": "array",
"items": {"type": "string"}
},
"location": {
"type": ["string", "null"],
"description": "地点,如果未提及则为 null"
}
},
"required": ["name", "date", "participants", "location"],
"additionalProperties": False
}
}
}
)
3.1.3 Schema 约束规则
OpenAI Structured Outputs 的 strict: true 模式有以下限制:
| 规则 | 说明 |
|---|---|
additionalProperties: false |
必须显式声明所有属性 |
所有属性 required |
所有字段都必须在 required 中 |
可选字段用 null |
用 ["string", "null"] 而非省略 |
| 嵌套深度限制 | 最多 5 层嵌套 |
| 属性数量限制 | 最多 100 个属性 |
不支持 $ref |
不支持 JSON Schema 引用 |
| 枚举 | 支持 enum |
3.2 Anthropic Claude 的结构化输出
Claude 通过 Tool Use 机制实现结构化输出。
3.2.1 Tool Use 作为结构化输出
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=[{
"name": "extract_event",
"description": "从文本中提取日历事件信息",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "事件名称"},
"date": {"type": "string", "description": "ISO 8601 格式"},
"participants": {
"type": "array",
"items": {"type": "string"}
},
"location": {
"type": "string",
"description": "地点"
}
},
"required": ["name", "date", "participants"]
}
}],
tool_choice={"type": "tool", "name": "extract_event"},
messages=[{
"role": "user",
"content": "明天下午 3 点和张三、李四在会议室 A 开产品评审会"
}]
)
# 从 tool_use 内容块中提取结构化数据
for block in response.content:
if block.type == "tool_use":
event = block.input
print(event)
关键参数:
tool_choice: {"type": "tool", "name": "extract_event"}:强制模型调用指定工具tool_choice: {"type": "any"}:模型必须调用某个工具,但可以自行选择
3.2.2 Anthropic 的 JSON Mode
Claude 也支持 Prompt 级的 JSON 输出约束:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system="你是一个数据提取助手。始终以 JSON 格式返回结果。",
messages=[{
"role": "user",
"content": "提取事件信息并以 JSON 返回:明天下午3点开会"
}, {
"role": "assistant",
"content": "{" # 预填充 JSON 开头
}]
)
预填充技巧:在 assistant 消息中预填充 { 或 [ 可以引导 Claude 直接输出 JSON,减少前导文本。
3.3 Google Gemini 的结构化输出
3.3.1 Response Schema
import google.generativeai as genai
model = genai.GenerativeModel(
"gemini-2.0-flash",
generation_config=genai.GenerationConfig(
response_mime_type="application/json",
response_schema={
"type": "object",
"properties": {
"name": {"type": "string"},
"date": {"type": "string"},
"participants": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["name", "date", "participants"]
}
)
)
response = model.generate_content(
"提取事件:明天下午3点和张三、李四在会议室A开产品评审会"
)
3.3.2 Enum 约束
import enum
class Sentiment(enum.Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
NEUTRAL = "neutral"
model = genai.GenerativeModel(
"gemini-2.0-flash",
generation_config=genai.GenerationConfig(
response_mime_type="text/x.enum",
response_schema=Sentiment
)
)
response = model.generate_content("这个产品太棒了!")
# 保证输出是 "positive"、"negative" 或 "neutral" 之一
4. Function Calling 与结构化输出
4.1 Function Calling 的本质
Function Calling(函数调用)本质上是一种结构化输出机制:模型输出的不是自由文本,而是一个结构化的函数调用请求。
// 模型输出
{
"tool_calls": [{
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"上海\", \"unit\": \"celsius\"}"
}
}]
}
4.2 Function Calling vs JSON Schema
| 维度 | Function Calling | JSON Schema |
|---|---|---|
| 设计目的 | 调用外部工具 | 结构化数据输出 |
| 输出位置 | tool_calls 字段 |
content 字段 |
| 多个输出 | 支持并行调用多个函数 | 单一 JSON 对象 |
| Schema 严格性 | 可选 strict | 可选 strict |
| 适用场景 | 需要执行外部操作 | 纯数据提取/格式化 |
4.3 何时用 Function Calling 何时用 JSON Schema
需要模型触发外部操作?(搜索/计算/数据库查询)
├── 是 → Function Calling
└── 否 → 只需结构化数据输出
├── 需要 Schema 严格保证?
│ ├── 是 → JSON Schema(strict mode)
│ └── 否 → JSON Mode + 后处理验证
└── 需要从多个角度提取?
└── 是 → Function Calling(多工具并行)
5. Pydantic 与类型安全
5.1 Pydantic 模型定义
from pydantic import BaseModel, Field, validator
from typing import Optional, Literal
from datetime import datetime
class Address(BaseModel):
street: str = Field(description="街道地址")
city: str = Field(description="城市")
province: str = Field(description="省/直辖市")
postal_code: str = Field(description="邮编", pattern=r"^\d{6}$")
class OrderItem(BaseModel):
product_name: str = Field(description="商品名称")
quantity: int = Field(description="数量", ge=1)
unit_price: float = Field(description="单价(元)", ge=0)
@property
def subtotal(self) -> float:
return self.quantity * self.unit_price
class Order(BaseModel):
order_id: str = Field(description="订单号")
customer_name: str = Field(description="客户姓名")
items: list[OrderItem] = Field(description="订单项", min_length=1)
shipping_address: Address = Field(description="配送地址")
status: Literal["pending", "confirmed", "shipped", "delivered"]
notes: Optional[str] = Field(default=None, description="备注")
created_at: datetime = Field(description="创建时间")
@validator("order_id")
def validate_order_id(cls, v):
if not v.startswith("ORD-"):
raise ValueError("订单号必须以 ORD- 开头")
return v
5.2 Pydantic 生成 JSON Schema
schema = Order.model_json_schema()
print(json.dumps(schema, indent=2, ensure_ascii=False))
输出的 JSON Schema 可以直接用于 OpenAI Structured Outputs 或 Gemini Response Schema。
5.3 Instructor 库
Instructor 是一个轻量级库,简化了 Pydantic + LLM 的集成。
import instructor
from openai import OpenAI
from pydantic import BaseModel
client = instructor.from_openai(OpenAI())
class UserInfo(BaseModel):
name: str
age: int
email: str
user = client.chat.completions.create(
model="gpt-4o",
response_model=UserInfo,
messages=[{
"role": "user",
"content": "我叫张三,今年 28 岁,邮箱是 zhangsan@example.com"
}]
)
print(user.name) # "张三"
print(user.age) # 28
print(user.email) # "zhangsan@example.com"
Instructor 的优势:
- 自动重试:如果模型输出不符合 Schema,自动重试
- 验证反馈:将验证错误反馈给模型让其修正
- 多模型支持:OpenAI、Anthropic、Google、本地模型
6. 高级模式
6.1 嵌套结构与递归
class TreeNode(BaseModel):
"""组织架构中的节点"""
name: str
title: str
department: str
reports: list["TreeNode"] = Field(default_factory=list)
TreeNode.model_rebuild() # 支持递归引用
6.2 联合类型(Union Types)
from typing import Union, Literal
class TextBlock(BaseModel):
type: Literal["text"]
content: str
class CodeBlock(BaseModel):
type: Literal["code"]
language: str
content: str
class ImageBlock(BaseModel):
type: Literal["image"]
url: str
alt_text: str
class Document(BaseModel):
title: str
blocks: list[Union[TextBlock, CodeBlock, ImageBlock]]
6.3 流式结构化输出
OpenAI 支持流式返回结构化输出:
from openai import OpenAI
client = OpenAI()
with client.beta.chat.completions.stream(
model="gpt-4o",
messages=[...],
response_format=CalendarEvent,
) as stream:
for event in stream:
if event.type == "content.delta":
print(event.parsed) # 部分解析的 Pydantic 对象
final = stream.get_final_completion()
print(final.choices[0].message.parsed)
6.4 多步提取(Chain of Extraction)
对于复杂文档,分步提取比一次性提取更可靠。
# 步骤 1:提取文档的基础信息
class DocumentMeta(BaseModel):
title: str
author: str
date: str
document_type: Literal["report", "contract", "invoice", "letter"]
# 步骤 2:根据文档类型提取特定字段
class InvoiceDetails(BaseModel):
invoice_number: str
vendor: str
items: list[dict]
total_amount: float
tax_amount: float
due_date: str
class ContractDetails(BaseModel):
parties: list[str]
effective_date: str
termination_date: str
key_terms: list[str]
# 执行链
meta = extract(text, DocumentMeta)
if meta.document_type == "invoice":
details = extract(text, InvoiceDetails)
elif meta.document_type == "contract":
details = extract(text, ContractDetails)
7. 常见问题与解决方案
7.1 输出验证失败
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 字段缺失 | 输入中缺少对应信息 | 使用 Optional + 默认值 |
| 类型错误 | 模型返回错误类型 | 使用 strict mode |
| 枚举值错误 | 模型返回非枚举值 | 在 description 中列出所有选项 |
| 格式错误 | 日期/邮箱格式不对 | 在 description 中指定格式 |
| 数组为空 | 没有检测到符合条件的项 | 允许空数组或使用默认值 |
7.2 提高提取准确率的 Prompt 技巧
class ProductReview(BaseModel):
"""从电商评论中提取结构化信息。
重要规则:
- 如果信息在评论中未提及,对应字段设为 null
- 不要推测或编造不存在的信息
- 评分必须是 1-5 的整数
"""
product_name: str = Field(
description="商品名称。如果评论没有明确提到商品名,"
"使用 '未提及' 作为默认值。"
)
rating: int = Field(
description="用户评分,1-5 分。如果未提及,根据情感推断。"
"1=非常不满, 2=不满, 3=一般, 4=满意, 5=非常满意",
ge=1, le=5
)
pros: list[str] = Field(
description="用户提到的优点,每个优点一句话概括。"
"如果没有提到任何优点,返回空数组。",
default_factory=list
)
cons: list[str] = Field(
description="用户提到的缺点,每个缺点一句话概括。"
"如果没有提到任何缺点,返回空数组。",
default_factory=list
)
purchase_intent: Optional[bool] = Field(
default=None,
description="用户是否表达了回购意愿。"
"true=会回购, false=不会回购, null=未提及"
)
7.3 处理大量数据的策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 批量提取 | 一次提取多条记录 | 表格数据、列表 |
| 分页提取 | 分块处理长文档 | 长文档、多页PDF |
| 并行提取 | 多个请求并行发送 | 大量独立文档 |
| 增量提取 | 先粗提取,再细化 | 复杂嵌套结构 |
8. 性能与成本优化
8.1 Token 消耗对比
| 方法 | 额外输入 Token | 输出可靠性 | 成本影响 |
|---|---|---|---|
| Prompt 约束 | ~50 | 低 | 最小 |
| JSON Mode | ~10 | 中 | 小 |
| Structured Outputs | ~100-300 (Schema) | 高 | 中 |
| Function Calling | ~100-500 (Tool 定义) | 高 | 中-大 |
8.2 优化建议
| 建议 | 说明 |
|---|---|
| Schema 简化 | 只定义必要的字段,避免过度复杂 |
| Description 精简 | 字段描述简洁明了,不要写长段落 |
| 使用 Flash 模型 | 结构化提取用小模型足够 |
| 缓存 Schema | 避免每次请求都发送完整 Schema |
| 批量处理 | 合并多次小请求为一次大请求 |
9. 实战案例
9.1 简历解析
class Education(BaseModel):
school: str
degree: str
major: str
start_year: int
end_year: Optional[int] = None
class WorkExperience(BaseModel):
company: str
title: str
start_date: str
end_date: Optional[str] = None
responsibilities: list[str]
class Resume(BaseModel):
name: str
email: Optional[str] = None
phone: Optional[str] = None
summary: Optional[str] = None
education: list[Education]
work_experience: list[WorkExperience]
skills: list[str]
languages: list[str] = Field(default_factory=list)
9.2 发票信息提取
class InvoiceLineItem(BaseModel):
description: str
quantity: float
unit_price: float
amount: float
tax_rate: Optional[float] = None
class Invoice(BaseModel):
invoice_number: str
invoice_date: str
vendor_name: str
vendor_tax_id: Optional[str] = None
buyer_name: str
buyer_tax_id: Optional[str] = None
items: list[InvoiceLineItem]
subtotal: float
tax_total: float
total: float
currency: str = "CNY"
notes: Optional[str] = None
9.3 多语言内容分类
class ContentClassification(BaseModel):
primary_language: str = Field(
description="内容的主要语言代码 (zh/en/ja/ko/...)"
)
category: Literal[
"technology", "business", "health",
"entertainment", "sports", "politics",
"education", "science", "other"
]
subcategory: str
sentiment: Literal["positive", "negative", "neutral"]
key_entities: list[str] = Field(
description="文中提到的关键实体(人名/公司名/产品名)"
)
summary: str = Field(
description="一句话摘要(使用内容的原始语言)"
)
is_opinion: bool = Field(
description="是否为观点/评论(true)还是事实报道(false)"
)
10. 趋势与展望
10.1 Schema 约束的标准化
各模型提供商的 Schema 支持正在趋同,未来可能出现统一的标准接口。
10.2 动态 Schema
根据输入内容动态生成 Schema,而非使用固定 Schema。例如,根据文档类型自动选择合适的提取模板。
10.3 结构化推理
不仅控制输出格式,还控制推理过程的结构。例如,要求模型按照特定的推理模板输出中间步骤。
10.4 多模态结构化输出
从图像、音频中直接提取结构化数据(发票图片到 JSON、语音到日历事件等)。
11. 结论
结构化输出是 LLM 应用工程化的基石。选择合适的结构化输出方案需要考虑:
- 可靠性要求:严格场景用 Schema 约束(Structured Outputs),宽松场景用 JSON Mode
- 模型兼容性:跨模型用 Instructor 库做抽象
- Schema 设计:好的 Schema 定义 = 好的提取效果
- 成本平衡:Schema 带来的额外 token 成本 vs 后处理验证的工程成本
- 类型安全:用 Pydantic 确保端到端的类型安全
掌握结构化输出技术,意味着你可以将 LLM 从"聊天工具"转变为"数据处理引擎",这是构建生产级 AI 应用的必经之路。
Maurice | maurice_wen@proton.me