想象一下,李阿姨要装修一套老房子。她没有直接拎着锤子进场砸墙,而是先请设计师张工出了一整套施工图:哪面墙要拆、水电怎么走、瓷砖贴到多高、柜子打在哪里,全部画得明明白白。拿到图纸后,李阿姨才按顺序安排施工队进场——先是敲墙拆旧,再是水电改造,每道工序完成,监理都要验收签字,不合格立刻返工,全部通过才交付。这套“先出图纸、再分步施工、逐道验收”的思路,就是本期要讲的 Plan-and-Execute(规划执行)模式。 AI Agent 处理复杂任务时,最容易犯的毛病就是“拎着锤子就砸墙”——拿到问题直接开干,结果干到一半发现方向错了,或者漏掉了关键步骤。Plan-and-Execute 模式让 Agent 先调用大模型做整体规划,把大任务拆成有依赖关系的小步骤,再逐个执行、逐项校验、失败重试。就像装修有设计师和监理把关一样,Agent 的每一步都有据可查、有错可纠。本文会用一个贯穿全文的“装修故事”,带你从零搭建一个可用的规划执行系统。

岗位关联

对 AI 从业者来说,Plan-and-Execute 是 Agent 工程化落地最实用的架构之一。掌握它,你就能让模型稳定地完成多步骤复杂任务,这是从“会调 API”走向“能交付产品”的关键一跃。

目录
  1. 可视化演示
  2. 模式概述
  3. 核心流程
  4. 任务分解
  5. 工具调用
  6. 结果校验
  7. 失败重试
  8. 实践指南
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Plan-and-Execute模式三大组件Planner/Executor/Validator协作流程、任务分解策略与指数退避容错重试机制。

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

Plan-Execute-Validate三阶段架构 Planner将目标分解为可执行步骤序列 Executor逐步调用工具执行每个子任务 Goal → Plan → Execute → Validate → Result Validator校验中间结果决定是否继续或重试

模式概述

李阿姨装修前为什么要先找设计师?因为如果让施工队边干边想,很可能出现“瓷砖贴完了才发现插座位置留错了”的惨剧。同样,如果让 AI Agent 拿到任务就直接开干,大模型在每一步都要重新理解全局目标,很容易迷失方向。Plan-and-Execute 模式的精髓,就是让规划与执行解耦:先用一次推理把任务想清楚,再按计划逐步落地。

Plan-and-Execute(简称 PnE)是 Agent 的一种架构模式,与另一主流模式 ReAct(推理+行动交替进行)形成鲜明对比。如果用装修打比方,ReAct 是“边想边干”——瓦工贴几块砖,停下来想想图纸,再贴几块;而 PnE 是“先出完整图纸,再按图施工”。前者灵活但容易跑偏,后者稳定但牺牲了部分随机应变能力。因此 PnE 特别适合目标明确、步骤清晰的多步骤任务,比如数据分析报告生成、竞品调研、批量文档处理。

PnE 的核心优势有三点:第一,全局视野——规划时能看到整个任务的形状,避免遗漏关键步骤;第二,可追踪——每一步执行结果都能对应到计划中的具体位置,出了问题能精准定位;第三,可复用——规划器产出的计划可以缓存复用,同一类任务不必反复规划。当然,它也有短板:如果任务本身高度动态、需要根据中间结果频繁调整方向,PnE 就不如 ReAct 灵活。理解了这些,你就掌握了选型的天平。

回到装修故事:设计师出了图纸,施工队照图施工,每道工序独立验收。如果某道工序出了问题,只需要看图纸上对应那一项,返工也只需重做那一部分,不会牵连全局。这就是 PnE 最朴素的智慧——把不确定性锁进规划阶段,把确定性留给执行阶段。

PnE=Planner(T)⏟出图纸→{s1,s2,…,sn}→⋃i=1nExecutor(si)⏟按图施工\text{PnE} = \underbrace{\text{Planner}(\mathcal{T})}_{\text{出图纸}} \rightarrow \{s_1, s_2, \ldots, s_n\} \rightarrow \displaystyle\bigcup_{i=1}^{n}\underbrace{\text{Executor}(s_i)}_{\text{按图施工}}
这个式子描述 PnE 的宏观骨架: Planner 把任务 $T$ 变成子任务集合,然后 Executor 逐个执行子任务。“出图纸”和“按图施工”是同一件事的两个阶段,各司其职。
  1. Planner(T) 规划器接收原始任务 $T$,相当于设计师根据业主需求画图纸。大模型在这里一次性完成全局思考,输出结构化的子任务列表。
    Planner(T)={s1,s2,…,sn}\text{Planner}(\mathcal{T}) = \{s_1, s_2, \ldots, s_n\}
  2. 子任务集合$\{s_1, s_2, \ldots, s_n\}$ 是规划产物,每个 $s_i$ 是一个独立可执行的子任务,相当于图纸上的每一道工序。
    {s1,s2,…,sn}\{s_1, s_2, \ldots, s_n\}
  3. 逐个执行$\bigcup$ 符号表示对每个子任务依次调用 Executor,得到各自的执行结果,再汇总成最终答案。
    ⋃i=1nExecutor(si)\displaystyle\bigcup_{i=1}^{n}\text{Executor}(s_i)

