Structured Outputs 实战:让大模型稳定输出 JSON 的三种方案对比
让模型输出 JSON,说起来简单,实际上能把人逼疯。这篇文章讲清楚三种方案的差别,以及怎么通过 TheRouter 在多个模型上统一使用。
问题:大模型输出 JSON 有多不可靠?
你让模型"以 JSON 格式输出",它可能给你:
- 正确的 JSON —— 大多数时候
- JSON 前后加上 Markdown 代码块(` ```json … ````)—— 经常
- 字段名拼错,或者多了/少了字段 —— 偶尔
- 直接用自然语言回答,完全忘了 JSON 要求 —— 偶发
- 输出截断,JSON 不完整 —— 长内容时容易出现
这不是偶然,是模型的训练目标决定的——模型被训练成"有帮助地回答问题",而不是"严格按 schema 输出"。
解决方案有三个层次,稳定性依次递增。
方案 1:Prompt 工程
最简单的做法:在 prompt 里加一句"请以 JSON 格式输出"。
from openai import OpenAI
client = OpenAI(
api_key="tr-...",
base_url="https://api.therouter.ai/v1"
)
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4",
messages=[
{
"role": "system",
"content": "你是一个信息提取助手。请严格以 JSON 格式输出,不要包含任何其他内容。"
},
{
"role": "user",
"content": """从以下文章中提取关键信息,输出格式:
{
"title": "文章标题",
"author": "作者",
"summary": "100字以内摘要",
"tags": ["标签1", "标签2"]
}
文章内容:
苹果公司昨日发布了最新款 MacBook Pro,搭载 M4 Max 芯片,由首席执行官蒂姆·库克亲自发布...
"""
}
]
)
import json
try:
# 有时输出带代码块,需要清理
content = response.choices[0].message.content.strip()
if content.startswith("```"):
content = content.split("```")[1]
if content.startswith("json"):
content = content[4:]
result = json.loads(content)
print(result)
except json.JSONDecodeError as e:
print(f"解析失败: {e}")
print(f"原始输出: {response.choices[0].message.content}")
优点:零配置,所有模型都支持。
缺点:不稳定。复杂任务时模型容易"忘记"格式要求,长输出时 JSON 可能被截断,需要写额外的清理代码。
适合场景:原型验证、对格式要求不严格的内部工具。
方案 2:json_object 模式
OpenAI 引入了 response_format 参数,设置 type: "json_object" 后,模型保证输出合法 JSON(不会有代码块、不会截断)。
response = client.chat.completions.create(
model="openai/gpt-4o",
messages=[
{
"role": "system",
"content": "你是一个商品信息提取助手,以 JSON 格式输出提取结果。"
},
{
"role": "user",
"content": "提取以下商品信息:iPhone 16 Pro Max 256GB 黑色,原价 9999 元,现价 8999 元,库存 50 件。"
}
],
response_format={"type": "json_object"}
)
import json
result = json.loads(response.choices[0].message.content)
print(result)
# 输出一定是合法 JSON,但字段名和结构由模型决定
# {'name': 'iPhone 16 Pro Max', 'storage': '256GB', 'color': '黑色',
# 'original_price': 9999, 'current_price': 8999, 'stock': 50}
优点:输出一定是合法 JSON,不用写清理代码,json.loads() 不会抛异常。
缺点:字段结构不受控,字段名可能每次不同(比如 price vs current_price vs sale_price),字段可能缺失或多余,类型不保证(数字可能输出成字符串)。
注意:使用 json_object 时,system prompt 或 user message 里必须提到 “JSON”,否则部分模型会报错。
支持情况:GPT-4o、GPT-4.1、DeepSeek V3/R1 等支持,Claude 目前不支持此参数(通过 TheRouter 会自动 fallback 到 prompt 注入)。
方案 3:json_schema 模式(推荐)
这是 OpenAI 在 2024 年底推出的 Structured Outputs 功能,也是最可靠的方案。你提供精确的 JSON Schema,模型保证输出完全符合 schema 的结构。
response = client.chat.completions.create(
model="openai/gpt-4o",
messages=[
{
"role": "system",
"content": "你是一个文章信息提取助手。"
},
{
"role": "user",
"content": """提取以下文章的结构化信息:
标题:《2025年AI编程工具全景报告》
作者:张伟,来自字节跳动AI实验室
发布于:2025年3月15日
正文:随着Claude 3.7、GPT-4.1等新一代模型的发布,AI编程助手正在经历新一轮革命...
标签:AI、编程、工具、2025
"""
}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "article_info",
"strict": True,
"schema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "文章标题"
},
"author": {
"type": "string",
"description": "作者姓名"
},
"organization": {
"type": ["string", "null"],
"description": "作者所在机构,不明确时为 null"
},
"published_date": {
"type": "string",
"description": "发布日期,格式 YYYY-MM-DD"
},
"summary": {
"type": "string",
"description": "100字以内的文章摘要"
},
"tags": {
"type": "array",
"items": {"type": "string"},
"description": "文章标签列表"
}
},
"required": ["title", "author", "organization", "published_date", "summary", "tags"],
"additionalProperties": False
}
}
}
)
import json
result = json.loads(response.choices[0].message.content)
# 输出保证严格符合 schema
# {
# "title": "《2025年AI编程工具全景报告》",
# "author": "张伟",
# "organization": "字节跳动AI实验室",
# "published_date": "2025-03-15",
# "summary": "...",
# "tags": ["AI", "编程", "工具", "2025"]
# }
注意 "strict": True 和 "additionalProperties": False 是关键——这两个参数让模型不能输出 schema 之外的字段,也不能缺少 required 里的字段。
用 Pydantic 自动生成 JSON Schema
手写 JSON Schema 很繁琐,Pydantic 可以自动生成:
from pydantic import BaseModel, Field
from typing import Optional
import json
from openai import OpenAI
client = OpenAI(
api_key="tr-...",
base_url="https://api.therouter.ai/v1"
)
# 定义数据模型
class ProductInfo(BaseModel):
name: str = Field(description="商品名称")
brand: str = Field(description="品牌")
price: float = Field(description="价格,单位元")
original_price: Optional[float] = Field(None, description="原价,无折扣时为 null")
specs: dict[str, str] = Field(description="规格参数,如 {'颜色': '黑色', '存储': '256GB'}")
in_stock: bool = Field(description="是否有货")
class ArticleSummary(BaseModel):
title: str
key_points: list[str] = Field(description="3-5个核心要点")
sentiment: str = Field(description="情感倾向:positive/negative/neutral")
word_count_estimate: int = Field(description="预估原文字数")
def structured_extract(model_class: type[BaseModel], user_message: str, model: str = "openai/gpt-4o"):
"""通用结构化提取函数"""
schema = model_class.model_json_schema()
# Pydantic v2 的 schema 需要小调整以适配 OpenAI strict 模式
# additionalProperties 必须为 False
schema["additionalProperties"] = False
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": user_message}],
response_format={
"type": "json_schema",
"json_schema": {
"name": model_class.__name__,
"strict": True,
"schema": schema
}
}
)
return model_class.model_validate_json(response.choices[0].message.content)
# 提取商品信息
product = structured_extract(
ProductInfo,
"提取商品信息:AirPods Pro 第三代,苹果品牌,现价1799元原价1999元,黑色,耳机类型入耳式,有货。"
)
print(f"商品: {product.name}, 价格: {product.price}, 折扣: {product.original_price}")
# 提取文章摘要
summary = structured_extract(
ArticleSummary,
"分析这篇文章并提取信息:《DeepSeek R2 发布,性能超越 GPT-4.1》——DeepSeek 今日发布...",
model="deepseek/deepseek-v3" # 换个模型
)
print(f"要点: {summary.key_points}")
各模型 Structured Outputs 支持情况
通过 TheRouter 调用时,不同模型对 json_schema 模式的支持情况:
| 模型 | json_object | json_schema (strict) | 说明 |
|---|---|---|---|
openai/gpt-4o | ✅ | ✅ | 原生支持 |
openai/gpt-4.1 | ✅ | ✅ | 原生支持 |
openai/gpt-4o-mini | ✅ | ✅ | 原生支持 |
anthropic/claude-sonnet-4 | ⚠️ | ⚠️ | TheRouter 转换为 prompt 约束 |
anthropic/claude-opus-4 | ⚠️ | ⚠️ | TheRouter 转换为 prompt 约束 |
google/gemini-2.5-pro | ✅ | ✅ | 原生支持 |
deepseek/deepseek-v3 | ✅ | ✅ | 原生支持 |
deepseek/deepseek-r1 | ✅ | ⚠️ | 推理模型,strict 效果略差 |
说明:Claude 目前不原生支持
response_format参数,TheRouter 会将 schema 注入到 system prompt 中实现类似效果,稳定性略低于原生支持的模型。对 schema 严格性要求高的场景,建议优先选 GPT-4o 或 Gemini 2.5 Pro。
错误处理与 Fallback 策略
即使用了 json_schema 模式,生产环境仍需要完善的错误处理:
from pydantic import BaseModel, ValidationError
import json
import logging
logger = logging.getLogger(__name__)
def safe_structured_extract(
client,
model_class: type[BaseModel],
messages: list[dict],
model: str = "openai/gpt-4o",
fallback_model: str = "openai/gpt-4o-mini",
max_retries: int = 2
) -> BaseModel | None:
"""带重试和 fallback 的结构化提取"""
schema = model_class.model_json_schema()
schema["additionalProperties"] = False
for attempt in range(max_retries):
current_model = model if attempt == 0 else fallback_model
try:
response = client.chat.completions.create(
model=current_model,
messages=messages,
response_format={
"type": "json_schema",
"json_schema": {
"name": model_class.__name__,
"strict": True,
"schema": schema
}
}
)
# 检查 finish_reason,length 说明被截断
finish_reason = response.choices[0].finish_reason
if finish_reason == "length":
logger.warning(f"输出被截断 (model={current_model},attempt={attempt})")
continue
raw = response.choices[0].message.content
result = model_class.model_validate_json(raw)
return result
except json.JSONDecodeError as e:
logger.warning(f"JSON 解析失败 attempt={attempt}: {e}")
except ValidationError as e:
# Schema 校验失败——说明模型没有严格遵守 schema
logger.warning(f"Schema 校验失败 attempt={attempt}: {e}")
except Exception as e:
logger.error(f"请求失败 attempt={attempt}: {e}")
logger.error(f"所有重试均失败,model={model}")
return None
# 使用示例
class OrderInfo(BaseModel):
order_id: str
customer_name: str
items: list[str]
total_amount: float
status: str
result = safe_structured_extract(
client,
OrderInfo,
messages=[{
"role": "user",
"content": "提取订单信息:订单号 ORD-2025-001,客户张三,购买了MacBook Pro和AirPods,总计15798元,已发货。"
}]
)
if result:
print(f"订单 {result.order_id},客户 {result.customer_name},金额 {result.total_amount}")
else:
print("提取失败,需要人工处理")
实际场景:API 响应标准化
一个实际用途:让大模型把非结构化的用户反馈转成标准化的结构,便于下游处理:
from pydantic import BaseModel
from typing import Literal
from enum import Enum
class FeedbackCategory(str, Enum):
BUG = "bug"
FEATURE_REQUEST = "feature_request"
COMPLAINT = "complaint"
PRAISE = "praise"
QUESTION = "question"
class ParsedFeedback(BaseModel):
category: FeedbackCategory
priority: Literal["low", "medium", "high", "critical"]
product_area: str
summary: str
action_required: bool
suggested_assignee: Literal["engineering", "product", "support", "none"]
def parse_user_feedback(feedback_text: str) -> ParsedFeedback:
schema = ParsedFeedback.model_json_schema()
schema["additionalProperties"] = False
response = client.chat.completions.create(
model="openai/gpt-4o",
messages=[
{
"role": "system",
"content": "你是一个客服工单分类助手,将用户反馈分类为标准化格式。"
},
{"role": "user", "content": f"分类以下用户反馈:\n\n{feedback_text}"}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "ParsedFeedback",
"strict": True,
"schema": schema
}
}
)
return ParsedFeedback.model_validate_json(response.choices[0].message.content)
# 测试
feedback = parse_user_feedback(
"我的账单页面一直报错500,已经持续两天了,严重影响我的业务运营,急需处理!!"
)
print(f"类别: {feedback.category.value}") # bug
print(f"优先级: {feedback.priority}") # critical
print(f"需要行动: {feedback.action_required}") # True
print(f"分配给: {feedback.suggested_assignee}") # engineering
三种方案对比总结
| 维度 | Prompt 工程 | json_object | json_schema (strict) |
|---|---|---|---|
| 配置复杂度 | 最低 | 低 | 中 |
| 输出稳定性 | 低 | 中 | 高 |
| 字段结构保证 | 无 | 无 | 严格保证 |
| 类型安全 | 无 | 无 | 保证 |
| 模型兼容性 | 全部 | 大部分 | GPT/Gemini/DeepSeek |
| 适用场景 | 原型/内部工具 | 简单集成 | 生产环境 |
生产环境的建议:用 Pydantic 定义数据模型 + json_schema strict 模式 + 错误重试,对于不支持原生 json_schema 的模型(如 Claude),通过 TheRouter 的 prompt 转换也能达到不错的稳定性。
需要在多个模型之间切换时,TheRouter 的统一接口让你不用为每个模型写不同的适配代码——同一套 Pydantic 模型,同一个 API 调用,换个 model 参数就行。
- 注册地址:therouter.ai
- API Base:
https://api.therouter.ai/v1 - 支持国内直连,不需要代理
更多推荐


所有评论(0)