想象一下,李阿姨要装修一套老房子。她没有直接拎着锤子进场砸墙,而是先请设计师张工出了一整套施工图:哪面墙要拆、水电怎么走、瓷砖贴到多高、柜子打在哪里,全部画得明明白白。拿到图纸后,李阿姨才按顺序安排施工队进场——先是敲墙拆旧,再是水电改造,每道工序完成,监理都要验收签字,不合格立刻返工,全部通过才交付。这套“先出图纸、再分步施工、逐道验收”的思路,就是本期要讲的 Plan-and-Execute(规划执行)模式。 AI Agent 处理复杂任务时,最容易犯的毛病就是“拎着锤子就砸墙”——拿到问题直接开干,结果干到一半发现方向错了,或者漏掉了关键步骤。Plan-and-Execute 模式让 Agent 先调用大模型做整体规划,把大任务拆成有依赖关系的小步骤,再逐个执行、逐项校验、失败重试。就像装修有设计师和监理把关一样,Agent 的每一步都有据可查、有错可纠。本文会用一个贯穿全文的“装修故事”,带你从零搭建一个可用的规划执行系统。
对 AI 从业者来说,Plan-and-Execute 是 Agent 工程化落地最实用的架构之一。掌握它,你就能让模型稳定地完成多步骤复杂任务,这是从“会调 API”走向“能交付产品”的关键一跃。
可视化演示
展示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 最朴素的智慧——把不确定性锁进规划阶段,把确定性留给执行阶段。
- Planner(T) 规划器接收原始任务 $T$,相当于设计师根据业主需求画图纸。大模型在这里一次性完成全局思考,输出结构化的子任务列表。
- 子任务集合$\{s_1, s_2, \ldots, s_n\}$ 是规划产物,每个 $s_i$ 是一个独立可执行的子任务,相当于图纸上的每一道工序。
- 逐个执行$\bigcup$ 符号表示对每个子任务依次调用 Executor,得到各自的执行结果,再汇总成最终答案。
核心流程
装修的完整流程是:设计师出图 → 施工队进场 → 监理验收 → 交付钥匙。PnE 的 Agent 走的也是同样的四步:规划 → 执行 → 校验 → 输出。这四步构成了一个完整的任务生命周期,每一步都有明确的输入输出,就像装修的每个阶段都有明确的交付物。
第一步是规划阶段。Agent 拿到用户的任务描述,调用大模型生成一份“施工计划”——通常是一个 JSON 数组,每个元素包含步骤名、所需工具、输入参数。这一步的关键是把模糊的大目标翻译成具体的行动指令。第二步是执行阶段,Agent 遍历计划中的每一步,调用对应的工具函数,如同施工队拿出电钻、水平仪各司其职。第三步是校验阶段,Agent 检查每一步的输出是否符合预期——好比监理拿着卷尺量瓷砖缝隙是否达标。第四步是输出阶段,所有步骤通过后,把中间结果汇总成最终答案交还用户。
这四个阶段在代码层面有清晰的边界。规划器只负责生成计划,执行器只负责调用工具,校验器只负责判断对错。这种“单一职责”的设计让系统变得容易调试:如果结果不对,先看是规划错了(图纸画错了),还是执行错了(施工队干坏了),还是校验漏了(监理没发现)。实践中,这四个阶段可以用一个简单的状态机来驱动。
李阿姨看着流程图就明白了:装修公司不是把钥匙直接交给她就完事,中间必须有监理这道关口。PnE 的“校验阶段”就是 Agent 的监理——没有这道关口,前面做得再快也可能是白忙。流程的价值不在于每一步多精彩,而在于每一步之间有人把关。
- Plan 状态转移$\text{Task} \xrightarrow{\text{Plan}} \text{Plan}$ 表示原始任务通过规划器变成可执行的步骤列表,这是从“模糊”到“清晰”的转化。
- Execute 状态转移$\text{Plan} \xrightarrow{\text{Execute}} \text{Result}$ 表示计划中的每个步骤被逐个执行,产出中间结果。
- Verify 状态转移$\text{Result} \xrightarrow{\text{Verify}} \text{Final}$ 表示中间结果经过校验、必要修正后,变成最终回答。
任务分解
设计师拿到户型图后,第一件事是把整个装修拆成可以发包的工程包:拆旧、水电、泥瓦、木工、油漆、软装。这个拆解的颗粒度很有讲究——拆得太粗,一个工程包里塞了太多活,施工队干着干着就乱了;拆得太细,光协调交接就要花掉大量时间。任务分解(Task Decomposition)是 PnE 模式中最考验“功力”的一环。
分解原则可以概括为三条:独立性(每个子任务尽量不依赖其他子任务的中间状态)、可验证性(每个子任务都有明确的完成标准)、适当粒度(子任务的大小要匹配单次 LLM 调用的能力边界)。比如“写一篇市场分析报告”可以拆成“收集竞品信息→分析价格带→总结趋势→撰写报告”,而不是拆成“打开电脑→新建文档→输入标题”这种过细的步骤。
在实现层面,任务分解通常有两种策略。一种是模板驱动式:针对常见任务类型预设分解模板,比如“报告生成 = 调研 + 大纲 + 撰写 + 校对”。另一种是自由式:直接让大模型根据目标任务现场拆解,适合没有先例的新任务。实际系统中常把两者结合——先用模板兜底,再允许 LLM 微调。无论哪种策略,都要注意子任务间的依赖关系,有的步骤必须等前置步骤完成才能开始,这就像必须等水电改造完才能贴瓷砖。
李阿姨和设计师最终把工程定成了 6 个包,而不是 60 个或 3 个——因为经验告诉他们,6 个包既能明确责任,又不用整天开会协调。任务分解的智慧,就是找到那个“不多不少”的颗粒度,让每个子任务都能独立检验、独立交付。
- 各步骤成本之和每个子任务 $s_i$ 执行一次需要消耗 Token 或时间,把它们加起来就是所有步骤的直接成本,相当于每道工序的工钱总和。
- 协调成本$\text{Cost}_{\text{overhead}} \times (n-1)$ 中,$n-1$ 是步骤之间的交接次数。每次交接都需要传递上下文、检查状态,就像每两道工序之间要开一次碰头会。
- 总成本模型两部分相加才是真实总成本。这提醒我们:分解粒度要选在“直接成本 + 协调成本”最小的那个平衡点。
工具调用
施工队进场后,电工要用测电笔,瓦工要用水平仪,木工要用切割机——每个工种都有自己趁手的工具。对应到 Agent 里,每个子任务也需要调用不同的工具函数:查数据库、调 API、做计算、搜网页。工具调用(Tool Calling)是执行阶段的核心技术,它让 Agent 从“只会动嘴”变成“还能动手”。
工具调用有一套标准的实现流程。第一步是工具注册:把所有可用的工具函数登记到一个注册表中,每个工具有一个名字、一段描述和参数 Schema,就像工地上每个工具箱贴着标签写明用途。第二步是参数解析:执行器拿到子任务中的工具名和参数后,从注册表找到对应的函数,验证参数格式是否正确——好比翻出电钻说明书,确认电压匹配再插电。第三步是调用编排:按计划顺序依次调用工具,前一个工具的输出可能作为后一个工具的输入,形成一条流水线。
在代码层面,工具注册表通常是一个字典结构:键是工具名,值是函数对象及其元信息。当大模型规划出一个步骤“调用 search 工具,查询关键词为 X”时,执行器只需要从注册表取出 search 函数,把参数传进去,拿到返回值。这个过程看似简单,但有一个关键设计决策:让大模型决定调什么工具,但让代码决定怎么调工具。大模型负责语义理解(选哪个工种),代码负责安全执行(怎么操作工具),各管一摊。
装修工地上,每种工具都放在固定位置、有明确的保养标准,工人们不会临时去五金店现买。Agent 的工具注册表也是如此——工具清单越规范,执行越不容易出错;参数校验越严格,运行时越少意外。
- 按名取函数$\text{Registry}[\text{name}]$ 表示用工具名作为键,从注册表字典中取出真正的函数对象,相当于按工具箱标签取工具。
- 传入参数$(s_i.\text{args})$ 表示把子任务 $s_i$ 里携带的参数传给工具函数,相当于把砖块递给瓦工。
- 得出结果整个表达式 $\text{Registry}[\text{name}](s_i.\text{args})$ 的值就是工具调用的返回结果 $r_i$,它是后续校验和汇总的输入。
结果校验
监理老周每天的工作就是在各工种完工后,拿着验收单逐项打勾:水电管路压力测试达标了吗?瓷砖空鼓率在允许范围内吗?每一项都合格,才签“同意进入下一道工序”。PnE 模式中的结果校验(Result Validation)扮演的正是老周的角色——不信任任何一步的默认成功,用明确的标尺量过才算数。
校验的时机有两种选择:一种是在所有步骤执行完后统一校验,另一种是每执行完一步立即校验。后者的成本更高,但能尽早发现问题——就像瓷砖贴完一面墙就检查一面,比全部贴完再检查能省下大量返工成本。校验标准可以是规则化的(结果长度大于 N、包含指定字段、JSON 格式正确),也可以让大模型来判(输出是否满足用户原始需求)。实践中推荐规则优先、模型兜底的双层校验,因为规则校验便宜且稳定,复杂语义校验则交给大模型。
更精细的设计是部分通过机制:某些步骤的校验不是简单的通过/失败,而是部分通过——比如搜索结果的相关性得分 0.6 分,离 0.8 的及格线还差一点,但不需要完全重做,可以带着这个部分结果继续,同时标记一个“风险提示”。这就像监理发现瓷砖有两块空鼓,不需要全部敲掉重贴,只需要局部返工。校验结果可以反馈给规划器,帮助它修正后续步骤的输入参数——形成“执行→校验→反馈→修正”的闭环。
监理老周的验收单,就是装修界的“校验标准”。没有他签字,施工队拿不到下一阶段的工钱。Agent 也一样:没有校验器的把关,每一步执行结果都可能带着隐患流向下游,最终酿成整体失败。校验不是多余的谨慎,而是质量的守门员。
- 打分函数$\text{score}(r_i)$ 是对第 $i$ 步结果 $r_i$ 的质量评分。规则校验里它可以是一个布尔判断,模型校验里它可以是一个 0-1 分数。
- 阈值比较$\text{score}(r_i) \geq \theta$ 是核心判断:达到或超过合格线 $\theta$ 就算合格。$\theta$ 的值根据任务类型调节,比如摘要任务要求长度≥50字,数据任务要求字段齐全。
- 二值输出整个分段函数输出 1(通过)或 0(不通过),这种简单的信号方便上层逻辑做分支决策。
失败重试
就算有设计师的图纸和监理的验收,装修也不可能一帆风顺——可能遇到墙体里藏着钢筋、水管位置偏移、材料临时缺货。施工队遇到问题怎么办?不是当场崩溃,而是有一套应对预案:先判断问题严重性,能局部返工就局部返工,返工还不行就换方案,方案全失效才停工求救。Agent 的失败重试(Retry)机制,就是为执行过程中的异常准备的“应急预案”。
失败识别是重试的第一步。系统需要区分三类失败:工具调用异常(API 超时、参数错误)、结果校验不通过(输出格式不符合预期)、大模型自身生成错误(输出无法解析)。不同类型的失败对应不同的重试策略。重试策略的核心参数是重试次数上限和退避策略:最简单的固定重试——失败后等 1 秒再试;更合理的是指数退避——第 n 次重试等待时间按指数增长,避免在系统抖动时反复踩踏;还可以在重试时换参数甚至换模型,就像瓦工发现这种砖贴不牢,换一种粘结剂。
更高级的设计是降级方案:某个工具连续失败后,用备用工具代替。比如主搜索 API 挂了,就切换到备用搜索源;向量数据库超时了,就退化为关键词检索。熔断机制则是在失败率达到阈值时,暂停整个 Agent 的执行,不再盲目重试,而是直接告知用户“当前任务无法完成,建议修改需求或稍后再试”——就像装修公司发现房子承重墙有问题,果断停工通知业主,而不是硬着头皮继续。
李阿姨的装修合同中明确写着“每道工序返工不超过两次,超过则更换施工方案”。这份合同就是重试策略的文字化。Agent 的失败重试机制,本质上就是把“意外”当作流程的一部分提前设计好,让系统在不确定环境中依然能收敛到可用结果。
- 基础等待第 $n$ 次重试的等待时间从 $a_0$(初始值,比如 1 秒)开始,每次翻倍,重试次数越多等待越久。
- 封顶限制$\min$ 操作确保等待时间不超过 $a_{\max}$(比如 30 秒),避免无限期等待,保证了系统响应时间可控。
- 指数退避的意义重试间隔呈指数增长,防止 Agent 在系统故障时高频重试造成“二次伤害”(像一群人在塌了的桥上反复踩)。
实践指南
有了前面的理论铺垫,我们来搭一个真实的 Plan-and-Execute 系统。以一个“文章摘要生成助手”为例,完整的代码框架包含四个核心类:Planner(规划器)、Executor(执行器)、Validator(校验器)、Agent(总控)。核心循环只有十几行:解析任务 → 规划 → 循环执行与校验 → 汇总输出。
设计经验上最重要的三条:第一,规划时给每个步骤配好工具和参数示例,这能显著降低执行阶段的解析错误率;第二,执行器的异常捕获要细粒度,区分“工具本身报错”和“返回结果异常”两种情形,前者重试、后者走校验;第三,在 Agent 入口处设置全局超时与重试上限,防止某个步骤卡死拖垮整个任务。常见的陷阱包括:规划步骤之间隐式依赖未声明(漏了前置步骤)、校验标准过于宽松(验收形同虚设)、重试时没有更新上下文(返工时拿着旧图纸)。
优化方向可以从三个角度展开:一是并行化——没有依赖关系的子任务可以同时执行,像瓦工和木工在不同房间同时施工;二是缓存复用——相同参数的规划结果和执行结果可以缓存,下次直接命中;三是动态重规划——当校验发现某一步无论如何都无法通过时,触发规划器重新规划后续步骤,而不是一味重试。这些优化能把 PnE 系统从“能跑”推向“高效稳定地跑”。
李阿姨通过这次装修总结出经验:图纸要画清楚,工序要排合理,验收要真较真,返工要有预案。把这四条翻译成工程师的语言,就是规划清晰、分解得当、校验严格、重试有度——这就是 Plan-and-Execute 模式的全部心法。
- 规划时间$T_{\text{plan}}$ 是调用规划器生成计划的那次大模型推理耗时。它只发生一次,所以占比不大。
- 单步耗时构成$T_{\text{exec}}(s_i) + T_{\text{verify}}(s_i)$ 是第 $i$ 步“干活 + 验收”的时间,这是每个子任务的固定开销。
- 重试叠加$T_{\text{retry}}(s_i)$ 是第 $i$ 步失败重试消耗的额外时间。把这一项压缩到最低,是优化系统延迟最有效的手段。
AI 应用场景
场景一:竞品调研报告自动生成
阶段:Agent 应用开发 → 任务规划与执行
解决的问题:传统竞品调研靠人工手动收集信息、整理数据、撰写报告,一个完整调研常要 2-3 天。AI Agent 借助 PnE 模式,先把“调研任务”拆成“搜索竞品官网信息→抓取用户评价→对比功能列表→生成分析报告”四个子任务,再逐步调用搜索工具、爬虫工具和生成模型,全程无需人工干预。底层逻辑就是本文讲的“先出图纸、再施工、逐项验收”。
具体案例:某 SaaS 公司的产品经理输入“分析我们与三家竞品的差异”,PnE Agent 自动搜索三家竞品官网和第三方测评,校验每步结果完整性,最终输出一份带数据对比表的报告,耗时从 2 天缩短到 15 分钟。
场景二:批量合同审查与风险标注
阶段:工具调用编排 → 结果校验
解决的问题:法务团队每天要审查大量合同,重复性高且容易漏看关键条款。PnE Agent 将“审查一份合同”规划为“解析合同结构→提取关键条款→比对风险规则库→生成审查意见”四个步骤,每步调用不同的工具:PDF 解析器、条款提取模型、风险规则引擎。校验器会检查每条风险点是否附有原文引用,缺失则触发重提取。
具体案例:某律所部署 PnE Agent 处理供应商合同,将原先每人每天 20 份的处理量提升到 200 份,且风险条款召回率达到 95%。得益于失败重试机制,遇到扫描件质量差导致解析失败时,Agent 会自动切换 OCR 模型重试,而不是中断整个批次。
Python 演示
"""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} 次")
class ToolRegistry:工具注册表,相当于“工具箱”,所有工具函数先登记再取用。self.tools[name] = func把函数对象以工具名为键存入字典,实现“按名取工具”。registry.register("summarize", tool_summarize)注册摘要工具,等价于给工具箱贴上“标签”。def plan(self, task):规划器把大任务拆成 4 个有序子步骤,相当于设计师出图纸。("summarize", {"text": task})每步包含两个元素:工具名 + 参数表,交给执行器即可直接调用。self.exec_count += 1执行器每调一次工具就计数,方便观察执行次数是否合理。except Exception as exc:捕获工具调用异常。这是重试的触发点之一,异常和校验失败都会进入重试分支。while not ok and attempt < max_retries:核心重试循环:只要没通过且没超上限就继续尝试。self.validator.verify(step_name, result)校验器对每一步的返回值做规则检查,扮演“监理”角色。if step_name == "keywords" and "ai" not in result:对特定步骤定义专属校验标准,全局规则与局部规则结合。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 模式的核心思想,最准确的描述是?
- A.边推理边行动,每步都参考全局目标
- B.先制定完整计划,再按计划分步执行← 正确答案
- C.让模型自我反思后重新生成答案
- D.用强化学习训练模型的决策能力
答案:B。PnE 的精髓是“先规划后执行”——先用规划器产出结构化步骤列表,再让执行器逐项调用工具,对应选项 B。A 描述的是 ReAct 模式,C 是自我修正类方法,D 是训练范式而非推理架构。
Q2. 工具注册表(ToolRegistry)在 PnE 中的主要作用是?
- A.记录大模型的调用日志
- B.存储对话历史供模型参考
- C.建立工具名到函数的映射关系← 正确答案
- D.缓存工具执行的结果
答案:C。注册表本质是一个字典,键为工具名、值为函数对象,让执行器能按名字取到可调用的函数。选项 B 是记忆机制,D 是缓存机制,A 与注册功能无关。
Q3. 在装修比喻中,“监理验收”对应 PnE 模式的哪个环节?
- A.任务规划阶段
- B.工具调用阶段
- C.结果校验阶段← 正确答案
- D.失败重试阶段
答案:C。监理对每道工序质量打分、判断是否合格,正对应校验器对每步结果做校验。规划阶段是出图纸,执行阶段是施工,重试阶段是返工,都不直接对应验收职责。
Q4. 任务分解时,$n$ 个子任务的总成本可以表示为 $\text{Cost}_{\text{total}} = \sum \text{Cost}(s_i) + \text{Cost}_{\text{overhead}} \times (n-1)$,由此可推断?
- A.分解越细总成本一定越低
- B.存在一个使总成本最小的最优粒度← 正确答案
- C.协调成本与子任务数量呈反比
- D.总成本只取决于子任务数
答案:B。公式中直接成本随步骤数量变化,而协调成本随 $n$ 增加而线性上升,所以总成本曲线是先降后升,存在最优粒度。A 忽略协调成本,C 说反了,D 忽略了每步成本差异。
Q5. 执行器在执行子任务时抛出了异常,PnE 系统最合理的首要处理方式是?
- A.终止整个任务并报错
- B.忽略异常继续执行下一步
- C.将异常结果直接返回用户
- D.进入重试流程,达到上限再终止← 正确答案
答案:D。异常是典型的可重试失败,应先按重试策略处理(通常配合指数退避),超过上限才熔断终止。A 直接终止太激进;B 忽略异常会让错误污染下游;C 把半成品交给用户同样不可取。
Q6. 关于指数退避重试策略 $a_n = \min(a_{\max}, a_0 \times 2^{n-1})$,哪种说法是正确的?
- A.每次重试等待时间固定不变
- B.重试间隔随次数指数增长但有上限← 正确答案
- C.重试间隔随次数不断减小
- D.该策略只适用于网络请求
答案:B。公式显示等待时间按 $2^{n-1}$ 指数放大,同时被 $a_{\max}$ 限制上限。A 忽略了指数部分,C 恰好相反,D 过度局限,该策略适用于一切可重试的耗时操作。
Q7. 下列哪些做法符合 PnE 模式的最佳实践?
- A.把无依赖的子任务设计成可并行执行← 正确答案
- B.从规划到执行全部靠 prompt 让模型自觉完成
- C.每步执行后立即校验并反馈结果← 正确答案
- D.重试时不断增大请求频率
答案:AC。并行化能降低总耗时,逐点校验能尽早发现问题,都是 PnE 的成熟实践。B 混淆了架构与提示词技巧;D 的高频重试会导致踩踏,正确做法是退避而不是加速。
Q8. 校验器发现某一步结果“部分通过”(例如摘要覆盖了部分要点),合理的后续处理包括?
- A.无条件丢弃该结果并重新执行
- B.保留部分结果,仅返工未达标片段← 正确答案
- C.标记风险并继续后续步骤← 正确答案
- D.直接判定为最终答案交付用户
答案:BC。部分通过意味着有可用价值,合理策略是局部返工或带风险标记继续。A 全量重做浪费资源;D 不做任何处理就把不完整结果交给用户,违背校验目的。
Q9. PnE 与 ReAct 的根本区别在于?
- A.PnE 使用大模型,ReAct 使用小模型
- B.PnE 将规划与执行解耦成独立阶段← 正确答案
- C.ReAct 不支持调用外部工具
- D.PnE 不包含失败处理机制
答案:B。PnE 的核心特征是规划器先一次性产出完整计划,执行器再按计划施工;ReAct 是“推理-行动”交替进行。两者的模型规模、工具能力、失败处理都取决于具体实现,不是根本区别。
Q10. 在 PnE 系统中引入熔断机制的价值包括?
- A.防止失败率过高时无意义的反复重试← 正确答案
- B.保护下游工具服务不被高频请求压垮← 正确答案
- C.提升单次重试的成功概率
- D.快速失败并向用户反馈“无法完成”← 正确答案
答案:ABD。熔断在失败率达到阈值时暂停执行、保护下游、快速失败告知用户,对应 A、B、D。但熔断本身不提升单次重试成功率,那是重试策略与降级方案的目标。