旅行前,你是先闭着眼睛冲上大街随便拦辆车,还是先打开地图查路线、订好酒店、列好景点清单再出发?显然,后者更靠谱——先规划,再行动。这就是今天要讲的Plan-and-Execute架构的核心思想:让AI Agent像一位资深旅行规划师那样,先把复杂任务拆成一份清晰的“旅行攻略”,再让经验丰富的“执行导游”按图索骥一步一步走完。规划器(Planner)负责动脑思考“该做什么”,执行器(Executor)负责动手实干“怎么做”。两者配合默契,Agent才能既看得远、又走得稳,在复杂的任务迷宫中不迷路。

岗位关联

掌握Plan-and-Execute架构,你就掌握了构建可靠智能体的底层思维——让AI从“想到哪做到哪”升级为“想清楚再动手”,这是走向生产级AI应用的关键一步。

目录
  1. 可视化演示
  2. 核心思想
  3. 规划器职责
  4. 执行器职责
  5. 与ReAct对比
  6. 失败处理
  7. 代码实现
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

动态演示Plan-and-Execu的核心机制,通过架构/数据/状态/性能四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Plan-and-Execu:Plan Plan-and-Execut 规划器的任务分解策略与计划生成 核心机制可视化 Plan-and-Execu整体架构与关键组件

核心思想

想象你是一家旅行社的老板,接待了一位想“玩遍云南”的游客。如果你不先规划路线,就让导游带着游客漫无目的地乱转——今天想到哪就去哪,一路上反复折返、错过航班、订不到酒店——游客早就投诉了。但如果你先花半天时间,由金牌规划师设计出一条“昆明→大理→丽江”的精品路线,把交通、住宿、景点耗时全部安排好,再交给执行导游按图索骥——旅行就会顺畅高效得多。

这正是Plan-and-Execute架构的核心思想:把“思考”和“行动”分离。规划器(Planner)负责全局思考——理解任务、拆解步骤、安排顺序;执行器(Executor)负责局部行动——专注完成眼前这一步。两者各司其职,就像旅行社里的规划师与执行导游的分工。

这种职责分离带来了两个关键优势:效率和可控性。效率体现在——Agent不再需要每行动一步就停下来问一次大模型“下一步该怎么办”,而是批量生成完整计划后连续执行;可控性体现在——计划本身就是一份可检查、可修改、可调整的中间产物,管理者能在执行前审核计划是否正确,就像出发前审核旅行路线图一样。

规划器像一位深谋远虑的军师,在战前就推演好整个战局的走向;执行器则像冲锋陷阵的将军,专注于打赢眼前这场仗。军师不亲自上场搏杀,将军不操心全局布置——这就是Plan-and-Execute的精髓。

总代价=C规划⏟一次性规划开销+∑i=1nC执行(si)⏟每个步骤的执行开销+n×C上下文⏟上下文传递开销\text{总代价} = \underbrace{C_{\text{规划}}}_{\text{一次性规划开销}} + \sum_{i=1}^{n} \underbrace{C_{\text{执行}}(s_i)}_{\text{每个步骤的执行开销}} + \underbrace{n \times C_{\text{上下文}}}_{\text{上下文传递开销}}
核心思想就是“一次规划,多次执行”,把思考的成本集中在前面花一次,后面的执行不再反复调用大模型“想”,而是机械地“做”。就像旅行出发前把路线讨论清楚,路上就不再纠结。
  1. 总代价公式表示整个任务完成的总开销由三部分构成——规划开销、各步骤执行开销、上下文传递开销的总和。
    C总=C规划+∑i=1nC执行(si)+n×C上下文C_{\text{总}} = C_{\text{规划}} + \sum_{i=1}^{n} C_{\text{执行}}(s_i) + n \times C_{\text{上下文}}
  2. 规划开销只需付出一次的大模型调用成本,把任务整体思考清楚,相当于旅行前查资料、做路线图的花费。
    C规划C_{\text{规划}}
  3. 执行步骤开销每个步骤执行时调用工具或模型的开销。因为不用每步都重新思考“该做什么”,所以比ReAct模式的单步开销小得多。
    ∑i=1nC执行(si)\sum_{i=1}^{n} C_{\text{执行}}(s_i)
  4. 上下文传递开销把前面的执行结果传给下一步所需的Token消耗。计划越细化,步骤数 $n$ 越多,这部分开销越大——这也是为什么规划粒度需要权衡。
    n×C上下文n \times C_{\text{上下文}}

