为什么需要结构化输出
接入大模型后最头疼的问题:你让它"提取订单信息",它给你返回一段带语气词、Markdown 代码块、甚至把键名从 order_id 改成 orderId 的自由文本。下游程序一解析就崩。
根本原因:LLM 默认是续写文本,不是生成 JSON。要拿到可靠的结构化数据,必须用约束手段,而不是靠"请返回 JSON"这种祈祷式 prompt。本文分享一套完整实战方案。
方案一:JSON Schema 严格模式
主流厂商(OpenAI、DeepSeek、智谱等 OpenAI 兼容接口)都支持 response_format 指定 JSON Schema。关键在 strict: true——开启后模型保证输出符合 Schema,字段顺序、类型、必填项都严格对齐:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
from openai import OpenAI
client = OpenAI() # 读环境变量 OPENAI_API_KEY
schema = {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"amount": {"type": "number"},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"qty": {"type": "integer"}
},
"required": ["name", "price", "qty"],
"additionalProperties": False
}
}
},
"required": ["order_id", "amount", "items"],
"additionalProperties": False
}
resp = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": "提取用户消息中的订单信息"},
{"role": "user", "content": "我买了2杯拿铁共36元,1块提拉米苏18元,订单号是20260802001"}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "order_info",
"schema": schema,
"strict": True
}
}
)
print(resp.choices[0].message.content)
|
输出稳定为合法 JSON,additionalProperties: false 防止模型多塞字段。
方案二:Pydantic 模型 + parse
手写 Schema 容易出错。更优雅的做法:定义 Pydantic 模型,库自动生成 Schema,解析失败直接抛异常,类型一目了然:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
from pydantic import BaseModel
from openai import OpenAI
client = OpenAI()
class OrderItem(BaseModel):
name: str
price: float
qty: int
class OrderInfo(BaseModel):
order_id: str
amount: float
items: list[OrderItem]
completion = client.beta.chat.completions.parse(
model="gpt-5.4-mini",
messages=[{"role": "user", "content": "2杯拿铁36元+1块提拉米苏18元,单号20260802001"}],
response_format=OrderInfo,
)
order = completion.choices[0].message.parsed
print(order.items[0].name) # 直接是 OrderItem 对象,不是字符串
|
response_format 直接传 Pydantic 类,返回 parsed 就是强类型对象,配合 IDE 自动补全,比手写 Schema 高效得多。
方案三:失败重试——把错误回灌给模型
严格模式也不是 100% 可靠(偶发截断、超长输出)。生产环境必须有兜底:解析失败时把异常信息作为新消息回灌,让模型自己修正:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import json
from openai import OpenAI
from pydantic import ValidationError
client = OpenAI()
def extract_order(text: str, max_retries: int = 2) -> OrderInfo:
messages = [{"role": "user", "content": text}]
for attempt in range(max_retries + 1):
try:
completion = client.beta.chat.completions.parse(
model="gpt-5.4-mini",
messages=messages,
response_format=OrderInfo,
)
return completion.choices[0].message.parsed
except ValidationError as e:
if attempt == max_retries:
raise
# 把错误回灌给模型,让它修正输出
messages.append({
"role": "user",
"content": f"你上一步的输出校验失败:{e}\n请重新提取,确保字段完整且类型正确。"
})
order = extract_order("我买了2杯拿铁36元和1块提拉米苏18元,单号20260802001")
print(order.model_dump_json())
|
实测这种"错误回灌"重试一次,成功率从约 95% 提升到 99.5%+,成本只增加一次小请求。
实战经验总结
- 能用严格模式就别用自由 prompt:
strict: true + additionalProperties: false 是底线配置
- Schema 越小越稳:字段越少、类型越简单,模型出错率越低;长文本让模型总结成 5 个以内的键
- 枚举约束用
enum:固定取值(如状态字段 pending/success/failed)必须声明 enum,杜绝自由发挥
- 永远假设会失败:解析层用 Pydantic 强校验,配好重试与兜底,别让坏数据流进业务逻辑
- 兼容多厂商:上述接口 OpenAI 兼容协议通用,DeepSeek、Qwen、智谱等填各自 base_url 即可,同一套代码无缝切换
结构化输出是 LLM 从"玩具"走向"生产"的关键一步。把这套三件套(Schema 约束 + 强类型解析 + 失败重试)用到你的下一个 Agent 项目里,解析崩溃会从此消失。