Plan-and-Execute
本节定位
ReAct 很适合边走边看。
但当任务变长以后,它会遇到一个典型问题:
- 每一步都临场决定,容易飘
这时很多系统会换一种组织方式:
先用 planner 拆计划,再由 executor 按计划逐步完成。
这就是 Plan-and-Execute 的核心。
学习目标
- 理解 Plan-and-Execute 为什么适合长任务
- 理解 planner 和 executor 的职责分离
- 通过可运行示例看懂一个最小的“先规划再执行”系统
- 理解它和 ReAct 的差别与取舍
先建立一张地图
Plan-and-Execute 更适合按“高层先定路线,低层再跑步骤”来理解:
所以这节真正想解决的是:
- 为什么长任务不适合全程即兴
- 为什么计划层和执行层分开 后,系统会更稳
一、为什么任务一长,就更需要“先规划”?
1.1 边走边想容易丢全局
如果任务只有一步两步,
ReAct 的即兴决策通常够用。
但如果任务变成:
- 整理一周售后数据
- 统计高频问题
- 生成汇报
- 再给出改进建议
这类长任务就有更强的全局结构。
如果每一步都临时决定,
常见问题会是:
- 漏步骤
- 顺序错
- 做了重复工作
1.2 Planner 的作用:先把大任务变成小任务
Planner 最核心的价值不是“更聪明”,
而是:
- 先画路线图
它会回答:
- 一共有哪些步骤
- 步骤顺序是什么
- 哪些结果要传给后面
1.3 Executor 的作用:专心把当前步骤做好
把计划拆出来后,
执行器就可以少想一点“战略问题”,
更多关注:
- 当前步骤怎么完成
- 当前工具怎么调
- 当前结果怎么落库
这让系统更稳,也更容易调试。
1.4 一个更适合新人的总类比
你可以把 Plan-and-Execute 理解成:
- 先列施工清单,再安排工人按清单施工
如果没有施工清单,工人当然也能边做边想,
但任务一长就很容易出现:
- 漏步骤
- 顺序错
- 重复返工
这个类比很适合新人,因为它会把“planner / executor”重新拉回一个很日常的组织问题。
二、Plan-and-Execute 和 ReAct 的差别到底在哪里?
2.1 ReAct 更像边调查边走
它适合:
- 信息未知很多
- 下一步取决于上一轮 observation
2.2 Plan-and-Execute 更像先列施工清单
它适合:
- 任务结构比较清楚
- 步骤可以预先拆解
- 希望减少即兴漂移
2.3 两者不是敌对关系
很多真实系统其实会混用:
- 高层先 Plan-and-Execute
- 每个执行步骤内部再用 ReAct
也就是说:
- 规划负责全局
- ReAct 负责局部探索
2.4 一个很适合初学者先记的选择表
| 任务特点 | 更稳的第一反应 |
|---|---|
| 路径清楚、步骤多 | Plan-and-Execute |
| 信息未知多、边走边查 | ReAct |
| 既要全局规划,又要局部探索 | 两者混用 |
这个表很适合新人,因为它会把“该用哪种推理组织方式”变成一个能判断的问题。
三、先跑一个真正的最小 Plan-and-Execute 示例
下面这个例子会模拟一个“售后周报 Agent”。
用户任务是:
- 统计售后问题
- 找出高频意图
- 生成一份简短总结
我们会明确拆出:
- planner
- executor
tickets = [
{"intent": "refund", "text": "订单未发货,可以退款吗?"},
{"intent": "refund", "text": "退款多久到账?"},
{"intent": "password", "text": "忘记密码怎么办?"},
{"intent": "address", "text": "地址填错了还能改吗?"},
{"intent": "refund", "text": "退款为什么还没到账?"},
]
def planner(goal):
return [
{"step": "load_tickets", "description": "读取本周售后工单"},
{"step": "count_intents", "description": "统计各类问题数量"},
{"step": "find_top_intent", "description": "找出最高频问题"},
{"step": "draft_report", "description": "生成简短周报"},
]
def executor(task, context):
name = task["step"]
if name == "load_tickets":
context["tickets"] = tickets
return "已读取 5 条工单"
if name == "count_intents":
counts = {}
for item in context["tickets"]:
counts[item["intent"]] = counts.get(item["intent"], 0) + 1
context["intent_counts"] = counts
return counts
if name == "find_top_intent":
counts = context["intent_counts"]
top_intent = max(counts, key=counts.get)
context["top_intent"] = top_intent
return top_intent
if name == "draft_report":
counts = context["intent_counts"]
top_intent = context["top_intent"]
report = (
f"本周共处理 {len(context['tickets'])} 条售后工单。"
f"最高频问题是 {top_intent},出现 {counts[top_intent]} 次。"
f"建议优先优化 {top_intent} 流程和 FAQ 文案。"
)
context["report"] = report
return report
raise ValueError(f"Unknown step: {name}")
goal = "生成本周售后问题周报"
plan = planner(goal)
context = {}
trace = []
for task in plan:
output = executor(task, context)
trace.append({"task": task["step"], "output": output})
print("plan:")
for item in plan:
print("-", item)
print("\ntrace:")
for item in trace:
print(item)
print("\nfinal report:")
print(context["report"])
3.1 这段代码最关键的价值是什么?
它清楚分开了两件事:
- 规划
确定要做哪些步骤 - 执行
真正把步骤跑完,并把结果放进 context
这就是 Plan-and-Execute 最本质的结构。
3.2 context 在这里扮演什么角色?
它就是执行期的共享状态。
前一步产出的:
ticketsintent_countstop_intent
都会被后一步继续使用。
所以 Plan-and-Execute 的关键并不只是“有 plan”,
还包括:
- 中间产物怎样被安全传递
3.3 为什么这比单纯 for step in plan 更值得学?
因为这不是在演示一个循环,
而是在演示:
- 长任务如何拆分
- 依赖如何传递
- 最终结果如何逐步汇总
3.4 再看一个最小“计划检查表”示例
plan_quality = {
"steps_clear": True,
"order_defined": True,
"handoff_defined": False,
}
def next_fix(plan_quality):
if not plan_quality["steps_clear"]:
return "先把步骤描述写清楚。"
if not plan_quality["order_defined"]:
return "先明确执行顺序。"
if not plan_quality["handoff_defined"]:
return "先写清每一步产出怎样传给后一步。"
return "计划已经具备基本可执行性。"
print(next_fix(plan_quality))
这个示例很适合初学者,因为它会提醒你:
- 好计划不只是“列几个步骤”
- 还要考虑步骤之间的交接关系
四、Plan-and-Execute 什么时候特别有价值?
4.1 长任务
例如:
- 写报告
- 做研究总结
- 整理知识库
- 搭建多步骤业务流程
4.2 需要稳定复现的流程
如果你希望同类任务每次都按相近结构执行,
那显式计划会比完全即兴更稳。