规划器职责

规划器是Agent的“大脑”和“军师”。它的核心工作就像旅行规划师拿到游客的模糊需求“我想去云南玩”,需要把它拆解成一份可执行的详细行程单。规划器的输入是用户的原始任务(可能是模糊、笼统、复杂的),输出是一份结构化的任务清单——每一步做什么、按什么顺序做、依赖什么前置条件。

任务拆解是规划器的看家本领。一个好的规划器能像厨师切菜一样,把一整只鸡(复杂任务)分解成鸡胸、鸡腿、鸡翅(子任务),再安排烹饪顺序:先腌后炸、先炖后收汁。在技术实现上,规划器通过大模型进行任务分解,通常使用few-shot prompting(给模型看几个任务拆解的范例)来引导模型产出规范的JSON格式计划。计划中的每个单元是一个“步骤对象”,至少包含描述(做什么)、期望输出(产出什么)、以及可选的前置依赖。

规划器的第二项职责是顺序编排。有些步骤可以并行,有些必须串行——就像旅行中“订机票”和“订酒店”可以同时进行,但“确定行程天数”必须先于“预订住宿”完成。规划器需要识别这些依赖关系,在计划中标注清楚。当任务执行到一半发现某个子任务无法完成时,规划器还需要能够动态调整计划——用备选方案替换失败节点,就像航班取消后迅速改签高铁一样。

规划器就像一位严谨的导游,出发前把行程单打印得清清楚楚——几点起床、几点出发、去哪景点、吃什么饭。虽然路上可能有变化,但先有这份行程单,整个旅行就不会乱套。计划的价值不在于百分之百准确,而在于为执行提供了一个可控的框架。

P_{\text{plan}} = \{s_1, s_2, \ldots, s_n\}, \quads_i = \langle \text{desc}_i, \text{dep}_i, \text{status}_i \rangle
计划是一个有序步骤集合,每个步骤有描述、依赖和状态三个属性。规划器产出的最终交付物,就是这样一个结构化的JSON数组。
  1. 计划集合的定义规划器输出的计划是一个步骤集合 $P_{plan}$,包含从 $s_1$ 到 $s_n$ 共 $n$ 个步骤对象。
    Pplan={s1,s2,…,sn}P_{\text{plan}} = \{s_1, s_2, \ldots, s_n\}
  2. 单个步骤的三元组每个步骤 $s_i$ 包含三个核心属性——描述(desc)、依赖(dep)、状态(status)。
    si=⟨desci,depi,statusi⟩s_i = \langle \text{desc}_i, \text{dep}_i, \text{status}_i \rangle
  3. 描述字段描述这一步要做什么、期望产出什么,是执行器的工作指令。
    desci\text{desc}_i
  4. 依赖字段标明了此步骤依赖哪几个前置步骤的结果,用于确定执行顺序。
    depi\text{dep}_i
  5. 状态字段标记步骤的当前状态——待执行、执行中、已完成或失败,供执行器和规划器追踪进度。
    statusi\text{status}_i

执行器职责

如果说规划器是军师,那执行器就是前线冲锋的将军。执行器收到规划器制定的“作战计划”后,不再需要思考“我该做什么”——它只需要专注于“把眼前这一步做好”。这种专注带来了两个显著好处:一是减少了与大模型的交互轮数,节省了Token和时间;二是每一步的成败可以单独验证和记录,出了问题能精准定位。

执行器的日常工作就像流水线上的工人。计划传到他手里,他按顺序拿起一个步骤,调用相应的工具(如搜索、计算、写文件、调API),完成后再把结果返回。执行器需要处理的核心问题是工具调用——把步骤描述翻译成具体的工具操作。比如步骤是“查询北京明天的天气”,执行器就需要调用天气查询API,传入参数“北京”和“明天”。

执行器还有一项重要职责是结果校验。每完成一步,它需要判断结果是否满足步骤的预期输出要求。就像流水线上的质检员——生产出来的零件要量一量、测一测,不合格的不能流入下一道工序。如果结果不符合预期,执行器会标记该步骤失败,将错误信息反馈给规划器进行重新规划。

