第三章 结构化输出:让大模型只说你要的格式
第三章 结构化输出:让大模型只说你要的格式
“让模型自由发挥是艺术,让模型按格式输出是工程。”
2026 年 4 月的一份行业调研显示,在所有接入大模型 API 的国内应用中,超过 60% 的线上故障与"输出格式不符合预期"直接相关。模型返回了一段自然语言文本,但下游代码期望的是一个 JSON 对象——json.loads() 一调用,直接抛异常,整个链路崩掉。
这不是模型的问题。是你没告诉它用什么格式说话。
上一章讲的系统提示词可以"建议"模型按某种格式输出,但那只是建议——模型可能听,也可能不听。这一章要解决的是:如何强制模型按你定义的 Schema 输出数据,不是靠"请求",而是靠技术手段。
3.1 从自由文本到结构化数据
先看一个对比。假设你在做一个商品信息提取系统,用户输入一段商品描述,你需要提取出结构化信息。
不用结构化输出的结果(模型随心所欲地回复):
这个商品是一款蓝牙耳机,品牌是漫步者,型号LolliPods Pro 2,
售价299元,支持主动降噪,续航约30小时。
用了结构化输出的结果(严格按 Schema):
{
"product_name": "LolliPods Pro 2",
"brand": "漫步者",
"price": 299,
"currency": "CNY",
"features": ["主动降噪", "蓝牙连接"],
"battery_hours": 30
}
哪个能被下游代码直接使用?显然是后者。前者还需要你写一堆正则或者再调一次模型来解析——这就多了一次不确定性。
3.2 三种主流结构化输出方案
目前主流的结构化输出技术有三种,适用场景各不相同:
| 方案 | 原理 | 可靠性 | 适用场景 |
|---|---|---|---|
| Prompt 引导 | 在提示词中写明 JSON 格式要求 | 中(偶尔格式错误) | 简单场景、快速原型 |
| JSON Mode / Response Format | API 参数强制 JSON 输出 | 高(格式保证,内容不保证) | 需要 JSON 但字段灵活 |
| Function Calling / Tool Use | 定义函数签名,模型填充参数 | 高(格式和字段类型都保证) | 需要严格 Schema |
下面分别说每种怎么用。
3.3 方案一:Prompt 引导(最简单但最不可靠)
在系统提示词或用户消息中直接写明期望的 JSON 格式:
import json
from openai import OpenAI # 这里用 DeepSeek 兼容接口
client = OpenAI(
api_key="your-deepseek-api-key",
base_url="https://api.deepseek.com"
)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": """你是一个商品信息提取器。
请从用户提供的文本中提取商品信息,必须以 JSON 格式返回:
{
"product_name": "商品名",
"brand": "品牌",
"price": 数字,
"features": ["特性1", "特性2"]
}
只返回 JSON,不要有任何其他文字。"""},
{"role": "user", "content": "漫步者LolliPods Pro 2蓝牙耳机,299元,支持主动降噪"}
]
)
result = json.loads(response.choices[0].message.content)
这个方案的问题是:模型大概率会返回正确的 JSON,但偶尔会加上 ```json 的标记、多一段解释文字、或者字段名拼写不一致。在生产环境里,“大概率"不够,你需要"一定”。
3.4 方案二:JSON Mode(格式保证)
DeepSeek、通义千问等国内大模型 API 都支持 JSON Mode,通过 API 参数强制模型输出合法的 JSON:
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "提取商品信息,返回JSON格式"},
{"role": "user", "content": "漫步者LolliPods Pro 2蓝牙耳机,299元,降噪"}
],
response_format={"type": "json_object"}
)
# 这里 json.loads() 不会报错——API 保证了 JSON 格式
data = json.loads(response.choices[0].message.content)
JSON Mode 保证了语法正确(一定是合法 JSON),但不保证语义正确(字段名可能不是你要的、字段可能缺失)。这时候就需要下一步:Schema 验证。
3.5 Pydantic:Python 世界的 Schema 卫士
Pydantic 是 Python 生态中最流行的数据验证库。它可以把"模型应该输出什么结构"定义成一个 Python 类,然后自动验证模型输出是否符合要求:
from pydantic import BaseModel, Field
from typing import List, Optional
class ProductInfo(BaseModel):
product_name: str = Field(description="商品名称")
brand: str = Field(description="品牌名")
price: float = Field(ge=0, description="价格,必须大于等于0")
currency: str = Field(default="CNY", description="货币")
features: List[str] = Field(description="商品特性列表")
battery_hours: Optional[float] = Field(
default=None, description="电池续航小时数"
)
# 验证模型输出
raw_output = json.loads(response.choices[0].message.content)
try:
product = ProductInfo(**raw_output)
print(f"验证通过:{product.product_name}, ¥{product.price}")
except ValidationError as e:
print(f"输出不符合 Schema:{e}")
# 这里可以触发重试逻辑
Pydantic 帮你做了三件事:
- 类型检查:price 是不是数字?features 是不是列表?
- 约束验证:price 是不是大于 0?
- 默认值填充:如果模型没返回 currency,自动填 “CNY”
3.6 方案三:Function Calling(最严格的约束)
Function Calling(也叫 Tool Use)是当前最可靠的结构化输出方案。你定义一个"函数签名",模型的任务是填充函数参数——这比"请输出 JSON"的约束力强很多。
tools = [
{
"type": "function",
"function": {
"name": "extract_product_info",
"description": "从文本中提取商品信息",
"parameters": {
"type": "object",
"properties": {
"product_name": {
"type": "string",
"description": "商品名称"
},
"brand": {
"type": "string",
"description": "品牌名"
},
"price": {
"type": "number",
"description": "价格"
},
"features": {
"type": "array",
"items": {"type": "string"},
"description": "商品特性列表"
}
},
"required": ["product_name", "brand", "price"]
}
}
}
]
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "漫步者LolliPods Pro 2,299元"}],
tools=tools,
tool_choice={"type": "function", "function": {"name": "extract_product_info"}}
)
# 模型返回的是结构化的函数调用参数
args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
product = ProductInfo(**args) # 再用 Pydantic 做二次验证
Function Calling 的核心优势是模型知道自己在"填表"而不是"写作",这从认知模式上就减少了格式错误的概率。
3.7 重试机制:当格式还是错了
即使用了 JSON Mode + Function Calling + Pydantic,偶尔还是会出现验证失败的情况。这时候需要一个重试机制:
def extract_with_retry(text: str, max_retries: int = 3) -> ProductInfo:
"""带重试的结构化提取"""
last_error = None
for attempt in range(max_retries):
try:
response = call_llm(text, tools=tools)
args = parse_tool_call(response)
return ProductInfo(**args)
except (json.JSONDecodeError, ValidationError) as e:
last_error = e
# 把错误信息反馈给模型,让它修正
text = f"""上次输出有误:{str(e)}
请重新从以下文本提取信息:{text}"""
raise RuntimeError(f"重试 {max_retries} 次仍然失败:{last_error}")
关键技巧:重试时把错误信息告诉模型。模型看到"price 应该是数字但你给了字符串"这样的反馈,第二次大概率就能修正。
3.8 实战决策树
面对一个新场景,怎么选择结构化输出方案?
输出需要结构化吗?
├── 不需要 → 自由文本即可
└── 需要
├── 字段固定、类型严格?
│ ├── 是 → Function Calling + Pydantic 验证
│ └── 否 → JSON Mode + 宽松解析
└── 输出可能很长(>2000 tokens)?
├── 是 → 分段提取 + 合并
└── 否 → 单次调用即可
一个务实的建议:先用 Prompt 引导做原型验证想法,确认可行后切换到 Function Calling + Pydantic 做生产版本。不要一上来就搞最复杂的方案。
扩展阅读
如果你想在本章主题上走得更深,推荐以下资源:
- 📖 《Pydantic V2 官方文档》— 数据验证的标准参考,理解 Field、Validator、自定义类型
- 📖 《Building LLM Apps》(Valentino Gagliardi)— 第 5 章系统讲解了 Function Calling 的工程实践
- 🛠️ Instructor 库(jxnl/instructor)— 基于 Pydantic 的结构化输出库,支持自动重试和流式输出
本章小结
本章的核心是:结构化输出是把大模型从"随心所欲的作家"变成"严格按规格交付的工程师"的关键一步。
你在本章学到了:
- 三种结构化输出方案的原理和适用场景:Prompt 引导、JSON Mode、Function Calling
- Pydantic 是 Python 中做 Schema 验证的标准选择,能在模型输出后做二次把关
- 重试机制要把错误信息反馈给模型,而不是简单地重跑同样的请求
- 选择方案的决策树:从场景出发,不要过度设计
下一步行动:在接下来的 24 小时内,把你项目中一个用自由文本输出的 LLM 调用改成 Function Calling + Pydantic 验证,跑 20 次看通过率。
结构化输出解决了"格式"问题,但如果你需要更复杂的约束——比如"输出中不能包含竞争对手名称"或"必须引用知识库中的内容"——就需要更重的武器了。下一章,我们来看 Guardrails 框架,给 AI 装上真正的护栏。
本章末:4 个立刻可以用的 DeepSeek 提示词
提示词 1:为业务场景设计 Pydantic Schema
我的业务场景是 [描述场景,如:从用户评论中提取情感和关键词]。
请帮我设计一个 Pydantic V2 的 BaseModel 类,包含:
1. 所有需要提取的字段(合理的类型和约束)
2. 每个字段的 Field description
3. 必要的自定义 validator
4. 一个使用示例
输出完整的 Python 代码。
使用场景:快速为新的结构化提取任务设计数据模型。
提示词 2:生成 Function Calling 的 Tool 定义
我需要一个 Function Calling 的工具定义,功能是 [描述功能]。
输入参数包括:
[列出参数名、类型、是否必填、说明]
请生成符合 OpenAI/DeepSeek API 格式的 tools JSON 定义,
并附一段调用示例代码(Python)。
使用场景:从业务需求快速生成标准的 Tool 定义。
提示词 3:调试结构化输出错误
我用 Function Calling 提取信息,但遇到了以下错误:
Schema 定义:
[粘贴你的 Pydantic 或 JSON Schema]
模型实际输出:
[粘贴模型的原始输出]
错误信息:
[粘贴 ValidationError 或 JSONDecodeError]
请分析错误原因,并给出:
1. 修复 Schema 的建议(如果是 Schema 问题)
2. 修复 Prompt 的建议(如果是引导不足)
3. 添加兜底逻辑的代码
使用场景:结构化输出调试不通时的诊断工具。
提示词 4:批量测试结构化输出的稳定性
以下是我的结构化输出 Schema:
[粘贴 Schema]
请帮我生成 10 个不同复杂度的测试输入文本:
- 3 个简单文本(信息完整、格式清晰)
- 3 个中等文本(部分信息缺失或模糊)
- 2 个复杂文本(信息散乱、有干扰信息)
- 2 个边界文本(极短/极长/多语言混合)
每个测试输入附带期望的输出 JSON。
使用场景:系统上线前,批量测试结构化输出的鲁棒性。