核心流程

装修的完整流程是:设计师出图 → 施工队进场 → 监理验收 → 交付钥匙。PnE 的 Agent 走的也是同样的四步:规划 → 执行 → 校验 → 输出。这四步构成了一个完整的任务生命周期,每一步都有明确的输入输出,就像装修的每个阶段都有明确的交付物。

第一步是规划阶段。Agent 拿到用户的任务描述,调用大模型生成一份“施工计划”——通常是一个 JSON 数组,每个元素包含步骤名、所需工具、输入参数。这一步的关键是把模糊的大目标翻译成具体的行动指令。第二步是执行阶段,Agent 遍历计划中的每一步,调用对应的工具函数,如同施工队拿出电钻、水平仪各司其职。第三步是校验阶段,Agent 检查每一步的输出是否符合预期——好比监理拿着卷尺量瓷砖缝隙是否达标。第四步是输出阶段,所有步骤通过后,把中间结果汇总成最终答案交还用户。

这四个阶段在代码层面有清晰的边界。规划器只负责生成计划,执行器只负责调用工具,校验器只负责判断对错。这种“单一职责”的设计让系统变得容易调试:如果结果不对,先看是规划错了(图纸画错了),还是执行错了(施工队干坏了),还是校验漏了(监理没发现)。实践中,这四个阶段可以用一个简单的状态机来驱动。

李阿姨看着流程图就明白了:装修公司不是把钥匙直接交给她就完事,中间必须有监理这道关口。PnE 的“校验阶段”就是 Agent 的监理——没有这道关口,前面做得再快也可能是白忙。流程的价值不在于每一步多精彩,而在于每一步之间有人把关。

Task→PlanPlan→ExecuteResult→VerifyFinal\text{Task} \xrightarrow{\text{Plan}} \text{Plan} \xrightarrow{\text{Execute}} \text{Result} \xrightarrow{\text{Verify}} \text{Final}
四个箭头对应四次状态转移:任务变成计划,计划变成结果,结果经过校验变成最终输出。任何一步失败,状态机就回退到重试分支。
  1. Plan 状态转移$\text{Task} \xrightarrow{\text{Plan}} \text{Plan}$ 表示原始任务通过规划器变成可执行的步骤列表,这是从“模糊”到“清晰”的转化。
    Task→PlanPlan\text{Task} \xrightarrow{\text{Plan}} \text{Plan}
  2. Execute 状态转移$\text{Plan} \xrightarrow{\text{Execute}} \text{Result}$ 表示计划中的每个步骤被逐个执行,产出中间结果。
    Plan→ExecuteResult\text{Plan} \xrightarrow{\text{Execute}} \text{Result}
  3. Verify 状态转移$\text{Result} \xrightarrow{\text{Verify}} \text{Final}$ 表示中间结果经过校验、必要修正后,变成最终回答。
    Result→VerifyFinal\text{Result} \xrightarrow{\text{Verify}} \text{Final}

任务分解

设计师拿到户型图后,第一件事是把整个装修拆成可以发包的工程包:拆旧、水电、泥瓦、木工、油漆、软装。这个拆解的颗粒度很有讲究——拆得太粗,一个工程包里塞了太多活,施工队干着干着就乱了;拆得太细,光协调交接就要花掉大量时间。任务分解(Task Decomposition)是 PnE 模式中最考验“功力”的一环。

分解原则可以概括为三条:独立性(每个子任务尽量不依赖其他子任务的中间状态)、可验证性(每个子任务都有明确的完成标准)、适当粒度(子任务的大小要匹配单次 LLM 调用的能力边界)。比如“写一篇市场分析报告”可以拆成“收集竞品信息→分析价格带→总结趋势→撰写报告”,而不是拆成“打开电脑→新建文档→输入标题”这种过细的步骤。

在实现层面,任务分解通常有两种策略。一种是模板驱动式:针对常见任务类型预设分解模板,比如“报告生成 = 调研 + 大纲 + 撰写 + 校对”。另一种是自由式:直接让大模型根据目标任务现场拆解,适合没有先例的新任务。实际系统中常把两者结合——先用模板兜底,再允许 LLM 微调。无论哪种策略,都要注意子任务间的依赖关系,有的步骤必须等前置步骤完成才能开始,这就像必须等水电改造完才能贴瓷砖。