执行器的角色很像餐厅里的传菜员——菜单(计划)已经定好了,后厨(工具)也已经准备好了。传菜员不需要自己发明新菜,只需要把每道菜准确无误地送到客人桌上。虽然看似“机械”,但正是这份专注和分工,让整个系统跑得又快又稳。

Rstep=Executor(si,context),validate(Rstep)∈{pass,fail}R_{\text{step}} = \text{Executor}(s_i, \text{context}), \quad\text{validate}(R_{\text{step}}) \in \{\text{pass}, \text{fail}\}
执行器把每个步骤和上下文作为输入,调用工具执行后得到结果,再对结果做校验,判断是放行(pass)还是打回(fail)。
  1. 执行器调用执行器接收当前步骤 $s_i$ 和上下文信息,执行该步骤并返回结果。
    Rstep=Executor(si,context)R_{\text{step}} = \text{Executor}(s_i, \text{context})
  2. 步骤输入当前要执行的步骤,包含描述、依赖和状态信息。
    sis_i
  3. 上下文信息前面步骤产出的中间结果,在执行器中会被传递给工具作为参考或参数。
    context\text{context}
  4. 结果校验对执行结果进行验证,判断是合格(pass)还是不合格(fail),决定流程是继续还是中止。
    validate(Rstep)\text{validate}(R_{\text{step}})

与ReAct对比

理解Plan-and-Execute的最好方式,是把它和它的“竞争对手”ReAct模式放在一起比较。ReAct模式就像一位“行动派导游”——边走边想,到了一个景点发现没订门票,马上掏出手机现查方案;走了一半发现路线不合理,立刻现场改路线。这种模式非常灵活,但代价是频繁地停下来“思考”,每一步都要调用大模型决策一次,既费Token又费时间。

ReAct的工作方式是“思考-行动-观察”的循环:Agent观察当前环境,思考下一步该做什么,做出一个动作,观察结果,再进入下一轮思考。这个循环非常灵活,适合处理动态变化、无法提前预测所有情况的任务。但它的开销也很明显——每次循环都要调用大模型,而且为了让模型“记住”前面的过程,所有的思考过程和中间结果都要塞进上下文里,Token消耗像流水一样哗哗地流走。

而Plan-and-Execute走的是另一个极端:先花一次Token把整个任务想清楚,生成完整计划,然后闷头执行。它不需要每步回头问模型“我该怎么做”,省去了大量的决策开销。但代价是灵活性下降——如果中途出现计划之外的状况(比如执行器发现某个步骤根本无法完成),它需要停下来返回规划器重新规划,这个过程本身也有额外开销。选择哪种模式,本质上是一个关于“任务可预测性”的权衡:任务越简单、越固定,越适合Plan-and-Execute;任务越开放、环境越动态,ReAct越合适。

选哪种架构,就像选旅行方式一样“丰俭由人”。去楼下便利店买瓶水,不需要做攻略,直接去就行(ReAct的简单场景);但策划一次环球旅行攻略,你不可能边走边查——先规划再执行才是王道。聪明的开发者甚至会混合两者:先用Plan-and-Execute搭框架,遇到计划外的情况再临时切换到ReAct模式处理细节。

