4 结构化输出工程
为什么结构化输出很重要
结构化输出强制执行在企业应用中可提高 3-5 倍的可靠性。没有它,LLM 输出是不可预测的字符串,需要复杂的解析、错误处理和重试逻辑。
可靠性问题
无结构化输出时:
┌───────────────────────────────────────────────────── ────────┐
│ LLM 响应:"这里是分析: │
│ - 收入:可能在 5000 万左右 │
│ - 增长:强劲!约 15% │
│ - 风险:中等(详见下文) │
│ 注意:数字是估算..." │
└─────────────────────────────────────────────────────────────┘
↓ 解析地狱
- 正则提取在边缘情况下失败
- 数字格式多样("$50M" vs "50000000")
- 风险等级不一致("Medium" vs "3/5")
- 额外文本破坏 JSON 解析
结构化输出时:
┌─────────────────────────────────────────────────────────────┐
│ { │
│ "revenue": 50000000, │
│ "growthRate": 0.15, │
│ "riskLevel": "MEDIUM", │
│ "confidence": 0.85 │
│ } │
└─────────────────────────────────────────────────────────────┘
↓ 直接解析
- JSON.parse() 每次都成功
- 类型安全的反序列化
- 对照模式验证
- 无歧义
业务影响
| 指标 | 无结构 | 有结构 | 改进 |
|---|---|---|---|
| 解析成功率 | 75-85% | 99.9%+ | +15-25% |
| 重试率 | 15-25% | 少于 1% | -95% |
| 令牌浪费 | 高(冗长) | 低(精确) | -30-50% |
| 集成时间 | 天/周 | 小时 | 快 10 倍 |
| 生产事件 | 每周 | 罕见 | -90% |
1. JSON 模式:提供商实现
1.1 OpenAI 结构化输出(2024)
OpenAI 的结构化输出功能通过受限制解码保证 100% 的模式遵循。模型在数学上被限制为只生成符合您模式的令牌。
关键创新
与仅确保有效 JSON 的"JSON 模式"不同,结构化输出确保完全符合您模式的有效 JSON。这是通过在令牌生成级别的受限解码实现的。
工作原理:
传统 JSON 模式:
模型生成 → 有效 JSON(任何结构)→ 希望它匹配
结构化输出:
模式 → 受限令牌空间 → 只生成有效令牌
示例:如果模式要求 "status": enum["active", "inactive"]
其他值的令牌概率 = 0
实现:
import OpenAI from 'openai';
import { z } from 'zod';
import { zodResponseFormat } from 'openai/helpers/zod';
// 使用 Zod 定义模式
const AnalysisResult = z.object({
sentiment: z.enum(['positive', 'negative', 'neutral']),
confidence: z.number().min(0).max(1),
keyTopics: z.array(z.object({
topic: z.string(),
relevance: z.number().min(0).max(1),
mentions: z.number().int().positive()
})),
summary: z.string().max(500),
actionItems: z.array(z.string()).optional()
});
const client = new OpenAI();
const response = await client.beta.chat.completions.parse({
model: 'gpt-4o-2024-08-06',
messages: [
{ role: 'system', content: '分析客户反馈并提取见解。' },
{ role: 'user', content: feedbackText }
],
response_format: zodResponseFormat(AnalysisResult, 'analysis')
});
// 完全类型化的响应 - 无需解析!
const analysis = response.choices[0].message.parsed;
console.log(analysis.sentiment); // TypeScript 知道这是 'positive' | 'negative' | 'neutral'
支持的模式特性:
| 特性 | 支持 | 备注 |
|---|---|---|
string, number, boolean | ✅ 完整 | 基本类型 |
array | ✅ 完整 | 带类型化项目 |
object | ✅ 完整 | 支持嵌套对象 |
enum | ✅ 完整 | 字符串枚举 |
anyOf | ✅ 完整 | 联合类型 |
$ref / definitions | ✅ 完整 | 递归模式 |
additionalProperties: false | ⚠️ 必需 | 必须设置 |
required | ⚠️ 必需 | 所有属性都必须必需 |
限制:
- 最多 5 层嵌套
- 最多 100 个属性总数
- 不支持
additionalProperties: true - 所有字段都必须是
required(使用anyOf与null表示可选)
1.2 Anthropic:工具使用变通方案
Anthropic 的 Claude 没有原生 JSON 模式,但通过工具使用(函数调用)实现结构化输出。
Anthropic 的方法
不使用专门的 JSON 模式,Anthropic 建议使用工具定义作为模式。模型"调用"具有结构化参数的工具,有效地产生 JSON 输出。
import anthropic
client = anthropic.Anthropic()
# 将"输出模式"定义为工具
tools = [{
"name": "submit_analysis",
"description": "提交结构化分析结果",
"input_schema": {
"type": "object",
"properties": {
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "neutral"],
"description": "文本的整体情感"
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "0 到 1 之间的置信度分数"
},
"key_phrases": {
"type": "array",
"items": {"type": "string"},
"description": "从文本中提取的重要短语"
},
"summary": {
"type": "string",
"maxLength": 500,
"description": "内容的简要总结"
}
},
"required": ["sentiment", "confidence", "key_phrases", "summary"]
}
}]
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "submit_analysis"}, # 强制工具使用
messages=[{
"role": "user",
"content": f"分析此客户反馈并提交您的分析:\n\n{feedback_text}"
}]
)
# 从工具调用中提取结构化数据
tool_use = next(block for block in response.content if block.type == "tool_use")
analysis = tool_use.input # 这是您的结构化 JSON
1.3 Google Gemini
Gemini 支持带模式强制执行的 JSON 模式:
import google.generativeai as genai
from google.generativeai.types import GenerationConfig
# 定义模式
response_schema = {
"type": "object",
"properties": {
"sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]},
"score": {"type": "number"},
"themes": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["sentiment", "score", "themes"]
}
model = genai.GenerativeModel(
'gemini-1.5-pro',
generation_config=GenerationConfig(
response_mime_type="application/json",
response_schema=response_schema
)
)
response = model.generate_content("分析: " + text)
result = json.loads(response.text)
1.4 提供商比较矩阵
| 特性 | OpenAI | Anthropic | Gemini | Mistral |
|---|---|---|---|---|
| 原生 JSON 模式 | ✅ | ❌ | ✅ | ✅ |
| 模式强制执行 | ✅ 100% | 通过工具 | ✅ | ⚠️ 部分支持 |
| 受限解码 | ✅ | ❌ | ✅ | ❌ |
| 嵌套对 象 | ✅ 5 层 | ✅ 无限 | ✅ | ✅ |
| 递归模式 | ✅ | ✅ | ⚠️ 有限 | ❌ |
| 流式支持 | ✅ | ✅ | ✅ | ✅ |
| 令牌效率 | 高 | 中等 | 高 | 中等 |
2. XML 标签:无模式的结构
XML 标签提供轻量级结构而不需要 API 级别的模式强制执行。这种方法适用于任何 LLM,并且在 Claude 中特别有效。
2.1 为什么 XML 优于 Markdown 分隔符
Markdown 分隔符(有问题):
┌─────────────────────────────────────────────────────────────┐