Chapter 06

推理模型 × Agent:Plan-and-Execute 架构

将推理模型的全局规划能力与 Agent 的执行能力结合,构建真正"先想清楚再动手"的智能工作流。

为什么推理模型是天然的规划器

传统 ReAct Agent 的问题
ReAct Agent 的每次"思考(Thought)"都是局部的——它只能看到当前的观察结果,无法提前规划整个任务。这导致:(1) 工具调用顺序次优,先做了后面不需要的事;(2) 中途迷失方向;(3) 每轮推理消耗大量 token;(4) 对于需要 10+ 步的任务,错误会累积。
推理模型的优势:全局视野
推理模型的扩展思考允许它在开始执行前,完整地规划所有步骤——包括可能的失败路径和备选方案。这种"全局视野"是 ReAct Agent 所缺乏的。Plan-and-Execute 架构正是为了充分利用这一优势。
Plan-and-Execute 架构
将 Agent 的工作分为两个明确的阶段:规划阶段(用推理模型一次性生成完整计划)+ 执行阶段(用快速模型逐步执行计划)。规划模型和执行模型可以是不同的模型,通常规划用大模型(Sonnet + thinking),执行用小模型(Haiku),成本更优。
传统 ReAct(逐步思考,局部视野): Thought1 → Action1 → Observation1 Thought2 → Action2 → Observation2 ← 可能推翻 Thought1 的决定 Thought3 → Action3 → Observation3 ← 此时才发现需要 Action1 的结果 ... ← 步骤数不可预测,容易循环 Plan-and-Execute(全局规划,再执行): ┌─── 规划阶段(一次性,推理模型)────────────────┐ │ 思考:完成这个任务需要: │ │ 1. search_web("Tesla Q4 2024 earnings") │ │ 2. parse_pdf(search_result) │ │ 3. calculate_metrics(financials) │ │ 4. compare_with_previous_quarters() │ │ 5. generate_report(metrics, comparison) │ └────────────────────────────────────────────┘ ↓ 交给执行层 ┌─── 执行阶段(逐步,快速模型)──────────────────┐ │ 执行步骤 1 → 执行步骤 2 → ... → 执行步骤 5 │ └────────────────────────────────────────────┘

Plan-and-Execute 完整实现

import anthropic
import json
from typing import List, Dict, Callable, Any

client = anthropic.Anthropic()

# ── 工具定义 ─────────────────────────────────────────────
def search_web(query: str) -> str:
    """模拟 Web 搜索工具"""
    return f"[搜索结果: {query}] 找到 5 篇相关文章..."

def calculate(expression: str) -> str:
    """安全计算工具"""
    try:
        result = eval(expression, {"__builtins__": {}})
        return str(result)
    except Exception as e:
        return f"计算错误: {e}"

TOOL_MAP: Dict[str, Callable] = {
    "search_web": search_web,
    "calculate": calculate,
}

# ── 规划器:使用推理模型一次性规划所有步骤 ──────────────
def reasoning_planner(goal: str, available_tools: List[str]) -> List[Dict]:
    """
    使用推理模型生成完整的执行计划
    返回步骤列表,每步包含 tool、input、purpose
    """
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=10000,
        thinking={"type": "enabled", "budget_tokens": 5000},
        # 系统提示:只说背景,不指导推理
        system=f"""你是任务规划专家。
可用工具:{available_tools}
输出严格的 JSON 列表(不含注释):
[{{"step": 1, "tool": "工具名", "input": "输入参数", "purpose": "这步的目的"}}]""",
        messages=[{"role": "user", "content": f"目标:{goal}"}]
    )

    # 提取 text block(推理已在 thinking 中完成)
    text = next(b.text for b in response.content if b.type == "text")
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        # JSON 解析失败时,让小模型修复格式
        fix_response = client.messages.create(
            model="claude-haiku-4-5-20251001",
            max_tokens=1000,
            messages=[{"role": "user",
                       "content": f"将以下内容修复为合法 JSON 列表:\n{text}"}]
        )
        return json.loads(fix_response.content[0].text)

# ── 执行器:用快速模型逐步执行计划 ─────────────────────
def execute_plan(plan: List[Dict]) -> Dict:
    """逐步执行计划,支持步骤间的结果引用"""
    results = {}
    context = {}  # 存储每步结果,供后续步骤引用

    for step in plan:
        step_num = step["step"]
        tool_name = step["tool"]
        tool_input = step["input"]

        # 支持在 input 中引用前面步骤的结果 {step1_result}
        try:
            tool_input = tool_input.format(**context)
        except KeyError:
            pass  # 没有需要替换的引用

        # 调用对应工具
        if tool_name in TOOL_MAP:
            result = TOOL_MAP[tool_name](tool_input)
        else:
            result = f"工具 {tool_name} 不存在"

        context[f"step{step_num}_result"] = result
        results[step_num] = {
            "tool": tool_name,
            "input": tool_input,
            "output": result,
            "purpose": step["purpose"]
        }

    return results