NLLM调用次数={1+k,Plan-and-Execute(规划1次 + 失败重规划k次)m,ReAct(思考-行动循环 m 轮)N_{\text{LLM调用次数}} =\begin{cases}1 + k, & \text{Plan-and-Execute(规划1次 + 失败重规划k次)}\\m, & \text{ReAct(思考-行动循环 m 轮)}\end{cases}
比较两种模式的本质差异:一个把决策集中在起点,一个把决策分散在整个过程中。就像“旅行前规划好一切”与“走到哪算哪”的两种旅行哲学。
  1. Plan-and-Execute的调用次数只需要规划1次,加上失败后重新规划的 $k$ 次,总共 $1+k$ 次大模型调用。
    1+k1 + k
  2. ReAct的调用次数每轮“思考-行动-观察”循环都需要调用大模型,总共 $m$ 轮。
    mm
  3. 对比结论当 $1+k < m$ 时,Plan-and-Execute更省Token;反之ReAct更划算。一般来说,任务越简单,$k$ 越小,Plan-and-Execute的优势越明显。
    1+k<m1+k < m

失败处理

旅途中难免遇到意外:航班延误、景点关闭、酒店超售。Plan-and-Execute架构中的失败处理机制,就像旅行社的应急响应预案——不指望路上永远一帆风顺,而是准备好一套成熟的应对流程。执行失败时的处理能力,恰恰是这套架构从“能跑”到“可靠”的分水岭。

当一个步骤执行失败时,系统首先要做的是错误分类。这就好比医生看病先要诊断是什么病——是工具调用失败(相当于航班取消)、结果格式不对(相当于酒店订错了日期)、还是验证没通过(相当于景点根本不存在)?不同类型的错误需要不同的应对策略。工具调用失败可能重试几次就好;而结果格式不对则说明步骤本身就有问题,可能需要修改计划而不是重试。执行器负责任务分类,把错误类型和详细信息返回给规划器。

接下来是重新规划流程。当执行器报告失败后,规划器会介入,审视当前的计划进度:已完成哪些步骤、失败在哪一步、后续步骤是否仍然有效。然后规划器会针对失败点进行调整:替换失败的步骤、重新安排后续顺序,或者判断整个任务不可行而直接终止。这个过程就像旅行中航班取消后,规划师重新查询高铁、调整后续行程,尽量保证游客的旅行体验不受影响。为了减少失败造成的损失,优秀的实现还会做错误恢复——尝试从失败点恢复而不是全部推翻重来,利用有效的中间结果,避免重复劳动。

优秀的Agent系统不怕出错,怕的是出错后手忙脚乱、把错误无限放大。就像经验丰富的导游遇到突发情况时面不改色地说:“没关系,我们改走B方案。” 失败处理机制让Plan-and-Execute系统有了“抗打击能力”——它不再是一次性买卖,而是一个能自我修复、持续迭代的可靠系统。

Recovery(si,fail_type)={retry(si,rmax⁡),工具临时错误reschedule(safter),步骤条件不满足replan(planrest),计划本身有问题\text{Recovery}(s_i, \text{fail\_type}) =\begin{cases}\text{retry}(s_i, r_{\max}), & \text{工具临时错误}\\\text{reschedule}(s_{\text{after}}), & \text{步骤条件不满足}\\\text{replan}(\text{plan}_{\text{rest}}), & \text{计划本身有问题}\end{cases}
针对不同的失败类型,系统做出不同的响应:临时错误就重试,条件不满足就调整后续顺序,计划本身有问题就重新规划剩余部分。分级应对,避免“一刀切”的粗暴处理。
  1. 重试机制对于工具临时错误(如网络超时),最多重试 $r_{max}$ 次,每次间隔逐渐增加。
    retry(si,rmax⁡)\text{retry}(s_i, r_{\max})
  2. 重新安排当步骤本身没问题,但前置条件发生变化(如某个API不再可用),则调整后续步骤的编排顺序或替换等价方案。
    reschedule(safter)\text{reschedule}(s_{\text{after}})
  3. 重新规划当多个步骤连续失败、说明原始计划有根本性缺陷时,放弃剩余计划,交给规划器重新生成。
    replan(planrest)\text{replan}(\text{plan}_{\text{rest}})

代码实现

理解了所有概念后,我们来看看代码实现。一个完整的Plan-and-Execute系统由三部分组成:Planner类(规划器)、Executor类(执行器)、以及将它们串起来的主循环(PlanExecutor)。我们的代码设计遵循单一职责原则——Planner只负责“想”,Executor只负责“做”,PlanExecutor只负责“调度”。这种清晰的层次结构让系统易于扩展和维护。

首先,Planner类负责接收任务描述、调用大模型生成计划、解析计划结果。在真实的工业级应用中,planner背后是LLM调用(比如GPT-4对一份few-shot prompt的响应),但在示例代码中,我们用简单的规则生成计划,保证可运行。Executor类负责执行单个步骤,返回结果;同时维护一个执行日志,记录每一步的成败。

主循环PlanExecutor则是整个系统的心脏:先让Planner生成计划,然后循环取出计划中的步骤交给Executor执行,每步检查执行状态,如果失败则调用Planner重新规划,直到全部完成。这个主循环就像旅行社调度台,统筹规划师和导游之间的配合,确保旅行顺利走完。代码中还处理了步骤状态(待执行、执行中、已完成、失败)的流转,这正是Plan-and-Execute在实际生产中的核心骨架。

代码是概念的最好老师。当你亲手实现一遍Plan-and-Execute,看到规划器产出的计划一步步被执行器消化、执行结果被逐项验证、失败后计划被动态调整——你才能真正理解“先规划再执行”这六个字的重量。它不只是一个架构模式,更是一种工程哲学:把复杂问题拆解为可控步骤,用结构对抗混乱,用分工提升效率。

PlanExecutor: plan←Plannertask→for each si∈plan:resulti=Executor(si),check→replan/finish\text{PlanExecutor: } \text{plan} \xleftarrow{\text{Planner}} \text{task} \rightarrow \text{for each } s_i \in \text{plan}: \text{result}_i = \text{Executor}(s_i), \text{check} \to \text{replan/finish}
主循环的逻辑:任务交给规划器得到计划,然后逐个执行计划中的步骤,每个步骤执行后检查成功与否——成功继续下一步,失败触发重新规划。这就是整个架构的发动机。
  1. 规划调用主循环先把任务交给Planner,得到完整计划。
    plan←Plannertask\text{plan} \xleftarrow{\text{Planner}} \text{task}
  2. 逐步骤执行遍历计划中的每个步骤,交给Executor执行。
    for each si∈plan\text{for each } s_i \in \text{plan}
  3. 执行结果检查检查步骤执行结果,通过则继续,失败则进入重新规划分支。
    check→replan/finish\text{check} \to \text{replan/finish}
  4. 终止条件所有步骤执行完成(finish),或规划器判断任务不可行提前终止。
    finish\text{finish}

AI 应用场景

企业智能客服工单处理

阶段:任务规划与执行

解决的问题:传统客服系统收到用户复杂的报障工单后,系统只能做关键词匹配,无法真正“理解”问题并系统地解决它。使用Plan-and-Execute后,规划器先将工单拆解为“问题分类→查询用户信息→检索知识库→生成解决方案→发送回复”等多个步骤,执行器按顺序逐步调用内部系统API,一气呵成完成整个处理流程。

具体案例:某电商平台的智能客服收到用户“我的订单上周五显示签收但没收到货”的投诉。规划器生成计划:1) 通过订单系统查询物流信息;2) 调用物流API确认签收详情;3) 自动发起快递员联系请求;4) 生成补偿方案。执行器按计划执行,整个过程用时3秒,无需人工干预,用户问题得到圆满解决。

