【AI应用开发】 Agent篇(三):规划推理:ReAct 与 Plan-Execute

【AI应用开发】 Agent篇(三):规划推理:ReAct 与 Plan-Execute

前言

工具调用让 Agent 有了"手",但还是不够聪明------面对复杂任务,它需要知道先做什么、后做什么、遇到意外怎么办

这就是"规划推理"(Planning & Reasoning)要解决的问题。本篇深入三种主流范式:ReAct(边想边做)、Plan-Execute(先规划后执行)、ReWOO(规划执行分离),每种附完整可运行代码,让你真正理解 Agent 的"思考"机制。


目录

  1. [为什么 Agent 需要规划](#为什么 Agent 需要规划)
    • 1.1 简单任务 vs 复杂任务
    • 1.2 规划的三种层次
  2. ReAct:边想边做
    • 2.1 核心原理
    • 2.2 手写 ReAct Agent
    • 2.3 Thought/Action/Observation 循环剖析
  3. Plan-Execute:先规划后执行
    • 3.1 核心原理
    • 3.2 手写 Plan-Execute Agent
    • 3.3 动态重规划
  4. ReWOO:规划与执行分离
    • 4.1 为什么需要 ReWOO
    • 4.2 手写 ReWOO Agent
  5. 范式对比与选择

1. 为什么 Agent 需要规划

1.1 简单任务 vs 复杂任务

看两组对比:

复制代码
简单任务:
  "深圳今天天气怎么样?"
  → 调一次 get_weather → 回答
  → 1步搞定

复杂任务:
  "帮我分析一下深圳和北京过去一周的天气差异,做成对比表格, 
   然后根据天气情况推荐两地各3个适合的户外活动"
  → 查深圳过去7天天气
  → 查北京过去7天天气
  → 对比分析
  → 做成表格
  → 分别推荐户外活动
  → 至少需要规划+执行+整理

没有规划能力的 Agent:调一个工具 → 发现信息不够 → 再调一个 → 发现还不够 → ... 像一个无头苍蝇。

有规划能力的 Agent:先想清楚整个任务的步骤,再逐步执行,每一步都目标明确。

1.2 规划的三种层次

复制代码
层次一:单步执行
  每次只执行一个工具调用,没有整体规划
  
层次二:粗粒度规划
  先拆解成几个大步骤,但每步内部仍按 ReAct 执行
  
层次三:细粒度计划
  完整分解所有步骤,精确到每个工具调用和依赖关系

本篇文章依次讲解这三种层次对应的技术实现。


2. ReAct:边想边做

2.1 核心原理

ReAct = Rea soning + A cting,2022 年由 Google 提出,是最经典也是应用最广的范式。

核心思想:LLM 每步都输出三个部分:

复制代码
Thought(思考):我当前知道什么?应该做什么?
Action  (行动):调用哪个工具?参数是什么?
Observation(观察):工具返回了什么?

然后用 Observation 更新 Thought,开启下一轮循环。

2.2 手写 ReAct Agent

不用任何框架,用 ReAct Prompt 模板手写一个 ReAct Agent:

python 复制代码
"""
完整的 ReAct Agent 实现
特点:不依赖 Function Calling API,纯 Prompt 驱动
适用:任何 LLM,包括不支持 Function Calling 的开源模型
"""

import re
import json
from openai import OpenAI

client = OpenAI()

# ============================================================
# ReAct Prompt 模板
# ============================================================
REACT_SYSTEM_PROMPT = """你是一个能使用工具的智能助手。严格遵循以下格式回答:

可用工具:
- search(query: str) --- 搜索互联网获取信息
- calculate(expression: str) --- 执行数学计算
- get_weather(city: str) --- 查询城市天气

你必须严格按照以下格式回复(每行以对应标签开头):

Thought: [你对当前情况的分析,决定下一步做什么]
Action: [工具名称,如 search,如果不需调用工具则写 None]
Action Input: [JSON格式的工具参数,如 {"query": "今天日期"},如果不需调用工具则写 {}]

当你有足够信息回答用户时:
Thought: [你现在可以回答了]
Action: None
Action Input: {}
Final Answer: [你的最终回答,用中文,引用来源]

重要规则:
1. 每次只输出一组 Thought/Action/Action Input
2. 收到工具结果后继续用 Thought 分析
3. 不要编造信息,必须基于工具返回结果
"""

# ============================================================
# 工具执行
# ============================================================
def execute_tool(name: str, args: dict) -> str:
    tools = {
        "search": lambda: f"搜索结果:关于「{args['query']}」找到3条相关信息...(此处为搜索结果摘要)",
        "calculate": lambda: f"计算结果:{args['expression']} = {eval(args['expression'])}",
        "get_weather": lambda: f"{args['city']}今天多云转晴,18-25°C,微风,湿度55%"
    }
    return tools.get(name, lambda: f"未知工具:{name}")()

# ============================================================
# 解析 LLM 输出
# ============================================================
def parse_react_output(text: str) -> dict:
    """从 LLM 输出中提取 Thought/Action/Action Input"""
    thought = re.search(r'Thought:\s*(.+?)(?=\nAction:|\Z)', text, re.DOTALL)
    action = re.search(r'Action:\s*(.+?)(?=\nAction Input:|\Z)', text, re.DOTALL)
    action_input = re.search(r'Action Input:\s*(.+?)(?=\nFinal Answer:|\nThought:|\Z)', text, re.DOTALL)
    final_answer = re.search(r'Final Answer:\s*(.+)', text, re.DOTALL)
    
    return {
        "thought": thought.group(1).strip() if thought else "",
        "action": action.group(1).strip() if action else "None",
        "action_input": action_input.group(1).strip() if action_input else "{}",
        "final_answer": final_answer.group(1).strip() if final_answer else None
    }

# ============================================================
# ReAct Agent 主循环
# ============================================================
def react_agent(user_question: str, max_steps: int = 10) -> str:
    """ReAct Agent:边想边做,循环直到得到最终答案"""
    
    messages = [
        {"role": "system", "content": REACT_SYSTEM_PROMPT},
        {"role": "user", "content": user_question}
    ]
    
    for step in range(max_steps):
        print(f"\n{'='*60}")
        print(f"🔄 Step {step + 1}")
        
        # 调用 LLM
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages
        )
        llm_output = response.choices[0].message.content
        
        # 解析输出
        parsed = parse_react_output(llm_output)
        print(f"💭 Thought: {parsed['thought'][:80]}...")
        
        # 有最终答案 → 返回
        if parsed["final_answer"]:
            return parsed["final_answer"]
        
        # 需要执行工具
        action = parsed["action"]
        if action and action != "None":
            try:
                args = json.loads(parsed["action_input"])
            except json.JSONDecodeError:
                args = {}
            
            print(f"🔧 Action: {action}({args})")
            
            result = execute_tool(action, args)
            print(f"📋 Observation: {result[:80]}...")
            
            # 把 AI 输出和工具结果都加入对话
            messages.append({"role": "assistant", "content": llm_output})
            messages.append({"role": "user", "content": f"Observation: {result}"})
        else:
            # 没有工具调用也没有最终答案 → 让 LLM 继续
            messages.append({"role": "assistant", "content": llm_output})
            messages.append({"role": "user", "content": "请继续思考,使用工具获取信息或给出最终答案。"})
    
    return "抱歉,任务步骤过多,未能完成。"