# ── 汇总器:将执行结果综合为最终答案 ───────────────────
def synthesize_results(goal: str, execution_results: Dict) -> str:
    results_text = "\n".join([
        f"步骤{k}({v['purpose']}): {v['output']}"
        for k, v in execution_results.items()
    ])
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2000,
        messages=[{"role": "user", "content":
            f"原始目标:{goal}\n\n执行结果:\n{results_text}\n\n请综合以上结果,给出完整答案:"}]
    )
    return response.content[0].text

# ── 完整流程 ────────────────────────────────────────────
goal = "计算 Tesla 的市盈率并分析是否被高估"
tools = ["search_web", "calculate"]

plan = reasoning_planner(goal, tools)
print(f"规划了 {len(plan)} 个步骤")

results = execute_plan(plan)
final_answer = synthesize_results(goal, results)
print(final_answer)

ReWOO:推理与观察完全解耦

ReWOO(Reasoning WithOut Observation)是 Plan-and-Execute 的极致版本:规划器一次性生成所有工具调用计划(包括结果的引用占位符),然后工具并行执行,最后综合结果。核心优势是工具调用可以并行,大幅降低延迟。

import asyncio

async def rewoo_agent(task: str, tools: dict) -> str:
    """
    ReWOO 三阶段:Planner → Worker(并行)→ Solver
    适合工具调用可以并行的场景(如:同时搜索多个话题)
    """
    # ── Phase 1: Planner(一次性生成所有工具调用计划)────
    plan_prompt = f"""为以下任务制定工具调用计划(只规划,不执行):
任务:{task}
可用工具:{list(tools.keys())}

计划格式:
#E1 = tool_name["输入参数"]
#E2 = tool_name["输入,可引用 #E1 的结果"]

注意:可以并行执行的步骤请分组标注"""

    planner_response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=6000,
        thinking={"type": "enabled", "budget_tokens": 3000},
        messages=[{"role": "user", "content": plan_prompt}]
    )
    plan_text = next(b.text for b in planner_response.content if b.type == "text")
    tool_plan = parse_rewoo_plan(plan_text)  # 解析 #E1=... 格式

    # ── Phase 2: Worker(并行执行所有工具调用)──────────
    evidence = {}
    # 找出哪些步骤可以并行(不依赖其他步骤的结果)
    independent_steps = [s for s in tool_plan if "#E" not in s["input"]]
    dependent_steps = [s for s in tool_plan if "#E" in s["input"]]

    # 并行执行独立步骤
    independent_tasks = [
        call_tool_async(s["tool"], s["input"])
        for s in independent_steps
    ]
    results = await asyncio.gather(*independent_tasks)
    for step, result in zip(independent_steps, results):
        evidence[step["id"]] = result

    # 顺序执行依赖步骤(替换引用占位符)
    for step in dependent_steps:
        actual_input = step["input"]
        for ref_id, ref_val in evidence.items():
            actual_input = actual_input.replace(ref_id, ref_val)
        result = await call_tool_async(step["tool"], actual_input)
        evidence[step["id"]] = result

    # ── Phase 3: Solver(综合证据给出最终答案)──────────
    evidence_text = "\n".join([f"{k}: {v}" for k, v in evidence.items()])
    final = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2000,
        messages=[{"role": "user", "content":
            f"任务:{task}\n\n工具执行结果:\n{evidence_text}\n\n综合以上信息给出完整答案:"}]
    )
    return final.content[0].text
Plan-and-Execute 的局限性

Plan-and-Execute 假设计划是稳定的——如果工具调用的结果与预期差异很大(如搜索没找到结果),固定的计划就无法应对。解决方案:在每次工具调用后加入一个"重规划"检查节点,如果结果异常(空结果、错误),触发重规划。这增加了系统复杂度,但对于关键业务任务是值得的。

推理模型在 Agent 中的最佳实践总结

规划层用推理模型,执行层用快速模型
推理模型(Sonnet + thinking)负责"想清楚做什么",普通模型(Haiku、GPT-4o-mini)负责"按计划执行每一步"。这样在保证规划质量的同时控制成本。经验数字:规划成本约占总成本 30%,执行成本约 70%,但规划质量决定了任务成功率。
一次性全局规划优于逐步规划
让推理模型一次性看清全局(看到所有工具、理解完整目标),比每步局部推理更稳定。利用推理模型的"长思维链"能力,而不是把它用作普通的 ReAct agent(那样无法充分发挥推理模型的优势)。
只在关键决策节点启用推理
在 Agent 工作流中,只在需要复杂判断的决策节点(如初始规划、处理意外情况、最终综合)使用推理模型,其余节点用普通模型。这是成本与质量的最优平衡点。
工具失败时的推理驱动重规划
当工具调用失败或返回意外结果时,触发推理模型进行"障碍分析":分析失败原因、评估备选方案、决定是否继续。这使 Agent 具备了真正的鲁棒性,而不是简单的重试机制。
本章小结

推理模型最适合作为 Agent 的规划层,利用其全局视野一次性制定完整执行计划。Plan-and-Execute 将规划(推理模型)与执行(快速模型)分离,ReWOO 进一步实现并行工具调用。关键原则:规划层用推理模型(贵但精准),执行层用小模型(便宜高效),只在关键决策节点启用推理。下一章解决推理模型与结构化输出的兼容问题——这是生产落地最常见的工程挑战之一。