自动化数据分析报告生成

阶段:数据任务编排

解决的问题:数据分析师每天需要花费数小时从多个数据源拉取数据、清洗、分析并生成报告,重复劳动且容易出错。Plan-and-Execute架构将整个分析流程编排为:数据抽取→数据清洗→统计分析→图表生成→报告撰写五个步骤,每个步骤由专门的执行器调用对应工具完成。

具体案例:某SaaS公司的运营团队每天需要查看前一天的销售数据。使用Plan-and-Execute驱动的数据分析Agent后,系统每天自动执行完整流程:从数据仓库抽取销售数据、过滤异常值、计算关键指标(GMV、转化率)、生成趋势图表、撰写结论摘要,最终自动推送一份完整的PDF报告到团队群。整个流程从原来人工的2小时缩短到3分钟。

Python 演示

python111 行
#!/usr/bin/env python3"""Plan-and-Execute:一个可运行的Agent规划执行架构示例"""import jsonimport time  class Planner:    """规划器:负责任务分解,生成步骤计划"""     PLANNER_PROMPT = (        "请把任务分解成最多{n_steps}个步骤,输出JSON数组。"        "每步包含desc和dep字段。"    )     def __init__(self, max_steps=5):        self.max_steps = max_steps        self.replan_count = 0     def generate_plan(self, task):        """根据任务生成步骤计划(模拟LLM规划)"""        print(f"[Planner] 收到任务: {task}")        # 模拟大模型规划,实际场景替换为LLM API调用        plan = [            {"desc": "收集需求信息", "dep": []},            {"desc": "设计解决方案", "dep": [0]},            {"desc": "实现核心功能", "dep": [1]},            {"desc": "测试与验证", "dep": [2]},        ]        return plan[: min(len(plan), self.max_steps)]     def replan(self, task, plan, failed_idx, error):        """执行失败后重新规划剩余步骤"""        self.replan_count += 1        print(f"[Planner] 第{self.replan_count}次重新规划,失败步骤: {failed_idx}")        # 模拟重新规划:保留已完成步骤,替换失败步骤        new_plan = []        for i, step in enumerate(plan):            if i < failed_idx:                continue            if i == failed_idx:                new_plan.append({"desc": f"重试-{step['desc']}", "dep": step["dep"]})            else:                new_plan.append(step)        return new_plan  class Executor:    """执行器:按顺序执行单个步骤,返回执行结果"""     def __init__(self):        self.executed_steps = []     def execute_step(self, step, context):        """执行一个具体步骤"""        desc = step["desc"]        print(f"[Executor] 执行: {desc}")        time.sleep(0.2)  # 模拟耗时操作        # 模拟偶尔失败用于演示错误恢复        if "收集" in desc and len(self.executed_steps) == 0:            self.executed_steps.append(desc)            return {"success": False, "error": "API超时"}        result = {"success": True, "output": f"{desc}完成"}        self.executed_steps.append(desc)        return result  class PlanExecutor:    """主循环:调度规划器和执行器完成整个任务"""     def __init__(self, planner, executor):        self.planner = planner        self.executor = executor        self.context = {"task": None, "results": []}     def run(self, task):        """启动Plan-and-Execute主循环"""        self.context["task"] = task        plan = self.planner.generate_plan(task)        print(f"[PlanExecutor] 初始计划共{len(plan)}个步骤")        self._execute_plan(plan)     def _execute_plan(self, plan):        """执行计划中的所有步骤,失败时触发重新规划"""        step_idx = 0        while step_idx < len(plan):            step = plan[step_idx]            result = self.executor.execute_step(                step, self.context["results"]            )            if not result["success"]:                print(f"[PlanExecutor] 步骤{step_idx}执行失败: {result['error']}")                plan = self.planner.replan(                    self.context["task"], plan, step_idx, result["error"]                )                step_idx = 0                continue            self.context["results"].append(result["output"])            step_idx += 1        print(f"[PlanExecutor] 任务完成! 执行结果: {self.context['results']}")  def main():    """主入口:实例化组件并执行一个示例任务"""    planner = Planner(max_steps=5)    executor = Executor()    agent = PlanExecutor(planner, executor)    agent.run("开发一个用户登录功能")  if __name__ == "__main__":    main()
目的:演示Plan-and-Execute架构的完整实现——Planner生成计划,Executor逐步骤执行,失败时触发重新规划。代码模拟了一个任务的规划、执行、失败重规划、再执行直到完成的全过程。
  1. planner.generate_plan(task)规划器根据任务生成初始计划,返回步骤列表。
  2. plan = plan[: min(len(plan), self.max_steps)]限制最大步骤数,防止计划过长无法控制。
  3. executor.execute_step(step, context)执行器执行单个步骤,所有中间结果传入上下文。
  4. result["success"]检查执行结果是否成功,这是判断是否需要重新规划的依据。
  5. planner.replan(task, plan, failed_idx, error)任务失败后调用规划器重新规划剩余步骤,替换失败节点。
  6. step_idx = 0重新规划后重置步骤索引,从头执行新计划中未完成的部分。
  7. agent.run("开发一个用户登录功能")主入口,演示整个流程从规划到执行的完整链路。