# ============================================================
# 运行示例
# ============================================================
if __name__ == "__main__":
    question = "深圳今天的天气适合户外跑步吗?顺便帮我算一下如果我跑5公里,配速每公里6分钟,总共需要多久?"
    answer = react_agent(question)
    print(f"\n{'='*60}")
    print(f"✅ 最终答案:\n{answer}")

运行效果(精简后)

复制代码
🔄 Step 1
💭 Thought: 需要查深圳天气和计算跑步时间,先查天气
🔧 Action: get_weather({"city": "深圳"})
📋 Observation: 深圳今天多云转晴,18-25°C...

🔄 Step 2
💭 Thought: 天气适合户外跑步!现在计算时间
🔧 Action: calculate({"expression": "5 * 6"})
📋 Observation: 计算结果:5 * 6 = 30

🔄 Step 3
💭 Thought: 已有所有信息,可以回答了
✅ 最终答案:
深圳今天多云转晴,18-25°C,非常适合户外跑步。按照每公里6分钟的配速,
跑5公里总共需要30分钟。

2.3 Thought/Action/Observation 循环剖析

ReAct 的厉害之处不在于单个步骤,而在于这套循环机制:

复制代码
                     ┌──────────┐
                     │ Thought  │ ← 基于已有信息做推理
                     └────┬─────┘
                          │ 决定需要什么工具
                          ▼
                     ┌──────────┐
                     │ Action   │ ← 调用工具
                     └────┬─────┘
                          │ 获得新信息
                          ▼
                     ┌──────────┐
              ┌──────│Observation│←──────┐
              │      └──────────┘       │
              │            │             │
              │    信息足够了吗?         │
              │      │         │        │
              │    足够       不够      │
              │      │         │        │
              │      ▼         └────────┘
              │ ┌──────────┐   (回到 Thought)
              │ │  Final   │
              │ │  Answer  │
              │ └──────────┘
              └── 结束