李阿姨和设计师最终把工程定成了 6 个包,而不是 60 个或 3 个——因为经验告诉他们,6 个包既能明确责任,又不用整天开会协调。任务分解的智慧,就是找到那个“不多不少”的颗粒度,让每个子任务都能独立检验、独立交付。

Costtotal=∑i=1nCost(si)⏟各步骤成本之和+Costoverhead×(n−1)⏟步骤间协调成本\text{Cost}_{\text{total}} = \underbrace{\sum_{i=1}^{n} \text{Cost}(s_i)}_{\text{各步骤成本之和}} + \underbrace{\text{Cost}_{\text{overhead}} \times (n-1)}_{\text{步骤间协调成本}}
任务分解的总成本是“各子任务成本之和”加上“步骤间协调成本”。子任务拆得越多,协调成本越高——这就是为什么分解不是越细越好。
  1. 各步骤成本之和每个子任务 $s_i$ 执行一次需要消耗 Token 或时间,把它们加起来就是所有步骤的直接成本,相当于每道工序的工钱总和。
    ∑i=1nCost(si)\sum_{i=1}^{n} \text{Cost}(s_i)
  2. 协调成本$\text{Cost}_{\text{overhead}} \times (n-1)$ 中,$n-1$ 是步骤之间的交接次数。每次交接都需要传递上下文、检查状态,就像每两道工序之间要开一次碰头会。
    Costoverhead×(n−1)\text{Cost}_{\text{overhead}} \times (n-1)
  3. 总成本模型两部分相加才是真实总成本。这提醒我们:分解粒度要选在“直接成本 + 协调成本”最小的那个平衡点。
    Costtotal=∑i=1nCost(si)+Costoverhead×(n−1)\text{Cost}_{\text{total}} = \sum_{i=1}^{n} \text{Cost}(s_i) + \text{Cost}_{\text{overhead}} \times (n-1)

工具调用

施工队进场后,电工要用测电笔,瓦工要用水平仪,木工要用切割机——每个工种都有自己趁手的工具。对应到 Agent 里,每个子任务也需要调用不同的工具函数:查数据库、调 API、做计算、搜网页。工具调用(Tool Calling)是执行阶段的核心技术,它让 Agent 从“只会动嘴”变成“还能动手”。

工具调用有一套标准的实现流程。第一步是工具注册:把所有可用的工具函数登记到一个注册表中,每个工具有一个名字、一段描述和参数 Schema,就像工地上每个工具箱贴着标签写明用途。第二步是参数解析:执行器拿到子任务中的工具名和参数后,从注册表找到对应的函数,验证参数格式是否正确——好比翻出电钻说明书,确认电压匹配再插电。第三步是调用编排:按计划顺序依次调用工具,前一个工具的输出可能作为后一个工具的输入,形成一条流水线。

在代码层面,工具注册表通常是一个字典结构:键是工具名,值是函数对象及其元信息。当大模型规划出一个步骤“调用 search 工具,查询关键词为 X”时,执行器只需要从注册表取出 search 函数,把参数传进去,拿到返回值。这个过程看似简单,但有一个关键设计决策:让大模型决定调什么工具,但让代码决定怎么调工具。大模型负责语义理解(选哪个工种),代码负责安全执行(怎么操作工具),各管一摊。

装修工地上,每种工具都放在固定位置、有明确的保养标准,工人们不会临时去五金店现买。Agent 的工具注册表也是如此——工具清单越规范,执行越不容易出错;参数校验越严格,运行时越少意外。

ri=Toolname(si)⏟按工具名匹配函数=Registry[name](si.args)r_i = \underbrace{\text{Tool}_{\text{name}}(s_i)}_{\text{按工具名匹配函数}} = \text{Registry}[\text{name}](s_i.\text{args})
第 $i$ 个子任务的执行结果 $r_i$,等于从注册表中取出对应工具函数、传入该步骤参数后的返回值。工具注册表是“工具名 → 函数”的映射关系。
  1. 按名取函数$\text{Registry}[\text{name}]$ 表示用工具名作为键,从注册表字典中取出真正的函数对象,相当于按工具箱标签取工具。
    Registry[name]\text{Registry}[\text{name}]
  2. 传入参数$(s_i.\text{args})$ 表示把子任务 $s_i$ 里携带的参数传给工具函数,相当于把砖块递给瓦工。
    (si.args)(s_i.\text{args})
  3. 得出结果整个表达式 $\text{Registry}[\text{name}](s_i.\text{args})$ 的值就是工具调用的返回结果 $r_i$,它是后续校验和汇总的输入。
    ri=Registry[name](si.args)r_i = \text{Registry}[\text{name}](s_i.\text{args})

结果校验

监理老周每天的工作就是在各工种完工后,拿着验收单逐项打勾:水电管路压力测试达标了吗?瓷砖空鼓率在允许范围内吗?每一项都合格,才签“同意进入下一道工序”。PnE 模式中的结果校验(Result Validation)扮演的正是老周的角色——不信任任何一步的默认成功,用明确的标尺量过才算数。

