目录

LLM 结构化输出实战:用 JSON Schema 约束大模型返回可靠数据

为什么需要结构化输出

接入大模型后最头疼的问题:你让它"提取订单信息",它给你返回一段带语气词、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%+,成本只增加一次小请求。

实战经验总结

  1. 能用严格模式就别用自由 promptstrict: true + additionalProperties: false 是底线配置
  2. Schema 越小越稳:字段越少、类型越简单,模型出错率越低;长文本让模型总结成 5 个以内的键
  3. 枚举约束用 enum:固定取值(如状态字段 pending/success/failed)必须声明 enum,杜绝自由发挥
  4. 永远假设会失败:解析层用 Pydantic 强校验,配好重试与兜底,别让坏数据流进业务逻辑
  5. 兼容多厂商:上述接口 OpenAI 兼容协议通用,DeepSeek、Qwen、智谱等填各自 base_url 即可,同一套代码无缝切换

结构化输出是 LLM 从"玩具"走向"生产"的关键一步。把这套三件套(Schema 约束 + 强类型解析 + 失败重试)用到你的下一个 Agent 项目里,解析崩溃会从此消失。