ReAct 的优势:每一步都能根据最新观察动态调整,就算前面的假设错了,后面也能纠正。

ReAct 的劣势:每步都推理一次,token 消耗大;不能并行执行。


3. Plan-Execute:先规划后执行

3.1 核心原理

ReAct 是"走一步看一步",Plan-Execute 是"先画地图再走路"。

复制代码
Plan-Execute 两阶段:

阶段一(Plan):LLM 一次性生成完整执行计划
  Step 1: 查深圳天气
  Step 2: 查北京天气  
  Step 3: 计算两地温差
  Step 4: 根据天气推荐活动
  Step 5: 汇总成对比表格

阶段二(Execute):按计划逐步执行
  执行 Step 1 → 执行 Step 2 → 执行 Step 3 → ...
  如果某步失败 → 修正计划 → 继续

3.2 手写 Plan-Execute Agent

python 复制代码
"""
完整的 Plan-Execute Agent 实现
两个 LLM 调用:一次规划 + 多次执行
"""

import json
import re
from openai import OpenAI

client = OpenAI()

# ============================================================
# 阶段一:规划器 (Planner)
# ============================================================
PLANNER_PROMPT = """你是一个任务规划专家。根据用户需求制定详细的执行计划。

可用工具:
- search(query: str) --- 搜索互联网
- calculate(expression: str) --- 数学计算
- get_weather(city: str) --- 查询天气

请输出 JSON 格式的执行计划:
{
    "plan": [
        {
            "step": 1,
            "description": "描述这一步要做什么",
            "tool": "工具名称",
            "args": {"参数": "值"},
            "depends_on": []  // 依赖的前置步骤编号,无依赖则空
        },
        ...
    ]
}

规则:
1. 把任务拆成最小可执行步骤
2. 明确每个步骤用哪个工具、什么参数
3. 标记步骤间的依赖关系
4. 最后一步应该是"汇总结果回答用户"
"""

def make_plan(user_question: str) -> list:
    """调用 LLM 生成执行计划"""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": PLANNER_PROMPT},
            {"role": "user", "content": user_question}
        ],
        response_format={"type": "json_object"}
    )
    
    plan_data = json.loads(response.choices[0].message.content)
    return plan_data["plan"]

# ============================================================
# 阶段二:执行器 (Executor)
# ============================================================
def execute_plan(plan: list) -> dict:
    """按计划逐步执行,处理依赖和失败"""
    results = {}  # step_id → result
    tool_executor = create_tool_executor()  # 同前面的 execute_tool
    
    for step in plan:
        step_id = step["step"]
        print(f"\n📍 Step {step_id}: {step['description']}")
        
        # 检查是否有未满足的依赖
        deps_ready = True
        for dep_id in step.get("depends_on", []):
            if dep_id not in results:
                print(f"  ⚠️ 依赖步骤 {dep_id} 未完成,跳过")
                deps_ready = False
                break
            # 如果有依赖,将前置结果注入参数
            if "{step_" in json.dumps(step["args"]):
                for key, val in step["args"].items():
                    if isinstance(val, str) and "{step_" in val:
                        dep_id = int(re.search(r'step_(\d+)', val).group(1))
                        step["args"][key] = results[dep_id]
        
        if not deps_ready:
            continue
        
        try:
            result = tool_executor(step["tool"], step["args"])
            results[step_id] = result
            print(f"  ✅ 完成 → {result[:60]}...")
        except Exception as e:
            print(f"  ❌ 失败:{e}")
            results[step_id] = f"[执行失败] {str(e)}"
    
    return results

# ============================================================
# 阶段三:汇总器 (Summarizer)
# ============================================================
SUMMARIZE_PROMPT = """根据以下执行结果,用中文回答用户的原始问题。

原始问题:{question}

执行结果:
{results}

请综合所有结果,给出清晰、有用的回答。"""

def summarize(question: str, results: dict) -> str:
    """汇总执行结果生成最终答案"""
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": SUMMARIZE_PROMPT.format(
                question=question,
                results=json.dumps(results, ensure_ascii=False, indent=2)
            )
        }]
    )
    return response.choices[0].message.content

