【AI应用开发】 Agent篇(三):规划推理:ReAct 与 Plan-Execute
前言
工具调用让 Agent 有了"手",但还是不够聪明------面对复杂任务,它需要知道先做什么、后做什么、遇到意外怎么办。
这就是"规划推理"(Planning & Reasoning)要解决的问题。本篇深入三种主流范式:ReAct(边想边做)、Plan-Execute(先规划后执行)、ReWOO(规划执行分离),每种附完整可运行代码,让你真正理解 Agent 的"思考"机制。
目录
- [为什么 Agent 需要规划](#为什么 Agent 需要规划)
- 1.1 简单任务 vs 复杂任务
- 1.2 规划的三种层次
- ReAct:边想边做
- 2.1 核心原理
- 2.2 手写 ReAct Agent
- 2.3 Thought/Action/Observation 循环剖析
- Plan-Execute:先规划后执行
- 3.1 核心原理
- 3.2 手写 Plan-Execute Agent
- 3.3 动态重规划
- ReWOO:规划与执行分离
- 4.1 为什么需要 ReWOO
- 4.2 手写 ReWOO Agent
- 范式对比与选择
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 批量执行
总结
- ReAct 是最通用的范式,每一步都经过"思考→行动→观察"循环
- Plan-Execute 适合步骤清晰的任务,先规划后执行更高效
- ReWOO 追求极致效率,LLM 只调用两次
- 生产实践中一般采用混合模式
- 手写 Agent 的价值:理解循环机制 > 会用框架