校验的时机有两种选择:一种是在所有步骤执行完后统一校验,另一种是每执行完一步立即校验。后者的成本更高,但能尽早发现问题——就像瓷砖贴完一面墙就检查一面,比全部贴完再检查能省下大量返工成本。校验标准可以是规则化的(结果长度大于 N、包含指定字段、JSON 格式正确),也可以让大模型来判(输出是否满足用户原始需求)。实践中推荐规则优先、模型兜底的双层校验,因为规则校验便宜且稳定,复杂语义校验则交给大模型。

更精细的设计是部分通过机制:某些步骤的校验不是简单的通过/失败,而是部分通过——比如搜索结果的相关性得分 0.6 分,离 0.8 的及格线还差一点,但不需要完全重做,可以带着这个部分结果继续,同时标记一个“风险提示”。这就像监理发现瓷砖有两块空鼓,不需要全部敲掉重贴,只需要局部返工。校验结果可以反馈给规划器,帮助它修正后续步骤的输入参数——形成“执行→校验→反馈→修正”的闭环。

监理老周的验收单,就是装修界的“校验标准”。没有他签字,施工队拿不到下一阶段的工钱。Agent 也一样:没有校验器的把关,每一步执行结果都可能带着隐患流向下游,最终酿成整体失败。校验不是多余的谨慎,而是质量的守门员。