# ============================================================
# Plan-Execute Agent 主函数
# ============================================================
def plan_execute_agent(user_question: str) -> str:
    # 阶段一:制定计划
    print("📝 正在制定执行计划...")
    plan = make_plan(user_question)
    for step in plan:
        print(f"  {step['step']}. {step['description']} [{step['tool']}]")
    
    # 阶段二:执行计划
    print("\n🚀 开始执行计划...")
    results = execute_plan(plan)
    
    # 阶段三:汇总结果
    print("\n📊 汇总结果...")
    return summarize(user_question, results)


if __name__ == "__main__":
    question = "对比深圳和北京今天的天气,推荐两座城市各2个适合的户外活动"
    answer = plan_execute_agent(question)
    print(f"\n✅ 最终答案:\n{answer}")

3.3 动态重规划

Plan-Execute 的一个关键增强是动态重规划------某步失败时,重新调整后续计划:

python 复制代码
def execute_plan_with_replan(plan: list, max_retries: int = 3) -> dict:
    """带动态重规划的执行器"""
    results = {}
    current_plan = plan[:]
    
    while current_plan:
        step = current_plan.pop(0)
        step_id = step["step"]
        
        try:
            result = execute_tool(step["tool"], step["args"])
            results[step_id] = result
        except Exception as e:
            print(f"  ❌ Step {step_id} 失败: {e}")
            
            # 调用 LLM 生成修正计划
            replan_prompt = f"""
原计划步骤 {step_id}({step['description']})执行失败:{e}
已完成步骤的结果:{json.dumps(results, ensure_ascii=False)}

请生成修正后的后续步骤(JSON):
{{"replan": [{{"step": {step_id}, "description": "...", "tool": "...", "args": {{}}}}...]}}
"""
            response = client.chat.completions.create(
                model="gpt-4o",
                messages=[{"role": "user", "content": replan_prompt}],
                response_format={"type": "json_object"}
            )
            replan = json.loads(response.choices[0].message.content)
            # 新计划插入到队列前面(新 + 剩余)
            current_plan = replan["replan"] + current_plan
    
    return results

4. ReWOO:规划与执行分离

4.1 为什么需要 ReWOO

ReWOO = Re asoning W itho ut Observation

ReAct 的问题:每一步都要 LLM 推理一次,太贵太慢。

Plan-Execute 的问题:计划不够灵活,执行中获取新信息后不能动态调整。

ReWOO 的解法:一次性做完整规划(含所有工具调用),批量执行,最后汇总

复制代码
ReAct:     LLM推理→执行→LLM推理→执行→LLM推理→执行→...
           (每步都调用LLM,串行)

ReWOO:     LLM一次性规划(含所有工具调用)
           ├→ 并行执行工具1
           ├→ 并行执行工具2   (无依赖的并行)
           ├→ 执行工具3(依赖1和2的结果)
           └→ LLM汇总一次
           (LLM只调用2次:规划+汇总)

4.2 手写 ReWOO Agent

python 复制代码
"""
ReWOO Agent:规划执行分离模式
LLM 只被调用两次:一次规划、一次汇总
"""

REWOO_PLANNER_PROMPT = """你是一个任务规划专家。一次性生成所有需要的工具调用。

可用工具:search, calculate, get_weather

输出 JSON 格式:
{
    "plan": [
        {"id": "#E1", "tool": "get_weather", "args": {"city": "深圳"}, "depends": []},
        {"id": "#E2", "tool": "get_weather", "args": {"city": "北京"}, "depends": []},
        {"id": "#E3", "tool": "calculate", "args": {"expression": "#E1.temp - #E2.temp"}, "depends": ["#E1", "#E2"]}
    ]
}

规则:
1. 每个工具调用给一个唯一ID(#E1, #E2...)
2. depends 标记依赖关系
3. 最后的汇总步骤不需要加到 plan 中"""