常见误区

误区我以为Plan-and-Execute架构要求规划器一次性生成完美无缺的计划,如果后续步骤失败,就说明这个Agent设计得很糟糕。所以在设计时总想尽办法把计划制定得极其详尽,结果反而导致规划Prompt极其复杂,频繁超时。

正解Plan-and-Execute的"Plan"部分并不是追求一次完美的静态计划。恰恰相反,它的核心优势之一就是有失败重规划机制——计划在执行中可以动态调整。就像旅行规划师不可能预测到每个天气变化,但好的旅行社会有B计划、C计划。执行中发现计划有缺陷,返回规划器重新规划,这正是架构设计的一部分,不是Bug而是Feature。

为何常见:我们日常理解的“计划”通常是静态的、确定性的(比如项目排期表),所以会天然地认为生成后就要“执行到底”。但Agent场景下的计划本质是动态的、可迭代的。

误区既然Plan-and-Execute省Token、效率高,那我所有Agent应用都应该用这个架构,抛弃ReAct。

正解架构选择取决于任务特性。Plan-and-Execute适合结构化、可预测、流程稳定的任务;ReAct适合探索性强、环境动态、信息开放的任务。对于“帮我查一下今天北京到上海的高铁”,直接用ReAct一步搞定;对于“帮我做一个市场分析报告”,先用Plan-and-Execute搭框架更稳妥。实际工业界甚至会混合使用——外层用Plan-and-Execute控流程,内层步骤里用ReAct处理灵活探索。

