Chapter 07

结构化输出 + 推理:JSON 约束下的思维链

推理模型需要自由思考,但生产系统需要结构化输出。如何让模型既能深度推理,又能输出可解析的 JSON?

核心矛盾

推理模型的困境:

解耦方案:Two-Stage 架构

Stage 1: 推理阶段(Extended Thinking) 输入: 原始问题 模型: claude-sonnet-4-6 + thinking enabled 输出: thinking block(推理过程)+ text block(自由文本分析) Stage 2: 结构化提取阶段(不用推理模型) 输入: Stage 1 的 text block 模型: claude-haiku-4-5-20251001(快速便宜) 约束: response_format 强制 JSON 输出: 标准 JSON 结构
import anthropic
from pydantic import BaseModel
from typing import List

client = anthropic.Anthropic()

class CodeReviewResult(BaseModel):
    bugs: List[dict]
    suggestions: List[str]
    overall_score: int
    summary: str

def reasoning_then_extract(code: str) -> CodeReviewResult:
    # Stage 1: 深度推理分析
    reasoning_response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=8000,
        thinking={"type": "enabled", "budget_tokens": 5000},
        messages=[{"role": "user",
            "content": f"请深度审查以下代码,找出所有 bug 和改进点:\n```\n{code}\n```"}]
    )
    analysis_text = next(b.text for b in reasoning_response.content if b.type == "text")

    # Stage 2: 结构化提取(用轻量模型)
    extract_response = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=2000,
        system="""将以下代码审查分析提取为 JSON。
严格输出 JSON,不要任何其他内容:
{"bugs": [{"line": 数字, "severity": "high/medium/low", "description": "描述"}],
 "suggestions": ["建议1"],
 "overall_score": 0-10,
 "summary": "一句话总结"}""",
        messages=[{"role": "user",
            "content": f"审查分析:\n{analysis_text}"}]
    )
    return CodeReviewResult.model_validate_json(extract_response.content[0].text)

单模型方案:在 System Prompt 中分离推理与输出

如果不想用两阶段,可以让模型自己管理格式:

system = """你是代码审查专家。
工作方式:
1. 在 <analysis> 标签内自由思考和分析(不限格式)
2. 在 <result> 标签内输出严格的 JSON(不含注释)

示例格式:
<analysis>
这段代码... [任意分析内容]
</analysis>
<result>
{"bugs": [...], "score": 8}
</result>"""

def extract_json_from_result_tag(text: str) -> dict:
    import re
    match = re.search(r'<result>\s*(.*?)\s*</result>', text, re.DOTALL)
    if match:
        return json.loads(match.group(1))
    raise ValueError("No <result> tag found")

Instructor 库:自动化结构化提取

import instructor
from pydantic import BaseModel, Field
from typing import List

# Instructor 封装 Claude 客户端(兼容 thinking mode)
client_with_instructor = instructor.from_anthropic(
    anthropic.Anthropic(),
    mode=instructor.Mode.ANTHROPIC_TOOLS
)

class TechStackRecommendation(BaseModel):
    recommended_stack: List[str] = Field(description="推荐的技术栈列表")
    reasoning: str = Field(description="选择理由")
    risks: List[str] = Field(description="潜在风险")
    alternatives: List[str] = Field(description="备选方案")

result = client_with_instructor.chat.completions.create(
    model="claude-sonnet-4-6",
    max_tokens=4000,
    response_model=TechStackRecommendation,
    messages=[{"role": "user",
        "content": "为一个日活 100 万的实时聊天应用推荐技术栈"}]
)
print(result.recommended_stack)
print(result.reasoning)
Two-Stage 架构的成本优化 Stage 1 使用推理模型(贵),Stage 2 使用 haiku(便宜)。由于推理分析往往只有几百 token,Stage 2 成本极低。整体成本比让推理模型直接输出 JSON 低约 20-30%,且更可靠。
本章小结 推理 + 结构化输出的最佳实践:Two-Stage(推理 → 提取)或 XML 标签隔离推理区域。Instructor 库简化了提取层的代码。下一章进入推理模型评估体系。