def rewoo_agent(user_question: str) -> str:
    # ===== 阶段一:一次性规划 =====
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": REWOO_PLANNER_PROMPT},
            {"role": "user", "content": user_question}
        ],
        response_format={"type": "json_object"}
    )
    plan = json.loads(response.choices[0].message.content)["plan"]
    
    # ===== 阶段二:批量执行(无依赖的并行) =====
    results = {}
    
    # 按依赖层级分组执行
    while len(results) < len(plan):
        # 找出所有依赖已满足的步骤
        ready = [
            s for s in plan 
            if s["id"] not in results 
            and all(dep in results for dep in s.get("depends", []))
        ]
        
        if not ready:
            break  # 死锁
        
        # 并行执行同一层级的步骤
        import concurrent.futures
        with concurrent.futures.ThreadPoolExecutor() as executor:
            futures = {}
            for step in ready:
                # 替换参数中的依赖引用
                args = step["args"].copy()
                for k, v in args.items():
                    if isinstance(v, str):
                        for dep_id in step.get("depends", []):
                            v = v.replace(dep_id, str(results.get(dep_id, "")))
                        args[k] = v
                
                futures[executor.submit(execute_tool, step["tool"], args)] = step["id"]
            
            for future in concurrent.futures.as_completed(futures):
                step_id = futures[future]
                results[step_id] = future.result()
                print(f"  ✅ {step_id} → {results[step_id][:50]}...")
    
    # ===== 阶段三:一次汇总 =====
    return summarize(user_question, results)


# 对比:三种范式的 LLM 调用次数
task = "对比深圳和北京今天天气,算温差,推荐户外活动"

# ReAct:   约 4-6 次 LLM 调用
# Plan-Execute:约 3-5 次(1规划 + 若干执行判断 + 1汇总)
# ReWOO:    2 次 LLM 调用(1规划 + 1汇总)

5. 范式对比与选择

维度 ReAct Plan-Execute ReWOO
LLM 调用次数 多(每步都调) 中(规划+每步判断+汇总) 少(规划+汇总)
Token 消耗
灵活性 ⭐⭐⭐⭐⭐ 最高 ⭐⭐⭐⭐ 较高 ⭐⭐⭐ 中等
并行能力 ❌ 不支持 ⚠️ 部分支持 ✅ 天然支持
错误恢复 ✅ 每步可纠正 ⚠️ 需重规划 ⚠️ 需手动处理
适用场景 不确定性高、需探索 步骤清晰、可结构化 子任务独立、可并行

选择建议

python 复制代码
# 伪代码决策逻辑
def choose_paradigm(task):
    if "比较" in task or "分析" in task:
        # 需要多步推理和比对 → ReAct
        return "ReAct"
    
    if task.includes_multiple_independent_subtasks():
        # 子任务独立可并行 → ReWOO
        return "ReWOO"
    
    if task.has_clear_steps():
        # 步骤清晰可预测 → Plan-Execute
        return "Plan-Execute"
    
    # 默认 → ReAct(最通用)
    return "ReAct"

现代框架的实践

大多数生产级 Agent 框架(LangGraph、CrewAI)采用混合模式

  • 顶层用 Plan-Execute(清晰的任务拆解)
  • 每个子任务内部用 ReAct(灵活应对不确定性)
  • 能并行的用 ReWOO 批量执行

总结

  1. ReAct 是最通用的范式,每一步都经过"思考→行动→观察"循环
  2. Plan-Execute 适合步骤清晰的任务,先规划后执行更高效
  3. ReWOO 追求极致效率,LLM 只调用两次
  4. 生产实践中一般采用混合模式
  5. 手写 Agent 的价值:理解循环机制 > 会用框架
相关推荐
achong5 小时前
PenguinHarness实测:LlamaFactory作者新作,0.2元造自进化Agent
人工智能·深度学习
AlloyTeamZy5 小时前
我发现,一个人做小游戏最难的,根本不是写代码
前端·人工智能·程序员
Loveyourself5 小时前
什么是cc中的session memory compact(background notes)?
agent
Hotchip_MEMS5 小时前
当雾化器遇上MEMS:一场从交互到制造的全链路效率提升
人工智能·笔记·物联网·电脑·制造
lucas_AI5 小时前
Skill-α:教 Agent 学会自己'改说明书'
llm·agent
水如烟5 小时前
孤能子视角:LLM是续指代投器——从“续指论”与“代投机制”看大语言模型的本质定位
人工智能
BerryS3N6 小时前
2026年AI前沿技术全景深度解析:大模型推理范式变革、Agentic AI工程架构与底层基础设施演进
人工智能·架构
天天爱吃肉82186 小时前
【重磅发布:拿下新超仁达代理权 】
大数据·人工智能·python·功能测试·汽车
HIT_Weston6 小时前
167、【Agent】【OpenCode】TuiThreadCmd(EvenSource)
人工智能·agent·opencode
TunerT_TQ6 小时前
Valhalla 静态工程审阅 #022|百度PaddlePaddle 源码证据驱动评测【大厂开源基础设施特辑】
人工智能·百度·开源·paddlepaddle·#深度学习·#飞桨