为何常见:人们容易陷入“非此即彼”的技术选型二元论,特别是看到Plan-and-Execute在某个Benchmark上跑得好,就认为所有场景都适用。

误区我觉得任务拆得越细,执行器每一步就越简单、越不容易出错,所以我把一个任务拆成了50个小步骤,每个步骤对应一次工具调用。

正解规划粒度过细会带来两个问题:一是上下文传递开销飙升——每个步骤的结果都要传给下一步,步骤越多Token消耗越大;二是规划器生成超长计划时更容易出错,而且错误一旦发生在早期步骤,后续所有步骤都受到影响。合理的粒度应该按照“可以独立验证产出”的边界来划分。比如“写一个Python脚本”是一个合理步骤,但“写def main()函数”再拆成“写第一行代码”就过于琐碎了。

为何常见:直觉上会觉得“步骤越简单越不容易失败”,但忘记了系统整体的Token开销和错误传播成本不降反升——这就像把一段100米的路拆成100段,每段确实很短,但走起来反而更累。

误区执行器发现某一步失败后,我直接把整个计划全部扔掉,让规划器重新生成一份全新计划,再从头执行一遍。

正解优秀的Plan-and-Execute系统应该利用已完成步骤的有效结果,只对失败的步骤及其后续受影响部分做局部调整。比如计划先“查资料”再“写报告”,查资料成功了、写报告时发现资料不够——这时候重新规划只需要补充一个“继续收集更多资料”的步骤,而不是把整个任务推倒重来。聪明的做法是维护一个已执行结果的缓存,让规划器知道哪些“已完成步骤”的产出可以复用。

为何常见:从零重规划的逻辑最简单,代码也容易实现,所以很多初学者会这么写。但生产环境中,每个步骤都可能调用了昂贵的API或消耗了大量时间,全量重来不仅浪费资源,还可能因为重新规划引入新的不确定性。

面试题

Q1. Plan-and-Execute架构中,规划器(Planner)的主要职责是什么?

单选题 · easy

  • A.逐个执行计划中的步骤并返回结果
  • B.将复杂任务分解为有序的步骤计划← 正确答案
  • C.直接调用各种外部工具完成用户请求
  • D.记录每步执行日志并生成性能报告

答案:B。规划器的核心职责是把复杂任务“想清楚”——分解为有序步骤计划。执行器(Executor)才负责逐步骤执行和工具调用。就像旅行规划师负责做攻略,执行导游负责带团走。

Q2. 关于Plan-and-Execute和ReAct模式的区别,下列说法正确的是?

单选题 · easy

  • A.ReAct模式先规划再执行,Plan-and-Execute边思考边行动
  • B.Plan-and-Execute的Token消耗通常高于ReAct模式
  • C.ReAct模式在动态变化场景下比Plan-and-Execute更灵活← 正确答案
  • D.Plan-and-Execute适用于不可预测的开放探索任务

答案:C。ReAct是“思考-行动-观察”循环,灵活但Token开销大,适合动态场景;Plan-and-Execute先规划再执行,效率高但灵活度有限,适合结构化任务。C选项描述准确。

Q3. 执行器在执行步骤时发现工具调用返回超时,最优的处理策略是?

单选题 · mid

  • A.立即终止整个任务并返回错误信息给用户
  • B.将超时错误分类为临时错误,尝试重试机制← 正确答案
  • C.忽略错误,把超时结果当作有效输出继续执行
  • D.直接调用规划器废弃全部计划重新规划整个任务

答案:B。工具超时属于临时性错误(网络抖动、服务短暂不可用),应该先分类错误类型再决定策略。优先重试几次;重试仍失败的才考虑重新规划。直接终止或忽略都是错误处理,全量重规划则浪费已完成的中间结果。

Q4. 下列哪些情况适合使用Plan-and-Execute架构?(多选)

