为什么结构化输出如此重要?
在将 AI 集成到应用程序时,最大的挑战之一是输出的不确定性——同样的提示词,模型可能在不同时候输出略有差异的格式,导致下游解析失败。结构化输出技术解决了这个问题,让 AI 输出可以像数据库查询结果一样可靠、可解析。
三种结构化输出方式
- API 级别:OpenAI JSON mode、Function Calling、Structured Outputs(最可靠)
- 提示词级别:在 prompt 中明确指定格式,附带 Few-shot 示例(较可靠)
- 后处理级别:用正则或解析器从自由文本中提取结构(兜底方案)
OpenAI Structured Outputs
最可靠的 JSON 输出方式
OpenAI 于 2024 年推出 Structured Outputs,通过 JSON Schema 定义输出结构,API 层面保证输出符合 Schema,不再只是"建议"。
from openai import OpenAI
from pydantic import BaseModel
from typing import List, Optional
client = OpenAI()
# 用 Pydantic 定义输出结构
class ReviewAnalysis(BaseModel):
sentiment: str # "positive" | "negative" | "mixed"
score: float # 1.0 - 5.0
pros: List[str] # 优点列表
cons: List[str] # 缺点列表
key_issues: Optional[List[str]] # 关键问题
summary: str # 一句话总结
# 使用 parse 方法直接获得类型化结果
completion = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "分析用户评论的情感和关键信息。"},
{"role": "user", "content": "产品质量很好,但物流太慢了,等了两周!"}
],
response_format=ReviewAnalysis
)
# 直接访问类型化字段,无需手动解析
analysis = completion.choices[0].message.parsed
print(analysis.sentiment) # "mixed"
print(analysis.score) # 3.0
print(analysis.pros) # ["产品质量很好"]
提示词级别的 JSON 控制
当无法使用原生 API 特性时
分析以下用户评论,以严格的 JSON 格式返回,
不要在 JSON 前后添加任何说明文字。
评论内容:
---
「界面很漂亮,操作也流畅。就是有时候会闪退,
客服响应很慢。总体还可以,会继续使用。」
---
返回格式(严格遵守,不要添加注释):
{
"overall_sentiment": "positive|negative|mixed",
"rating": 1-5,
"aspects": {
"ui": {"sentiment": "positive|negative|neutral", "mentions": ["提到的具体内容"]},
"performance": {"sentiment": "positive|negative|neutral", "mentions": []},
"support": {"sentiment": "positive|negative|neutral", "mentions": []}
},
"would_continue": true|false,
"summary": "一句话总结(20字以内)"
}
Markdown 格式控制
结构化报告生成
以下是某 SaaS 产品的关键指标数据:
MAU: 50,000;NPS: 42;Churn Rate: 3.2%;ARPU: $28
请生成一份简洁的月度产品健康度报告,严格按以下 Markdown 格式:
# 产品月度健康报告 - [月份]
## 核心指标总览
| 指标 | 数值 | 环比 | 评级 |
|------|------|------|------|
| MAU | ... | ... | 🟢/🟡/🔴 |
## 亮点分析
> [1-2句核心亮点]
### 增长驱动因素
1. **[因素1]**:[分析]
2. **[因素2]**:[分析]
## 风险提示
- ⚠️ [风险1]:[建议]
- ⚠️ [风险2]:[建议]
## 下月重点
- [ ] [行动项1]
- [ ] [行动项2]
XML 标签法
Claude 特别推荐的结构化方式
Claude 对 XML 标签的响应特别稳定,Anthropic 官方推荐在复杂任务中使用 XML 标签组织提示词结构:
<task>
你是代码审查工具,分析提供的 Python 代码。
</task>
<code_to_review>
def calculate_bmi(weight, height):
return weight / height ** 2
</code_to_review>
<review_criteria>
- 输入验证
- 错误处理
- 代码文档
- 边界情况
</review_criteria>
<output_format>
以 XML 格式返回:
<review>
<issues>
<issue severity="high|medium|low">
<description>问题描述</description>
<fix>修复建议</fix>
</issue>
</issues>
<improved_code>改进后的完整代码</improved_code>
</output_format>
输出验证与解析
健壮的 JSON 解析策略
import json
import re
from typing import Optional, Dict, Any
def robust_json_parse(text: str) -> Optional[Dict[str, Any]]:
"""健壮的 JSON 解析,处理 LLM 常见的输出问题"""
# 策略1:直接解析
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# 策略2:提取 ```json...``` 代码块
code_block = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text)
if code_block:
try:
return json.loads(code_block.group(1))
except json.JSONDecodeError:
pass
# 策略3:找第一个 { 到最后一个 } 的内容
json_match = re.search(r'\{[\s\S]*\}', text)
if json_match:
try:
return json.loads(json_match.group())
except json.JSONDecodeError:
pass
# 策略4:使用 jsonrepair 库修复常见格式错误
try:
from jsonrepair import repair_json
repaired = repair_json(text)
return json.loads(repaired)
except:
pass
return None # 所有策略失败
Pydantic 校验:从解析到验证
解析成功只是第一步,还需要验证字段类型、值范围、必填字段。使用 Pydantic 的 model_validate() 方法,可以同时完成解析和验证,并获得清晰的错误信息。
结构化输出的失败模式与修复
混入说明文字
模型在 JSON 前后输出"好的,以下是分析结果:"或"希望这个格式满足您的需求"等说明文字,导致直接 json.loads() 失败。修复:在提示词中明确写"只输出 JSON,不要任何说明文字";在代码层用正则提取 { } 之间的内容;使用 API 级别的 JSON mode 或 Structured Outputs。
字段截断(输出 token 限制)
当输出内容很长时,模型可能在 JSON 中途截断,导致输出无效的 JSON。修复:设置足够大的 max_tokens;将大型输出拆分为多个小 JSON;使用流式输出时在前端做 JSON 完整性检测。
嵌套层级错误
复杂嵌套结构(超过 3 层)容易出现括号匹配错误。修复:使用 Structured Outputs 让 API 保证格式;简化 Schema 设计,减少嵌套层级;或改用 XML 格式(模型对 XML 的嵌套关系处理更稳定)。
枚举值偏离
当字段要求 "positive|negative|mixed" 时,模型可能输出 "mostly_positive" 或 "正面"(中文)。修复:在提示词中明确列出所有合法值;使用 Pydantic 的 Literal 类型在解析后验证;考虑用 0-1 的数值代替枚举,避免字符串匹配问题。
Function Calling:结构化输出的另一种方式
# Function Calling 是强制结构化输出的另一种机制
# 模型被迫以特定参数格式"调用函数",确保字段完整
tools = [
{
"type": "function",
"function": {
"name": "analyze_review",
"description": "分析用户评论的情感和关键信息",
"parameters": {
"type": "object",
"properties": {
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "mixed"]
},
"score": {
"type": "number",
"minimum": 1,
"maximum": 5,
"description": "1-5 分评分"
},
"key_points": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["sentiment", "score", "key_points"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "分析:产品很棒,物流很慢!"}],
tools=tools,
tool_choice={"type": "function", "function": {"name": "analyze_review"}}
)
# 提取函数调用参数(保证是合法 JSON)
import json
args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
本章小结
本章核心要点
- 三层结构化方式:API 级别(Structured Outputs/Function Calling)最可靠但仅支持特定模型;提示词级别(指定格式+示例)通用但需要解析兜底;后处理级别(正则提取)作为最后防线。生产环境推荐第一层 + 第三层双保险。
- Structured Outputs 的优势:OpenAI 2024 年推出,用 Pydantic 模型定义 Schema,API 层面保证 100% 符合格式;直接获得类型化的 Python 对象,无需手动解析;是目前最可靠的结构化输出方案。
- XML 标签法的场景:Claude 系列模型对 XML 响应特别稳定;XML 可以清晰区分输入的不同部分(任务、数据、约束);复杂嵌套结构用 XML 比 JSON 更不容易出格式错误。
- 健壮解析的四步策略:直接解析 → 提取代码块 → 正则提取 {} → jsonrepair 修复;配合 Pydantic 做字段类型和值范围验证;解析失败时返回结构化的错误信息而非空值。
- 常见失败模式:混入说明文字、输出截断、嵌套错误、枚举偏离。每种失败模式都有对应的预防和修复策略;最根本的解决方案是使用 API 级别的 Structured Outputs。