ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent设计模式实战:ReAct、Plan-and-Execute等五种模式选型与避坑指南

Agent设计模式实战:ReAct、Plan-and-Execute等五种模式选型与避坑指南 1. 从学废了说起Agent设计模式到底在解决什么问题学废了这个词很妙它精准地描述了一种状态——你看了很多Agent的教程知道ReAct、知道Plan-and-Execute、知道Reflection但真让你从零搭一个能扛住真实业务场景的Agent时脑子里还是一团浆糊。问题出在哪儿出在你学的是模式的名字而不是模式要解决的矛盾。我接触Agent开发这几年最大的体会是设计模式不是让你背下来的八股文而是当你的Agent在某个环节卡住时你脑子里能立刻弹出这个场景该用哪种结构来拆的条件反射。就像23种设计模式之于Java真正写业务代码时你不会刻意想我现在要用观察者模式而是当对象间一对多依赖导致耦合过重时观察者模式自然就浮出来了。Agent的设计模式也一样它的价值在于帮你把一个模糊的智能任务翻译成一组可编排、可调试、可扩展的执行单元。这篇文章要聊的五种模式是我在实际项目中反复用到、也反复踩坑总结出来的。它们分别是ReAct循环模式、Plan-and-Execute模式、Reflection自省模式、Multi-Agent协作模式、以及Tool-Use编排模式。这五种不是互斥的很多时候一个成熟的Agent系统里会同时存在三四种。我会把每种模式的适用边界、核心机制、代码骨架、以及我在真实项目里踩过的坑都摊开讲。适合谁看如果你已经知道Agent是什么能写基本的LLM调用但一到怎么让Agent稳定完成复杂任务就犯难那这篇就是写给你的。如果你是完全的新手建议先补一下function calling和prompt engineering的基础再回来看会更有感觉。提示本文所有代码示例以Python为主但模式本身与语言无关。你用Java、TypeScript、Rust实现核心逻辑是一样的。2. ReAct循环模式最基础也最容易写崩的那一个2.1 ReAct的本质不是推理行动而是观察驱动的状态机很多人把ReAct理解成让模型先想一想再调个工具这个理解太浅了。ReActReasoning Acting真正的核心是一个由观察结果驱动的循环状态机模型输出思考Thought→ 决定行动Action→ 执行工具得到观察Observation→ 把观察塞回上下文 → 再次思考。这个循环什么时候停不是模型说停就停而是你要设计明确的终止条件。我见过太多人写的ReAct是这样的把工具描述往system prompt里一塞然后while循环调API直到模型输出里没有Action字样就退出。这种写法在demo里能跑一上真实场景就崩。崩的原因通常有三个循环没有硬性上限导致死循环烧token、工具报错后模型陷入重复调用同一个工具的泥潭、以及上下文无限增长最终超出窗口。2.2 一个能扛住真实场景的ReAct骨架下面这个骨架是我在多个项目里迭代出来的关键点在于每一步都有护栏import json MAX_STEPS 12 MAX_CONSECUTIVE_ERRORS 3 def react_loop(user_query, tools, llm_client): messages [ {role: system, content: build_react_system_prompt(tools)}, {role: user, content: user_query} ] consecutive_errors 0 tool_call_history [] for step in range(MAX_STEPS): response llm_client.chat(messages, stop[Observation:]) content response.content # 终止条件一模型主动给出最终答案 if Final Answer: in content: return extract_final_answer(content) # 解析Action action parse_action(content) if action is None: messages.append({role: assistant, content: content}) messages.append({role: user, content: Observation: 未识别到有效Action请重新输出。}) continue # 防重复调用同一个工具同一组参数连续调用超过2次就拦截 signature f{action[tool]}::{json.dumps(action[args], sort_keysTrue)} if tool_call_history.count(signature) 2: messages.append({role: user, content: Observation: 检测到重复调用请换一个思路或直接给出Final Answer。}) continue tool_call_history.append(signature) # 执行工具捕获异常 try: observation execute_tool(action[tool], action[args], tools) consecutive_errors 0 except Exception as e: observation f工具执行失败: {str(e)} consecutive_errors 1 if consecutive_errors MAX_CONSECUTIVE_ERRORS: return f连续{MAX_CONSECUTIVE_ERRORS}次工具失败任务中止。最后错误: {observation} messages.append({role: assistant, content: content}) messages.append({role: user, content: fObservation: {observation}}) return 达到最大步数限制任务未完成。这段代码里有几个设计决策值得展开说。为什么用stop[Observation:]因为你要强制模型在输出Action后就停下来把执行权交还给你的代码。如果不加stop模型可能会自己脑补一个Observation继续往下编这就是所谓的幻觉工具结果。为什么要做重复调用检测因为模型在工具返回不符合预期时最常见的退化行为就是原封不动再调一次指望结果不一样。这个检测能逼它换策略。为什么错误计数是连续的而不是累计的因为一个复杂任务里偶发的单次工具失败很正常累计计数会导致任务过早中止连续计数才真正反映卡住了。2.3 ReAct的适用边界别拿它干规划类的活ReAct最适合的是步骤数不确定、每步依赖上一步观察的任务比如帮我查一下这个报错信息然后根据结果去文档里找解决方案再验证一下。这类任务你没法提前规划因为下一步做什么完全取决于上一步看到了什么。但如果你拿ReAct去做帮我规划一个为期三天的旅行行程这种任务就会很痛苦。因为规划类任务需要全局视角而ReAct是贪心的、短视的它容易走出订了机票发现酒店没房这种局部最优但全局崩盘的路径。这种场景就该上第二种模式了。注意ReAct的token消耗是随步数线性增长的因为每一步都要把完整历史塞回去。步数超过15步的任务建议考虑上下文压缩或者换模式。3. Plan-and-Execute模式先想清楚再动手的代价与收益3.1 为什么先规划能解决ReAct的短视问题Plan-and-Execute的核心思想很朴素把想和做分开。先用一次或几次LLM调用生成一个完整的任务计划把大目标拆成有序的子任务列表然后逐个执行子任务。执行阶段可以用更便宜的小模型甚至可以用确定性代码只有遇到计划外的异常才回头找规划器重新规划。这个模式解决的是ReAct的两个硬伤一是短视二是贵。规划阶段用最强的模型想一次执行阶段用便宜模型跑N次总体成本反而更低。而且因为计划是显式的你可以把它展示给用户确认这在企业级应用里非常重要——没人敢让一个黑盒Agent直接去操作生产数据库。3.2 规划器的prompt该怎么写才不跑偏规划器的输出质量直接决定整个系统的上限。我踩过的最大坑是早期我让规划器自由发挥结果它生成的计划要么粒度太粗第一步完成用户需求要么粒度太细拆了30步其中20步是废话。后来我固定了一套结构化输出格式效果稳定很多PLANNER_PROMPT 你是一个任务规划器。将用户需求拆解为有序的子任务列表。 约束 1. 每个子任务必须是一个可用单一工具或单一推理步骤完成的原子操作 2. 子任务数量控制在3-8个之间 3. 每个子任务必须明确标注依赖的前置子任务编号无依赖填null 4. 输出严格的JSON格式不要有任何额外文字 输出格式 { goal: 对用户需求的复述, subtasks: [ {id: 1, desc: 具体动作描述, tool_hint: 建议使用的工具名或null, depends_on: null}, {id: 2, desc: ..., tool_hint: ..., depends_on: [1]} ] } 关键在depends_on这个字段。有了依赖关系你就能把计划转成一个DAG有向无环图无依赖的子任务可以并行执行有依赖的串行。这一步是从能用到好用的分水岭。我有个项目里一个原本串行要跑40秒的任务识别出并行分支后压到了12秒。3.3 执行阶段的重新规划触发条件计划永远赶不上变化执行阶段一定会遇到规划时没预料到的情况。什么时候该触发重新规划我的经验是设三个触发器工具连续失败某个子任务的工具调用失败超过2次说明计划里对这个工具的假设是错的需要重新规划。观察结果与预期严重不符比如计划里假设查询用户表能得到邮箱结果返回空后续依赖这个结果的子任务全部失效。执行了计划的一半但目标明显无法达成这需要执行器有一个轻量的进度评估逻辑每完成几个子任务就让LLM判断一次当前状态离目标还有多远。重新规划不是从头再来而是把已完成的子任务结果当前卡点作为新输入让规划器生成剩余部分的计划。这样能保留已完成的工作避免浪费。3.4 Plan-and-Execute不适合什么场景如果你的任务本身就是探索性的比如帮我调研一下这个技术方向你事先根本不知道要查什么那硬做规划就是自欺欺人。这种场景ReAct反而更合适。另外实时性要求极高的场景比如对话式客服也不适合因为规划阶段本身就要花一次LLM调用的时间用户等不起。4. Reflection自省模式让Agent学会回头看一眼4.1 自省不是再问一遍模型而是结构化批判Reflection模式最容易被误解成把答案再丢给模型问一遍对不对。这种做法效果很差因为模型对自己刚生成的答案有路径依赖你直接问对吗它大概率说对的。真正有效的自省需要引入外部信号或结构化批判维度。我在实践中总结的自省触发方式有三种一是工具验证比如代码执行结果、单元测试通过率、搜索结果的事实核对二是多维度评分让模型从准确性、完整性、格式合规性三个维度给自己的输出打分并给出扣分理由三是角色切换让模型扮演一个挑刺的审稿人专门找前一个输出的漏洞。4.2 一个带自省循环的代码生成Agent实例以代码生成为例纯靠模型自省效果有限但加上执行反馈就完全不一样了def code_agent_with_reflection(task, max_reflections3): code llm_generate_code(task) for i in range(max_reflections): # 外部信号实际执行 exec_result run_code_safely(code) if exec_result[success] and exec_result[tests_passed]: return code # 结构化批判把执行结果和错误信息作为输入 critique_prompt f 任务: {task} 当前代码: {code} 执行结果: {exec_result[output]} 错误信息: {exec_result[error]} 请分析失败原因并输出修正后的完整代码。 要求 1. 先说明失败的根本原因不要只说表面现象 2. 再输出修正代码 3. 如果认为当前思路根本性错误请说明并给出新思路 code llm_call(critique_prompt) return code # 达到最大自省次数仍未通过这里的关键是执行结果是硬信号模型没法糊弄。我实测下来带执行反馈的自省循环代码一次通过率能从40%左右提到75%以上。但要注意自省次数不是越多越好超过3次之后边际收益急剧下降而且模型可能开始过拟合测试用例改出一些奇怪的补丁。4.3 自省的代价延迟翻倍慎用于实时场景每一次自省都是一次完整的LLM调用如果自省里还带工具执行那延迟就是叠加的。一个原本2秒返回的Agent加两轮自省可能变成8秒。所以自省模式适合离线任务、批处理任务、对质量要求高于速度的任务比如文档生成、数据分析报告、代码生成。实时对话场景要慎用或者只在检测到低置信度时才触发自省。提示一个实用的折中是选择性自省——让模型在输出时附带一个置信度只有置信度低于阈值时才启动自省循环。这样大部分简单请求走快速路径复杂请求才走慢速路径。5. Multi-Agent协作模式什么时候真的需要多个Agent5.1 多Agent不是银弹多数场景是过度设计先说一个反直觉的结论我见过80%号称用了多Agent的系统其实一个Agent加几个工具就能做得更好。多Agent带来的通信开销、状态同步复杂度、调试难度是成倍上升的。很多人上多Agent是因为觉得分工明确很优雅但实际跑起来发现Agent之间互相甩锅、信息在传递中丢失、一个Agent的幻觉污染了整个链路。那什么时候真的需要多Agent我的判断标准是当不同子任务需要根本不同的system prompt、不同的工具集、甚至不同的模型时。比如一个系统里有的任务需要严谨的代码能力用强模型代码工具有的任务需要创意写作用另一个模型无工具这两种角色塞进一个Agent的prompt里会互相干扰这时候拆成两个Agent才合理。5.2 三种多Agent拓扑结构及其适用场景拓扑结构通信方式适用场景主要风险主管-下属主管分发任务下属汇报结果任务可清晰分解需要统一调度主管成为瓶颈上下文爆炸流水线A的输出是B的输入依次传递阶段明确的处理流程错误逐级放大难回溯辩论/评审多个Agent对同一问题给出方案互相批判需要高质量决策容错要求高token消耗巨大可能无法收敛我实际项目里用得最多的是主管-下属结构但做了一个关键优化主管不保存所有下属的完整对话历史只保存每个下属的任务摘要最终结果。这样主管的上下文不会被撑爆。下属之间的通信一律通过主管中转不允许直接对话避免出现难以追踪的隐式依赖。5.3 多Agent通信的协议设计别让Agent自由聊天多Agent系统最容易失控的地方就是Agent之间的自由文本通信。A给B发一段话B理解偏了整个链路就歪了。我的做法是强制结构化消息class AgentMessage: def __init__(self, sender, receiver, task_id, status, payload, artifactsNone): self.sender sender # 发送方Agent标识 self.receiver receiver # 接收方Agent标识 self.task_id task_id # 关联的任务ID用于追踪 self.status status # request | response | error self.payload payload # 结构化数据不是自由文本 self.artifacts artifacts or [] # 产出物引用文件路径、数据ID等payload要求是结构化数据JSONartifacts用引用而不是内联内容。这样做的好处是每个消息都可校验、可日志、可重放。调试多Agent系统时你能清楚地看到每一步谁给谁发了什么而不是面对一堆自然语言对话抓瞎。5.4 多Agent的并发问题共享状态是万恶之源热词里有个ai agent怎么扛并发这确实是个真问题。多Agent并发执行时如果它们共享一个可变状态比如一个共享的草稿文档、一个共享的数据库连接就会出现竞态条件。我的经验是能不用共享状态就不用每个Agent操作自己的私有数据最后通过一个明确的合并步骤汇总。如果非要共享用乐观锁版本号冲突时让Agent重新读取最新状态再操作。6. Tool-Use编排模式工具多了之后怎么不乱6.1 工具数量超过15个模型选择准确率断崖下跌这是我在实际项目里测出来的经验值。当工具数量在10个以内时模型选对工具的概率很高超过15个之后相似工具之间的混淆开始明显超过30个基本就没法用了。解决办法不是换更强的模型而是做工具的分层路由。具体做法是先让一个轻量级的路由Agent根据用户意图判断属于哪个工具域比如数据库操作域、文件处理域、网络请求域然后只把该域下的工具描述塞给执行Agent。这样每次执行Agent看到的工具数量控制在10个以内准确率就回来了。6.2 工具描述怎么写才能让模型不选错工具描述的质量比工具本身的功能更重要。我见过太多工具描述写成查询数据这种废话模型根本不知道什么时候该用它。一个好的工具描述应该包含四个要素功能边界这个工具能做什么不能做什么触发条件什么情况下应该用这个工具参数说明每个参数的类型、含义、示例值返回格式返回什么结构的数据便于模型判断下一步tools [ { name: search_knowledge_base, description: 在企业内部知识库中搜索文档。适用于查找公司政策、流程规范、产品文档。不适用于查询实时数据或外部信息。, parameters: { query: {type: string, description: 搜索关键词建议使用具体的名词短语如报销流程而非怎么报销}, top_k: {type: integer, description: 返回结果数量默认5最大20} }, returns: 文档片段列表每项包含title、content、source_url } ]注意description里明确写了不适用于什么这个负向边界非常重要能大幅减少误调用。6.3 工具执行失败的降级策略工具一定会失败——网络超时、API限流、参数错误。一个健壮的Tool-Use编排必须有降级链。我的做法是给每个工具配一个降级策略重试适用于瞬时故障网络抖动最多重试2次指数退避替代工具比如主搜索工具挂了降级到备用搜索源返回缓存如果之前调用过相同参数返回缓存结果并标注数据可能不是最新优雅失败以上都不行返回结构化的错误信息让Agent决定是换思路还是告知用户关键原则是工具失败不能让整个Agent崩溃而应该变成一个Agent可以处理的观察结果。7. 五种模式怎么选一张决策表和几条实战心得7.1 模式选择决策表场景特征推荐模式理由步骤不确定每步依赖上一步结果ReAct灵活无需预先规划任务可分解需要全局视角Plan-and-Execute避免短视成本可控对输出质量要求高可接受延迟Reflection通过自省提升质量子任务需要不同prompt/工具/模型Multi-Agent角色隔离避免干扰工具数量多需要精确调用Tool-Use编排分层路由降级处理实际项目里这五种模式经常是组合使用的。我最近做的一个数据分析Agent就是外层用Plan-and-Execute做任务分解每个子任务内部用ReAct循环执行关键的分析结论用Reflection做一轮校验工具调用走分层路由。这不是炫技而是每个环节确实有对应的痛点需要解决。7.2 几条用血泪换来的心得第一先写单Agent跑不通再拆多Agent。我见过太多项目一上来就设计三四个Agent结果调试成本高到项目延期。单Agent加好工具能解决大部分问题。第二所有循环都要有硬性上限。不管是ReAct的步数、Reflection的次数、还是重新规划的轮数都必须有上限。没有上限的Agent就是一颗定时炸弹早晚烧光你的token预算。第三日志要记到每一步的完整输入输出。Agent出问题时你唯一能依靠的就是日志。我习惯把每一步的prompt、模型原始输出、工具调用参数、工具返回结果全部落盘出问题时能完整重放。第四别迷信框架。LangChain、AutoGPT这些框架能帮你快速起步但它们的抽象层会在你需要精细控制时变成障碍。我的建议是用框架跑通概念验证但生产系统里核心的循环逻辑自己写只借用框架的工具定义和模型调用部分。第五测试用例要覆盖模型不听话的情况。正常路径的测试谁都会写但Agent的bug往往出在异常路径模型输出了无法解析的格式、工具返回了空结果、模型陷入了重复循环。这些场景必须有针对性的测试用例。7.3 关于学废了这件事回到标题。Agent的设计模式之所以让人学废了是因为大部分教程只告诉你模式长什么样不告诉你模式在什么条件下会失效。ReAct会在工具报错时陷入死循环Plan-and-Execute会在计划假设错误时全盘崩溃Reflection会在没有外部信号时变成自我催眠Multi-Agent会在通信协议不严格时互相污染Tool-Use会在工具描述模糊时选错工具。真正学会这些模式不是能背出它们的定义而是当你的Agent出问题时你能立刻定位到是哪个模式的哪个环节出了岔子然后知道该加什么护栏。这个能力没有捷径只能靠一个个项目踩出来。但希望这篇把坑提前摊开的文章能让你少踩几个。最后分享一个我自己的习惯每做一个新Agent项目我都会先画一张图标出这个系统里用到了哪几种模式、每种模式的循环边界在哪里、失败时的降级路径是什么。这张图往往比代码本身更能帮我理清思路。你也可以试试。
返回列表