Pass(ri)={1,if score(ri)≥θ合格0,if score(ri)<θ不合格\text{Pass}(r_i) = \begin{cases} 1, & \text{if } \text{score}(r_i) \geq \theta \quad \text{合格} \\ 0, & \text{if } \text{score}(r_i) < \theta \quad \text{不合格} \end{cases}
校验器的输出是一个二值判断:如果结果得分超过阈值 $\theta$ 就放行,否则打回重做。$\theta$ 是校验标准的具体量化,相当于验收单上的合格线。
  1. 打分函数$\text{score}(r_i)$ 是对第 $i$ 步结果 $r_i$ 的质量评分。规则校验里它可以是一个布尔判断,模型校验里它可以是一个 0-1 分数。
    score(ri)\text{score}(r_i)
  2. 阈值比较$\text{score}(r_i) \geq \theta$ 是核心判断:达到或超过合格线 $\theta$ 就算合格。$\theta$ 的值根据任务类型调节,比如摘要任务要求长度≥50字,数据任务要求字段齐全。
    score(ri)≥θ\text{score}(r_i) \geq \theta
  3. 二值输出整个分段函数输出 1(通过)或 0(不通过),这种简单的信号方便上层逻辑做分支决策。
    Pass(ri)={1,⋯0,⋯\text{Pass}(r_i) = \begin{cases} 1, & \cdots \\ 0, & \cdots \end{cases}

失败重试

就算有设计师的图纸和监理的验收,装修也不可能一帆风顺——可能遇到墙体里藏着钢筋、水管位置偏移、材料临时缺货。施工队遇到问题怎么办?不是当场崩溃,而是有一套应对预案:先判断问题严重性,能局部返工就局部返工,返工还不行就换方案,方案全失效才停工求救。Agent 的失败重试(Retry)机制,就是为执行过程中的异常准备的“应急预案”。

失败识别是重试的第一步。系统需要区分三类失败:工具调用异常(API 超时、参数错误)、结果校验不通过(输出格式不符合预期)、大模型自身生成错误(输出无法解析)。不同类型的失败对应不同的重试策略。重试策略的核心参数是重试次数上限和退避策略:最简单的固定重试——失败后等 1 秒再试;更合理的是指数退避——第 n 次重试等待时间按指数增长,避免在系统抖动时反复踩踏;还可以在重试时换参数甚至换模型,就像瓦工发现这种砖贴不牢,换一种粘结剂。

更高级的设计是降级方案:某个工具连续失败后,用备用工具代替。比如主搜索 API 挂了,就切换到备用搜索源;向量数据库超时了,就退化为关键词检索。熔断机制则是在失败率达到阈值时,暂停整个 Agent 的执行,不再盲目重试,而是直接告知用户“当前任务无法完成,建议修改需求或稍后再试”——就像装修公司发现房子承重墙有问题,果断停工通知业主,而不是硬着头皮继续。

李阿姨的装修合同中明确写着“每道工序返工不超过两次,超过则更换施工方案”。这份合同就是重试策略的文字化。Agent 的失败重试机制,本质上就是把“意外”当作流程的一部分提前设计好,让系统在不确定环境中依然能收敛到可用结果。

an=min⁡(amax⁡⏟上限,a0×2n−1⏟指数增长),n=1,2,3,…a_n = \min\left(\underbrace{a_{\max}}_{\text{上限}}, \underbrace{a_0 \times 2^{n-1}}_{\text{指数增长}}\right), \quad n=1, 2, 3, \ldots
这是指数退避的等待时间公式:第 $n$ 次重试前等待 $a_0 \times 2^{n-1}$ 秒,但不超过上限 $a_{\max}$。重试越频繁,间隔越长,给系统留出恢复时间。
  1. 基础等待第 $n$ 次重试的等待时间从 $a_0$(初始值,比如 1 秒)开始,每次翻倍,重试次数越多等待越久。
    a0×2n−1a_0 \times 2^{n-1}
  2. 封顶限制$\min$ 操作确保等待时间不超过 $a_{\max}$(比如 30 秒),避免无限期等待,保证了系统响应时间可控。
    min⁡(amax⁡,a0×2n−1)\min(a_{\max}, a_0 \times 2^{n-1})
  3. 指数退避的意义重试间隔呈指数增长,防止 Agent 在系统故障时高频重试造成“二次伤害”(像一群人在塌了的桥上反复踩)。
    an=min⁡(amax⁡,a0×2n−1)a_n = \min(a_{\max}, a_0 \times 2^{n-1})

实践指南

有了前面的理论铺垫,我们来搭一个真实的 Plan-and-Execute 系统。以一个“文章摘要生成助手”为例,完整的代码框架包含四个核心类:Planner(规划器)、Executor(执行器)、Validator(校验器)、Agent(总控)。核心循环只有十几行:解析任务 → 规划 → 循环执行与校验 → 汇总输出。

设计经验上最重要的三条:第一,规划时给每个步骤配好工具和参数示例,这能显著降低执行阶段的解析错误率;第二,执行器的异常捕获要细粒度,区分“工具本身报错”和“返回结果异常”两种情形,前者重试、后者走校验;第三,在 Agent 入口处设置全局超时与重试上限,防止某个步骤卡死拖垮整个任务。常见的陷阱包括:规划步骤之间隐式依赖未声明(漏了前置步骤)、校验标准过于宽松(验收形同虚设)、重试时没有更新上下文(返工时拿着旧图纸)。

优化方向可以从三个角度展开:一是并行化——没有依赖关系的子任务可以同时执行,像瓦工和木工在不同房间同时施工;二是缓存复用——相同参数的规划结果和执行结果可以缓存,下次直接命中;三是动态重规划——当校验发现某一步无论如何都无法通过时,触发规划器重新规划后续步骤,而不是一味重试。这些优化能把 PnE 系统从“能跑”推向“高效稳定地跑”。

李阿姨通过这次装修总结出经验:图纸要画清楚,工序要排合理,验收要真较真,返工要有预案。把这四条翻译成工程师的语言,就是规划清晰、分解得当、校验严格、重试有度——这就是 Plan-and-Execute 模式的全部心法。

Ttotal=Tplan+∑i=1n(Texec(si)+Tverify(si)+Tretry(si))T_{\text{total}} = T_{\text{plan}} + \sum_{i=1}^{n}\left(T_{\text{exec}}(s_i) + T_{\text{verify}}(s_i) + T_{\text{retry}}(s_i)\right)
总耗时 = 规划时间 + 每个子任务的执行、校验、重试耗时之和。这一公式揭示了优化的切入点:想加速,就把重试率降下来,或把无依赖的步骤并行掉。
  1. 规划时间$T_{\text{plan}}$ 是调用规划器生成计划的那次大模型推理耗时。它只发生一次,所以占比不大。
    TplanT_{\text{plan}}
  2. 单步耗时构成$T_{\text{exec}}(s_i) + T_{\text{verify}}(s_i)$ 是第 $i$ 步“干活 + 验收”的时间,这是每个子任务的固定开销。
    Texec(si)+Tverify(si)T_{\text{exec}}(s_i) + T_{\text{verify}}(s_i)
  3. 重试叠加$T_{\text{retry}}(s_i)$ 是第 $i$ 步失败重试消耗的额外时间。把这一项压缩到最低,是优化系统延迟最有效的手段。
    ∑i=1n(Texec(si)+Tverify(si)+Tretry(si))\sum_{i=1}^{n}\left(T_{\text{exec}}(s_i) + T_{\text{verify}}(s_i) + T_{\text{retry}}(s_i)\right)

AI 应用场景

场景一:竞品调研报告自动生成

阶段:Agent 应用开发 → 任务规划与执行

解决的问题:传统竞品调研靠人工手动收集信息、整理数据、撰写报告,一个完整调研常要 2-3 天。AI Agent 借助 PnE 模式,先把“调研任务”拆成“搜索竞品官网信息→抓取用户评价→对比功能列表→生成分析报告”四个子任务,再逐步调用搜索工具、爬虫工具和生成模型,全程无需人工干预。底层逻辑就是本文讲的“先出图纸、再施工、逐项验收”。

具体案例:某 SaaS 公司的产品经理输入“分析我们与三家竞品的差异”,PnE Agent 自动搜索三家竞品官网和第三方测评,校验每步结果完整性,最终输出一份带数据对比表的报告,耗时从 2 天缩短到 15 分钟。

场景二:批量合同审查与风险标注

阶段:工具调用编排 → 结果校验

解决的问题:法务团队每天要审查大量合同,重复性高且容易漏看关键条款。PnE Agent 将“审查一份合同”规划为“解析合同结构→提取关键条款→比对风险规则库→生成审查意见”四个步骤,每步调用不同的工具:PDF 解析器、条款提取模型、风险规则引擎。校验器会检查每条风险点是否附有原文引用,缺失则触发重提取。

具体案例:某律所部署 PnE Agent 处理供应商合同,将原先每人每天 20 份的处理量提升到 200 份,且风险条款召回率达到 95%。得益于失败重试机制,遇到扫描件质量差导致解析失败时,Agent 会自动切换 OCR 模型重试,而不是中断整个批次。

Python 演示

python135 行
"""Plan-and-Execute 最小可运行示例:文章摘要助手"""import timeimport random  class ToolRegistry:    """工具注册表:按名字存放和查找工具函数"""     def __init__(self):        self.tools = {}     def register(self, name, func):        self.tools[name] = func     def call(self, name, args):        if name not in self.tools:            raise ValueError(f"未知工具: {name}")        return self.tools[name](args)  def tool_summarize(args):    text = args["text"]    return f"{text[:15]}...(已压缩摘要,长度={min(50, len(text))})"  def tool_keywords(args):    return "ai, agent, plan, execute"  def tool_format(args):    return "# 摘要\n\n- 核心观点一\n- 核心观点二"  def tool_check(args):    # 模拟一次可能失败的校验工具    if random.random() < 0.3:        raise RuntimeError("模拟故障:校验服务暂时不可用")    return "校验通过:结构完整,要点齐全"  class Planner:    """规划器:把大任务拆成有序子步骤"""     def __init__(self):        self.plan_count = 0     def plan(self, task):        self.plan_count += 1        return [            ("summarize", {"text": task}),            ("keywords", {"text": task}),            ("format", {"style": "markdown"}),            ("check", {"max_len": 200}),        ]  class Executor:    """执行器:逐个调用工具完成子步骤"""     def __init__(self, registry):        self.registry = registry        self.exec_count = 0     def execute(self, step_name, args):        self.exec_count += 1        return self.registry.call(step_name, args)  class Validator:    """校验器:检查执行结果是否满足最低要求"""     def verify(self, step_name, result):        if not result or len(str(result)) < 5:            return False        if step_name == "keywords" and "ai" not in result:            return False        return True  class PlanAndExecuteAgent:    """Agent 总控:编排规划、执行、校验、重试"""     def __init__(self, planner, executor, validator):        self.planner = planner        self.executor = executor        self.validator = validator        self.retry_count = 0     def run(self, task, max_retries=3):        plan = self.planner.plan(task)        results = {}        for step_name, args in plan:            ok = False            attempt = 0            while not ok and attempt < max_retries:                attempt += 1                try:                    result = self.executor.execute(step_name, args)                except Exception as exc:                    print(f"[重试] {step_name} 第{attempt}次异常: {exc}")                    self.retry_count += 1                    time.sleep(0.2)                    continue                if self.validator.verify(step_name, result):                    ok = True                    results[step_name] = result                else:                    print(f"[重试] {step_name} 第{attempt}次校验不通过")                    self.retry_count += 1                    time.sleep(0.2)            if not ok:                raise RuntimeError(f"步骤 {step_name} 重试 {max_retries} 次仍失败")        return results  def build_agent():    registry = ToolRegistry()    registry.register("summarize", tool_summarize)    registry.register("keywords", tool_keywords)    registry.register("format", tool_format)    registry.register("check", tool_check)    planner = Planner()    executor = Executor(registry)    validator = Validator()    return PlanAndExecuteAgent(planner, executor, validator)  if __name__ == "__main__":    task_text = "深度学习在自然语言处理中的应用综述,涵盖Transformer架构与预训练语言模型。"    agent = build_agent()    final = agent.run(task_text)    print("最终输出:")    for key, value in final.items():        print(f"  [{key}] -> {value}")    print(f"统计:规划 {agent.planner.plan_count} 次,执行 {agent.executor.exec_count} 次,重试 {agent.retry_count} 次")
目的:演示一个完整的 Plan-and-Execute Agent——规划器拆解任务、执行器调用注册表工具、校验器把关每一步输出、失败自动重试,并输出执行统计。
  1. class ToolRegistry:工具注册表,相当于“工具箱”,所有工具函数先登记再取用。
  2. self.tools[name] = func把函数对象以工具名为键存入字典,实现“按名取工具”。
  3. registry.register("summarize", tool_summarize)注册摘要工具,等价于给工具箱贴上“标签”。
  4. def plan(self, task):规划器把大任务拆成 4 个有序子步骤,相当于设计师出图纸。
  5. ("summarize", {"text": task})每步包含两个元素:工具名 + 参数表,交给执行器即可直接调用。
  6. self.exec_count += 1执行器每调一次工具就计数,方便观察执行次数是否合理。
  7. except Exception as exc:捕获工具调用异常。这是重试的触发点之一,异常和校验失败都会进入重试分支。
  8. while not ok and attempt < max_retries:核心重试循环:只要没通过且没超上限就继续尝试。
  9. self.validator.verify(step_name, result)校验器对每一步的返回值做规则检查,扮演“监理”角色。
  10. if step_name == "keywords" and "ai" not in result:对特定步骤定义专属校验标准,全局规则与局部规则结合。
  11. raise RuntimeError(...)重试耗尽后主动抛错,避免系统带着错误结果继续运行(熔断思想的代码体现)。

常见误区

误区我理解这个模式了,不就是让大模型先输出一个 to-do list,然后照着做吗?那我在 prompt 里加一句“请先制定计划再执行”不就行了?

正解Prompt 里加一句话确实能让大模型“看起来”在规划,但这不是 PnE 架构。真正的 PnE 是把“规划”和“执行”拆成两个独立的系统组件:规划器的输出是结构化的、可校验的数据(如 JSON 步骤列表),执行器是代码层面的工具调用循环。两者的交互靠数据结构驱动,而不是靠模型“自觉”。拿装修打比方:口头说“你看着办”不是规划,画一套可施工、可验收的图纸才是规划。

为何常见:因为在小 demo 里,模型“边生成计划边执行”看起来效果不错,让人误以为 prompt 技巧就能替代架构设计。一旦任务变复杂,模型就会把计划和执行混在一起,输出不可控。

误区既然说 PnE 擅长处理复杂任务,那我干脆把任务拆成几十个步骤,每一步都很简单,这样每步成功率就高,整体一定更可靠。

正解从公式 $\text{Cost}_{\text{total}} = \sum \text{Cost}(s_i) + \text{Cost}_{\text{overhead}} \times (n-1)$ 可以看出,步骤越多,步骤间的协调成本就越高。每一步都要传递上下文、做一次校验、加一次重试开销。实际项目中,子任务最佳粒度是“一次 LLM 调用或工具调用能独立完成并验证”的大小,通常一个任务拆 3-8 步为宜。装修也一样,把贴砖拆成“拿砖、抹浆、放砖、敲平”没有意义,因为交接成本远超收益。

为何常见:人容易把“分解”理解成“越细越好”,忽略了系统调用的固定开销。

误区工具调用成功返回了,没有抛异常,这不就说明这步执行成功了吗?校验还有必要吗?

正解工具“成功返回”和“结果正确”是两回事。搜索引擎返回了结果,但结果可能为空;JSON 解析成功,但字段可能缺少关键信息;模型生成了文字,但可能与用户需求无关。校验器的作用是检查“结果是否符合预期”,而不仅仅是“有没有异常”。就像监理要量瓷砖的平整度,而不只是看看施工队有没有把砖贴上去。

为何常见:因为传统的函数调用思维里,返回值 = 成功值。而在 Agent 场景中,工具返回的是信息,不是结论,信息质量必须单独把关。

误区失败就重试,那就把 max_retries 设成 10 次、20 次,总有一次能成功吧?

正解无上限的重试会带来两个问题:一是消耗大量时间和 Token,一个本来 10 秒的任务可能卡 5 分钟;二是可能“踩踏”——如果失败原因是下游服务过载,越重试越加重故障。正确的做法是限制重试次数(通常 2-3 次)并配合指数退避,超过上限就触发降级方案或熔断。装修中“返工不超过两次就换方案”的规则,就是为了避免在错误路线上无限投入。

为何常见:人们天然觉得“多试几次总行”,忽略了每次重试的成本和重试对下游系统的伤害。

面试题

Q1. 关于 Plan-and-Execute 模式的核心思想,最准确的描述是?

单选题 · easy

  • A.边推理边行动,每步都参考全局目标
  • B.先制定完整计划,再按计划分步执行← 正确答案
  • C.让模型自我反思后重新生成答案
  • D.用强化学习训练模型的决策能力

答案:B。PnE 的精髓是“先规划后执行”——先用规划器产出结构化步骤列表,再让执行器逐项调用工具,对应选项 B。A 描述的是 ReAct 模式,C 是自我修正类方法,D 是训练范式而非推理架构。

Q2. 工具注册表(ToolRegistry)在 PnE 中的主要作用是?

单选题 · easy

  • A.记录大模型的调用日志
  • B.存储对话历史供模型参考
  • C.建立工具名到函数的映射关系← 正确答案
  • D.缓存工具执行的结果

答案:C。注册表本质是一个字典,键为工具名、值为函数对象,让执行器能按名字取到可调用的函数。选项 B 是记忆机制,D 是缓存机制,A 与注册功能无关。

Q3. 在装修比喻中,“监理验收”对应 PnE 模式的哪个环节?

单选题 · mid

  • A.任务规划阶段
  • B.工具调用阶段
  • C.结果校验阶段← 正确答案
  • D.失败重试阶段

答案:C。监理对每道工序质量打分、判断是否合格,正对应校验器对每步结果做校验。规划阶段是出图纸,执行阶段是施工,重试阶段是返工,都不直接对应验收职责。

Q4. 任务分解时,$n$ 个子任务的总成本可以表示为 $\text{Cost}_{\text{total}} = \sum \text{Cost}(s_i) + \text{Cost}_{\text{overhead}} \times (n-1)$,由此可推断?

单选题 · mid

  • A.分解越细总成本一定越低
  • B.存在一个使总成本最小的最优粒度← 正确答案
  • C.协调成本与子任务数量呈反比
  • D.总成本只取决于子任务数

答案:B。公式中直接成本随步骤数量变化,而协调成本随 $n$ 增加而线性上升,所以总成本曲线是先降后升,存在最优粒度。A 忽略协调成本,C 说反了,D 忽略了每步成本差异。

Q5. 执行器在执行子任务时抛出了异常,PnE 系统最合理的首要处理方式是?

单选题 · mid

  • A.终止整个任务并报错
  • B.忽略异常继续执行下一步
  • C.将异常结果直接返回用户
  • D.进入重试流程,达到上限再终止← 正确答案

答案:D。异常是典型的可重试失败,应先按重试策略处理(通常配合指数退避),超过上限才熔断终止。A 直接终止太激进;B 忽略异常会让错误污染下游;C 把半成品交给用户同样不可取。

Q6. 关于指数退避重试策略 $a_n = \min(a_{\max}, a_0 \times 2^{n-1})$,哪种说法是正确的?

单选题 · hard

  • A.每次重试等待时间固定不变
  • B.重试间隔随次数指数增长但有上限← 正确答案
  • C.重试间隔随次数不断减小
  • D.该策略只适用于网络请求

答案:B。公式显示等待时间按 $2^{n-1}$ 指数放大,同时被 $a_{\max}$ 限制上限。A 忽略了指数部分,C 恰好相反,D 过度局限,该策略适用于一切可重试的耗时操作。

Q7. 下列哪些做法符合 PnE 模式的最佳实践?

多选题 · mid

  • A.把无依赖的子任务设计成可并行执行← 正确答案
  • B.从规划到执行全部靠 prompt 让模型自觉完成
  • C.每步执行后立即校验并反馈结果← 正确答案
  • D.重试时不断增大请求频率

答案:AC。并行化能降低总耗时,逐点校验能尽早发现问题,都是 PnE 的成熟实践。B 混淆了架构与提示词技巧;D 的高频重试会导致踩踏,正确做法是退避而不是加速。

Q8. 校验器发现某一步结果“部分通过”(例如摘要覆盖了部分要点),合理的后续处理包括?

多选题 · mid

  • A.无条件丢弃该结果并重新执行
  • B.保留部分结果,仅返工未达标片段← 正确答案
  • C.标记风险并继续后续步骤← 正确答案
  • D.直接判定为最终答案交付用户

答案:BC。部分通过意味着有可用价值,合理策略是局部返工或带风险标记继续。A 全量重做浪费资源;D 不做任何处理就把不完整结果交给用户,违背校验目的。

Q9. PnE 与 ReAct 的根本区别在于?

单选题 · hard

  • A.PnE 使用大模型,ReAct 使用小模型
  • B.PnE 将规划与执行解耦成独立阶段← 正确答案
  • C.ReAct 不支持调用外部工具
  • D.PnE 不包含失败处理机制

答案:B。PnE 的核心特征是规划器先一次性产出完整计划,执行器再按计划施工;ReAct 是“推理-行动”交替进行。两者的模型规模、工具能力、失败处理都取决于具体实现,不是根本区别。

Q10. 在 PnE 系统中引入熔断机制的价值包括?

多选题 · hard

  • A.防止失败率过高时无意义的反复重试← 正确答案
  • B.保护下游工具服务不被高频请求压垮← 正确答案
  • C.提升单次重试的成功概率
  • D.快速失败并向用户反馈“无法完成”← 正确答案

答案:ABD。熔断在失败率达到阈值时暂停执行、保护下游、快速失败告知用户,对应 A、B、D。但熔断本身不提升单次重试成功率,那是重试策略与降级方案的目标。