上篇你给 Agent 装上了 ReAct 引擎——它能反复思考、反复调工具,直到任务完成。但面对复杂任务时,边做边想容易跑偏。这篇讲 Plan-and-Execute——先规划再执行,让 Agent 做事更有章法。
小明遇到了复杂任务
小明兴冲冲地给 Agent 下了一个任务:
你:调研 AI 编程工具现状,写一份简要报告
ReAct 模式的 Agent 开始工作了:
[步骤 1/5]
[调用工具]: web_search
[参数]: {"query": "AI 编程工具"}
[结果]: Copilot、Cursor、Codeium...
[步骤 2/5]
[调用工具]: web_search
[参数]: {"query": "AI 编程"}
[结果]: (跟上次类似...)
[步骤 3/5]
Agent:AI 编程工具主要包括 GitHub Copilot...(一段简短的总结)
小明皱眉头:”这就完了?它搜了两轮就给答案了,但我想的是——先搜工具列表 → 每个工具的详情 → 对比分析 → 写报告。它完全没按这个思路走。”
老张说:”ReAct 的特点是边做边想。模型在每一步判断’还需要什么信息’,但面对复杂任务时,这种自底向上的方式容易:
-
-
-
“那怎么办?”
“你需要的不是边做边想,而是先想清楚再做——先制定完整的执行计划,用户确认后,再按计划一步步执行。”
Plan-and-Execute 是什么
“Plan-and-Execute 把一个任务分成两个阶段:”
┌─────────────┐ ┌─────────────────────────────────┐
│ Planner │ │ Executor │
│ (规划者) │ │ (执行者) │
│ │ │ │
│ 拆解任务 │────→│ 步骤1:web_search "AI编程工具列表" │
│ 输出步骤清单 │ │ ↓ 拿到结果 │
│ │ │ 步骤2:web_search "Cursor功能详情" │
│ │ │ ↓ 拿到结果(带前序) │
│ 用户确认 │ │ 步骤3:web_search "Copilot对比" │
│ │ │ ↓ 拿到结果 │
│ │ │ 步骤4:整合撰写报告 │
└─────────────┘ └─────────────────────────────────┘
“跟 ReAct 的关键区别:”
| 维度 |
ReAct |
Plan-and-Execute |
| 策略 |
边做边想 |
先规划后执行 |
| 模型决策 |
每步决定下一步做什么 |
一次性决定全部步骤 |
| 可控性 |
依赖模型判断 |
用户可以审查计划 |
| 适合场景 |
单次复杂问答、动态任务 |
调研报告、多步分析 |
| 性能 |
单步等待时间长 |
计划可预览,执行可预测 |
| 失败处理 |
模型自适应调整 |
需要预设容错逻辑 |
小明懂了:”就像我写文章——先列大纲,确认方向对了,再逐段填充。”
“对。Plan-and-Execute 就是 Agent 的’先列大纲’。”
代码实战:Planner —— 只规划,不执行
老张打开 planner.py:
PLANNER_PROMPT = """你是一个任务规划专家。
用户给你一个复杂任务,把它分解成 3-6 个清晰、可执行的步骤。
可用工具:web_search(搜索)、get_weather(天气)、
calculate(计算)、get_current_time(时间)
要求:
- 每步要具体,能直接执行
- 需要工具的步骤标明工具名,不需要的填 null
- 步骤数控制在 3-6 步
只返回 JSON,不要其他文字:
{
"goal": "任务总目标一句话描述",
"steps": [
{"step": 1, "description": "具体步骤描述", "tool": "工具名或null"},
{"step": 2, "description": "具体步骤描述", "tool": "工具名或null"}
]
}"""
def make_plan(task: str) -> dict:
messages = [
{"role": "system", "content": PLANNER_PROMPT},
{"role": "user", "content": f"请为这个任务制定执行计划:{task}"},
]
response = chat(messages, tools=None)
text = response.content or ""
return json.loads(text)
“三个关键设计:
-
tools=None —— Planner 不调用任何工具。它只需要把任务拆成步骤清单,不需要执行。
-
每步声明工具名 —— 步骤里标注
"tool": "web_search" 或 "tool": null,这样 Executor 知道哪些步骤需要调工具、哪些步骤只需要模型推理。
-
JSON 输出 —— 结构化计划,方便程序解析和步骤遍历。”
“注意容错:万一 JSON 解析失败(模型输出多了几句废话),用正则兜底提取 {...}。如果连正则都救不回来,回退为单步计划——把整个任务当一步执行。”
代码实战:Executor —— 按计划逐步执行
“计划有了,接下来 Executor 按步骤清单逐步执行:”
def execute_plan(plan: dict) -> str:
goal = plan.get("goal", "未知任务")
steps = plan.get("steps", [])
collected: list[str] = []
for step_info in steps:
description = step_info["description"]
tool_name = step_info.get("tool")
if tool_name:
params = _infer_params_via_fc(
tool_name, description, goal, prior_results=collected
)
result = execute_tool(tool_name, params)
else:
msg = chat([{
"role": "user",
"content": f"请完成这个步骤:{description}"
}])
result = msg.content or ""
collected.append(f"步骤{step_num}({description}):\n{result}")
summary = chat([{
"role": "user",
"content": (
f"你完成了任务:{goal}\n\n"
f"以下是每个步骤的执行结果:\n{all_results}\n\n"
"请基于以上所有信息,给用户一个完整、清晰的最终答案。"
),
}])
return summary.content or ""
“Executor 的核心逻辑就三层:
-
-
-
汇总 → 所有步骤结果拼成一个大 prompt,让模型写最终报告”
“关键数据结构是 collected 列表——每执行完一步就把结果追加进去。这个列表有两个用途:
-
传给
_infer_params_via_fc(让下一步推断参数时能参考前序结果)
-
核心技术:只暴露一个工具的 FC
“这里有一个关键技巧——_infer_params_via_fc。”
小明问:”为什么不让模型直接选工具、定参数?像 ReAct 那样自然地 tool_calls?”
老张说:”因为在 Plan-and-Execute 里,选工具这件事 Planner 已经做完了。Executor 的阶段,工具已经定了——你只需要让模型推断参数。”
他打开 executor.py 的这个函数:
def _infer_params_via_fc(
tool_name: str,
step_description: str,
goal: str,
prior_results: list[str] | None = None,
) -> dict | None:
all_tools = get_tools_schema()
tools = [t for t in all_tools
if t["function"]["name"] == tool_name]
prior_block = ""
if prior_results:
joined = "\n---\n".join(prior_results[-3:])
prior_block = f"\n\n前序步骤结果(供参考):\n{joined}\n"
message = chat(
messages=[{
"role": "system",
"content": "你必须调用给定工具完成当前步骤,不要直接用文字回答。",
}, {
"role": "user",
"content": (
f"任务总目标:{goal}\n"
f"当前步骤:{step_description}\n"
f"请调用工具 {tool_name}。{prior_block}"
),
}],
tools=tools,
tool_choice={
"type": "function",
"function": {"name": tool_name}
},
)
if not message.tool_calls:
return None
return json.loads(message.tool_calls[0].function.arguments)
“三个关键点:
1. 只暴露一个工具
tools = [t for t in all_tools
if t["function"]["name"] == tool_name]
“如果暴露全部 4 个工具,模型可能选错。比如 Planner 说’用 web_search‘,但模型看到 get_weather 也在工具列表里,可能自作主张选 get_weather。只暴露一个工具 = 杜绝选错的概率。”
2. tool_choice 强制调用
tool_choice={"type": "function", "function": {"name": tool_name}}
“如果没有 tool_choice,模型可能不调工具、直接返回文字——比如’我觉得用 web_search 搜索 AI 工具比较好’。这等于没执行。
tool_choice={"type":"function","function":{"name":"web_search"}} 强制模型必须调用 web_search,必须产出合法的 JSON 参数。不调就报错——从协议层面保证执行。”
“注意:tool_choice 在有些模型上有限制。比如通义千问的 thinking 模式下不支持 tool_choice=required 或指定函数对象,需要临时关掉 thinking。代码里做了兼容:”
if tool_choice == "required" or isinstance(tool_choice, dict):
kwargs["extra_body"] = {"enable_thinking": False}
3. prior_results 传递前序信息
“假设计划是:
-
-
-
-
执行第 3 步时,如果不告诉模型前两步搜到了什么,模型可能:
prior_results 把前面的步骤结果拼接成 前序步骤结果(供参考) 区块,喂给模型:
前序步骤结果(供参考):
步骤1(搜索AI编程工具列表):
1. Copilot | ...
2. Cursor | ...
---
步骤2(搜索Cursor详细功能):
Cursor 支持多语言、内联补全、Agent模式...
模型看到这些上下文后,推断搜索参数会更有针对性。比如第3步它会搜 GitHub Copilot vs Cursor 而不是泛泛地再搜一遍 AI 编程工具。
注意只传最近 3 步的 prior_results[-3:],避免 context 过长。”
完整运行示例
“看一个完整的 /plan 运行过程:”
你:/plan 调研 AI 编程工具现状,写一份简要报告
正在制定计划...
目标:调研当前主流 AI 编程工具,分析功能特点并生成报告
共 5 步:
步骤 1:搜索 AI 编程工具列表和概况(工具:web_search)
步骤 2:搜索 GitHub Copilot 详细功能介绍(工具:web_search)
步骤 3:搜索 Cursor 和 Windsurf 功能介绍(工具:web_search)
步骤 4:搜索 AI 编程工具对比和趋势(工具:web_search)
步骤 5:整合信息,撰写简要报告
确认执行?(y/n):y
开始执行计划:调研当前主流 AI 编程工具...
────────────────────────────────
步骤 1:搜索 AI 编程工具列表和概况
[调用工具 web_search],参数:{'query': 'AI 编程工具 2025 现状 列表'}
[结果]: 1. GitHub Copilot | ... 2. Cursor | ... 3. Windsurf | ...
────────────────────────────────
步骤 2:搜索 GitHub Copilot 详细功能介绍
[调用工具 web_search],参数:{'query': 'GitHub Copilot 功能 代码补全 2025'}
[结果]: Copilot 支持多语言、Chat 模式、Agent 模式...
────────────────────────────────
步骤 3:搜索 Cursor 和 Windsurf 功能介绍
[调用工具 web_search],参数:{'query': 'Cursor IDE Windsurf AI 编程 功能对比'}
[结果]: Cursor 基于 VS Code、支持整个项目上下文...
────────────────────────────────
步骤 4:搜索 AI 编程工具对比和趋势
[调用工具 web_search],参数:{'query': 'AI 编程工具 对比 趋势 2025'}
[结果]: AI 编程市场快速增长,从补全到自主编程...
────────────────────────────────
步骤 5:整合信息,撰写简要报告
[AI 完成]: 这是一份关于 AI 编程工具现状的简要报告...
────────────────────────────────
[整合所有结果,生成最终答案...]
Agent:《AI 编程工具现状调研报告》
一、主流工具概览...
二、核心功能对比...
三、发展趋势...
四、推荐建议...
“对比之前 ReAct 模式的表现——搜两轮就给一段简短总结——Plan-and-Execute 的输出明显更结构化、更完整。”
小明问:”但如果第 2 步搜 Copilot 时失败了怎么办?”
“Executor 会捕获失败,标记为’步骤失败’,然后继续执行后续步骤。在最后的整合报告里告诉用户’第 2 步未成功获取 Copilot 详情’——任务不因单步失败而崩溃。”
两个模式,一个 CLI
老张打开 main.py:
while True:
user_input = input("你:").strip()
if user_input.startswith("/plan "):
task = user_input[6:].strip()
plan = make_plan(task)
print_plan(plan)
confirm = input("\n确认执行?(y/n):")
if confirm == "y":
print(f"\nAgent:{execute_plan(plan)}\n")
else:
print("已取消。\n")
continue
print(f"\nAgent:{agent.run(user_input)}\n")
“两种模式共存:
-
直接输入 → 单任务 ReAct(适合快速问答、动态决策)
-
/plan → Plan-and-Execute(适合调研报告、多步分析)
用户确认的环节很重要——复杂任务不要直接执行。让用户看完计划、觉得方向对了再执行,避免浪费 API 费用。”
“另外注意:这里的 ReAct 每次 run() 新建 memory,不跨轮。跨轮记忆要到 Day7 才实现。”
一个容易混淆的点
小明想了想:”Plan-and-Execute 里面也有 Function Calling,那跟 ReAct 里的 FC 有什么区别?”
老张在白板上画出来:
ReAct 里的 FC:
模型 → "我需要信息" → 看全部工具列表 → 选择 + 推断参数 → 执行
角色:工具选择者 + 参数推断者
Plan-and-Execute 里的 FC(Executor 中):
计划已规定工具 → 只看这一个工具 → 只推断参数 → 执行
角色:参数推断者(工具选择权已上交 Planner)
“区别在于决策权的分配:
-
ReAct:模型同时掌握’选什么工具’和’用什么参数’——权限大,但可能选错
-
Plan-and-Execute:Planner 先选好工具,Executor 只推断参数——权限拆分,更可控”
“这就是为什么 _infer_params_via_fc 只暴露一个工具 + tool_choice 强制——它不是在让模型’选工具’,而是在让模型’在已知工具上填参数’。”
总结
老张说:”从 ReAct 到 Plan-and-Execute,Agent 的决策方式发生了一次升维。”
“ReAct 是自底向上——遇到什么问题解决什么问题。Plan-and-Execute 是自顶向下——先把全局看清楚,再步步为营。”
| 问题 |
ReAct 的做法 |
Plan-and-Execute 的做法 |
| 怎么开始? |
走一步看一步 |
先列完整计划 |
| 用户怎么干预? |
无法干预中间过程 |
审查计划、确认后执行 |
| 步骤间怎么协同? |
消息链自然传递 |
prior_results 显式传递 |
| 失败了怎么办? |
模型自适应 |
标记失败,继续下一步 |
| 适合什么任务? |
动态决策、快速问答 |
调研报告、固定流程 |
“三个核心理解:
-
规划与执行分离 ——
make_plan() 只规划不执行,execute_plan() 只执行不规划。职责单一,各自干净
-
FC 角色转变 —— 从’选工具 + 推断参数’降级为’只推断参数’,通过只暴露一个工具 +
tool_choice 强制实现
-
/plan 是交互模式 —— 用户看计划 → 确认 → 执行,把人的判断力嵌入 Agent 工作流”
小明说:”我明白了。ReAct 和 Plan-and-Execute 不是非此即彼,是两种策略——简单的用 ReAct,复杂的用 /plan。”
“对。而且这两种模式可以组合——比如 /plan 的某个步骤内部,如果需要灵活决策,又可以嵌套 ReAct。”
“下一篇呢?”
“下一篇是整个系列的收尾——跨轮记忆的完整 Agent。把你学到的 Function Calling、工具注册表、MCP、记忆、ReAct 循环、Plan-and-Execute 全部串起来,做成一个真正的生产级 Agent。”
Agent 的进化路线:会调用工具 → 会自主决策 → 会规划执行。每一步都在从「执行者」走向「规划者」。