Chapter 06

结构化输出

精准控制 JSON、Markdown、XML 等结构化输出格式,让 AI 输出直接可用于程序处理

为什么结构化输出如此重要?

在将 AI 集成到应用程序时,最大的挑战之一是输出的不确定性——同样的提示词,模型可能在不同时候输出略有差异的格式,导致下游解析失败。结构化输出技术解决了这个问题,让 AI 输出可以像数据库查询结果一样可靠、可解析。

三种结构化输出方式

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)

本章小结

本章核心要点