多选题 · mid

  • A.需要按固定流程处理每天重复的报表任务← 正确答案
  • B.探索性研究问题需要不断调整思路方向
  • C.任务可分解为多个有依赖关系的子任务← 正确答案
  • D.环境信息开放需要频繁试错获取反馈

答案:AC。Plan-and-Execute适合结构化、可预测、流程稳定的任务(A和C)。B和D描述的是动态开放场景,更适合ReAct模式——就像跟团游适合常规路线,自由行适合探索发现。

Q5. 一个步骤执行失败后,规划器重新规划时,以下哪项做法最能节省资源?

单选题 · hard

  • A.丢弃全部计划生成全新计划
  • B.保留已完成步骤结果,仅调整失败点及后续步骤← 正确答案
  • C.让执行器无限重试直到步骤成功
  • D.跳过失败步骤,直接执行后续步骤

答案:B。好的错误恢复策略是最大化利用已完成的有效中间结果,只对失败步骤及其影响到的后续步骤做局部调整(B)。全量重规划浪费了昂贵的中间产出;无限重试在网络错误外的场景没有意义;跳过失败步骤会导致后续步骤缺少必要输入。

Q6. 在Plan-and-Execute架构中,执行器返回的验证结果有哪两种?

单选题 · easy

  • A.成功与失败← 正确答案
  • B.开始与结束
  • C.输入与输出
  • D.规划与重试

答案:A。执行器完成步骤后要校验结果,校验结论是二值的——pass(结果合格,放行到下一步)或fail(结果不合格,触发错误处理)。这是整个反馈闭环的关键节点。

Q7. 关于计划中步骤的粒度划分,下列说法合理的有?(多选)

多选题 · hard

  • A.粒度越细执行越简单,所以拆得越细越好
  • B.粒度应按照“可独立验证产出”的边界进行划分← 正确答案
  • C.粒度过细会增加上下文传递的Token开销← 正确答案
  • D.一次性生成超长计划容易导致规划器出错← 正确答案

答案:BCD。步骤粒度是一个权衡问题。合理的分隔边界是“能独立验证产出”(B);粒度过细导致每次执行结果都要传入上下文,Token开销增加(C);超长计划对LLM生成的稳定性和准确率都有负面影响(D)。A是常见误区,不是合理的粒度策略。

Q8. 在代码实现中,PlanExecutor主循环遇到步骤执行失败后,正确的处理动作是?

单选题 · mid

  • A.忽略错误继续执行后面的步骤
  • B.调用Planner的replan方法重新规划← 正确答案
  • C.直接抛出异常让程序崩溃退出
  • D.把失败标记为成功继续走完流程

答案:B。主循环的职责之一是错误调度——发现失败后调用Planner重新规划剩余步骤,然后按新计划继续执行。忽略或掩盖错误会让系统带着错误状态继续运行,最终产出错误结果;直接崩溃则完全不可用。

Q9. 相比纯ReAct模式,Plan-and-Execute在“效率”上的优势主要体现在哪方面?

单选题 · mid

  • A.每一步执行时都要重新加载上下文数据
  • B.大量减少了大模型在每步骤上的决策开销← 正确答案
  • C.每次重试时都要调用一次大模型进行思考
  • D.用更多轮次的循环换取更高的执行准确性

答案:B。ReAct每步都要“思考-行动-观察”循环调用大模型,而Plan-and-Execute把决策集中到规划阶段,执行阶段是机械执行。这节省了大量重复决策的Token和时间。想象旅行团出发前定好路线,路上就不用每个路口都打开地图重新找路。

Q10. 某一个子步骤连续重试3次仍然失败,且错误消息显示“所需数据源不存在”,此时应该执行什么策略?

单选题 · extreme

  • A.继续重试直到成功为止
  • B.将错误归类为条件性错误,触发重新规划← 正确答案
  • C.把本地缓存的数据当作正确答案使用
  • D.切换到大模型直接生成答案绕过工具调用

答案:B。“数据源不存在”是典型的条件不满足错误——重试不会让不存在的数据源突然出现。正确做法是归类为条件性错误,触发重新规划替换该步骤(比如换数据源或调整方案)。继续重试只会浪费资源;用缓存数据或让大模型猜测都是编造信息,违背可靠性原则。