最近在重构一个工单助理的AI Agent第一版输在了过度相信长Prompt。我把系统提示词写得像一本操作手册结果模型该调库存的时候不调、该查流程的时候硬编几轮对话下来居然忘了自己最初的目标。后来反复拆了几次才明白真正影响Agent稳定性的是整体结构而不是提示词的篇幅。这篇文章我就基于这段时间的实际重构经验把AI Agent设计模式里最常用的六种拆开讲透ReAct循环、函数调用与工具编排、规划与执行分离、反思与自我修正、多智能体协作、记忆与检索增强。每种模式我都会讲清楚它解决什么问题、最小骨架怎么搭、以及哪些坑我亲身踩过。1. 为什么Agent开发需要“设计模式”而不是更长的Prompt先说一个我一直强调的观点Agent不是一次性的问答是一个“感知、决策、行动、观察”不断重复的运行系统。你给它一个任务它要能拆解、调工具、看结果、再决策直到得出最终答案。这个过程里任何一个环节不稳定整个链路的成功率都会暴跌。设计模式这个概念借鉴自传统软件工程意思是把那些反复出现、被验证过的结构沉淀成套路。比如服务端架构里的“网关模式”“事件驱动模式”到Agent开发这里我们也有“ReAct循环”“规划执行分离”这一类成熟套路。它们不会替你写业务代码但能帮你把Agent的系统骨架搭对。很多刚接触Agent的开发者会陷入一个误区效果不好就拼命加Prompt往系统提示词里写“你必须注意XX”“如果遇到XX要XX”最后一段提示词三四千字模型看起来聪明了一旦换场景就崩。原因是Prompt只能约束单次推理约束不了一个循环。你无法用Prompt保证“模型一定会调用工具”但在循环结构里加上强制校验、重试、超时就能让这个行为稳定下来。我用一张表把这六种模式先列出来后面逐章展开模式一句话定位典型场景ReAct循环推理、行动、观察交替进行多步查询、工具操作类任务函数调用与工具编排让模型通过规范接口使用外部能力需要实时数据、系统操作的Agent规划与执行分离先出计划再逐步执行长周期、多阶段复杂任务反思与自我修正生成结果后自检并修订对输出质量有严格要求的场景多智能体协作多个专长Agent分工配合职责差异大的复杂系统记忆与检索增强引入长期记忆和外部知识库个性化服务、知识密集型问答读这篇文章不需要你已经有很深的工程基础但建议你至少亲手调过一次大模型接口知道“上下文窗口”“System Prompt”这些概念。下面的代码都是示意级别的伪代码我刻意不绑定任何特定框架或厂商SDK目的是让你抓住设计模式本身而不是依赖某个产品。2. 第一个模式 ReAct推理与行动的循环骨架2.1 这个模式解决什么问题ReAct的核心思想是让模型在推理和行动之间交替先想一步再决定调什么工具或做什么动作拿到执行结果后继续思考直到能给出最终答案。我举一个生活中的类比。你在一个陌生城市找一家餐厅经验丰富的做法是先看地图思考决定往哪个方向走行动走到路口观察周围路牌观察再决定下一步。如果一上来就把整条路线背下来走到一半遇到修路就拉闸。ReAct解决的就是这种“走一步看一步”的策略问题。在Agent出现之前大模型的能力边界就是“生成文本”。它再聪明也没法自己查库存、发通知、改数据库。ReAct给了模型一条出路就是它只负责输出“我要调什么工具”由宿主程序真正执行执行结果再塞回对话里。这样模型既不越权又有能力影响真实世界。2.2 一个最小ReAct循环的实现实现ReAct最朴素的做法就是维护一个消息列表在循环里不断追加模型输出和工具执行结果直到模型输出终止标志。下面这段示意代码就是完整的循环骨架class ReActLoop: def __init__(self, model, tool_executor, system_prompt): self.model model self.tool_executor tool_executor self.messages [ {role: system, content: system_prompt} ] def run(self, user_request, max_iterations8): self.messages.append({role: user, content: user_request}) for _ in range(max_iterations): response self.model.chat(self.messages) if response.get(type) finish: return response[answer] if response.get(type) tool_call: observation self.tool_executor.run( response[tool_name], response[args] ) self.messages.append({ role: tool, content: f工具执行结果: {observation} }) return 已达到最大迭代轮数任务终止请注意我这里让模型返回结构化内容type字段而不是让它输出一大段散文。实际项目里建议让模型按固定的JSON格式输出比如{type: tool_call, tool_name: ..., args: {...}}程序侧做解析和校验解析失败就重试一次而不是靠正则去猜。2.3 我在这个循环里踩过的坑ReAct看起来简单实际工程里至少有四个坑是一定会遇到的。第一个坑是忘记设置最大迭代轮数。模型在一个小问题上反复绕圈如果不限制循环次数token费用直接拉满用户那边还一直转圈。我在工单助理里统一设了8轮上限并向上层返回“任务超时请简化问题或重试”。第二个坑是轨迹太长。每轮都追加完整思考文本和工具结果五六轮之后消息列表变得很长模型反而丢失了早期的关键信息。我的处理方式是每轮工具返回时只保留“结构化摘要”比如“结果共返回32条其中库存大于0的有5条”而不是把完整列表都塞给模型。真正的明细存到外部存储里模型需要时再按ID取用。第三个坑最隐蔽模型会伪造工具结果。明明宿主程序没执行工具模型在下一轮却能写出“工具返回了123件库存”这就是模型在自圆其说。规避方法只有一个就是工具执行结果必须由宿主程序注入永远不要相信模型自己描述的Observation内容。第四个坑是状态丢失后的死循环。如果任务执行到一半发现某个步骤不可能完成模型不会主动止步而是换个方式再试。这时候需要设计“终止条件校验”让系统识别“重复尝试相同动作”并强制终止把控制权还给人。3. 第二个模式 函数调用与工具编排让模型真正“动手”3.1 为什么要把“调用工具”单独抽出来只要用ReAct模式你迟早要面对一个问题模型说“我要调用query_stock工具”然后呢你的程序要接住这个意图校验参数执行真实函数最后把结果格式化返回。这一整段链路如果只是随手写在主流程里很快就会变成一堆if-else很难维护。函数调用模式就是给“模型使用工具”这件事定一套契约。你先把工具描述成结构化定义包括函数名、什么时候用、参数是什么、参数类型约束模型的输出不再是一个含糊的句子而是一份可以被程序直接解析的“工具调用请求”。3.2 工具描述比你想的更关键工具描述写得不好模型就会乱选工具或者参数传错。我见过最典型的例子是把描述写成一篇论文里面讲了一堆实现细节模型根本抓不住重点。一份合格的工具定义应该长这样[ { name: query_stock, description: 查询指定商品在指定区域仓库的实时库存量。只在用户询问库存、可发货数量、缺货情况时使用其他场景不要调用。, parameters: { type: object, properties: { item_id: { type: string, description: 商品编码形如 SKU-2024-001 }, area_code: { type: string, description: 区域仓库代码例如 east / north / south } }, required: [item_id, area_code] } } ]通过对比你会发现描述里最重要的是“什么时候用、什么时候不用”这个边界信息而不是函数内部怎么实现。参数schema尽量严格能枚举的就枚举不要让模型自由发挥。3.3 一次完整工具调用的代码流程假设现在模型决定调用query_stock代码侧的流程是解析请求安全校验执行函数把结果以tool消息追加回去然后让模型继续。示意如下tools load_tool_definitions() response model.chat(history [user_request], toolstools) if response.has_tool_calls: tool_call response.tool_calls[0] # 安全校验工具名必须存在参数必须通过schema校验 assert tool_call.name in registered_tools result execute_registered_tool( tool_call.name, validate_params(tool_call.arguments) ) history.append({ role: tool, tool_call_id: tool_call.id, content: format_result(result) }) final_response model.chat(history)这里有几个细节容易被忽略。第一当模型返回多个工具调用时如果彼此无依赖关系应该并发执行以缩短用户等待时间。第二工具执行失败时不要把异常堆栈直接丢给模型而是转化成一句客观描述“查询失败原因是账号无权限”让模型决定是换参重试还是终止。第三所有工具调用都要走白名单注册不能允许模型凭空发明一个函数名。3.4 工具模式常见的三个事故现场我在内部工具里遇到最多的事故一个是“工具描述空泛导致模型乱调”。比如某查询函数描述写的是“查询数据”模型几乎任何问题都会选它结果大量无效调用。改成“仅在用户询问销售汇总数据时使用”之后准确率立刻上来了。第二个事故是参数schema太宽松。模型把item_id传到了area_code的位置或者传了一个完全无关的字符串。现在我在schema里会写清楚格式示例并增加主机端的参数校验宁可返回错误让模型重试也不能让脏参数进入业务系统。第三个事故非常危险工具报错后模型不重试也不终止而是“编造”一个看似合理的返回结果继续推理。程序员看到的是成功输出用户看到的是假数据。要根治这个问题就需要把“工具结果可信”作为系统的铁律任何工具异常都要被上层捕获并显式反馈给模型而不是让模型当成普通上下文继续自洽。4. 第三个模式 规划与执行分离先画地图再走路4.1 什么时候会用到这个模式ReAct适合那种“边做边想”的任务但也有反例。用户说“帮我生成一份本月销售周报”如果走ReAct模型很可能已经查询了数据、生成了图表却在某个环节忘记周报的整体结构最后交出来一份四不像。这种长周期、多步骤、有依赖关系的任务更适合先规划再执行。“规划与执行分离”就是把“拆解任务”和“执行任务”解耦让规划器先产出蓝图执行器再照着蓝图一步一动。这个分工有点像装修房子你不会让一个工人边拆墙边决定卧室怎么布局而是先出设计图确定哪里是承重墙、哪里放水管工人按图施工。如果施工中发现图纸有问题回到规划阶段去改图纸而不是工人现场自由发挥。4.2 规划器和执行器怎么配合规划器的输出建议用JSON方便程序读取和校验。这里是一个周报任务的计划示例{ goal: 生成一份本月销售周报, steps: [ { id: 1, action: query_sales_data, params: {range: month}, expected: 销售明细数据表 }, { id: 2, action: make_trend_chart, params: {metric: sales_trend}, expected: 趋势图文件地址 }, { id: 3, action: write_report, params: {use_step_ids: [1, 2]}, expected: Markdown周报 } ] }执行器按顺序执行但有几个关键约束每步都要记录状态标记为pending / running / done / failed某一步失败后不是简单重试同一步而是把错误信息送回给规划器让规划器决定是换动作还是改计划整个执行过程要有上限比如最多允许5次重规划。执行流程的伪代码如下plan planner.create_plan(request) for step in plan[steps]: mark_step(step, running) result execute_step(step) if result.status ok: mark_step(step, done) save_intermediate_result(step[id], result.output) continue # 步骤失败交给规划器重新规划 if replan_count 5: plan planner.replan_failed_step( current_planplan, failed_stepstep, error_messageresult.error ) replan_count 1 else: return 任务失败重规划次数已达上限这里值得展开说一下“expected字段”的作用。它不是为了写给开发者看的注释而是执行器用来判断步骤是否真正成功的依据。比如第1步的预期是“销售明细数据表”如果工具返回了空结果或者错误提示这一步骤就不算成功不能糊弄着往下走。有了expected整个执行过程的可观测性和可控性都会提升一大截。4.3 规划执行模式下最常见的失败这个模式最大的坑在于规划器输出的步骤过于抽象。比如第1步是“分析数据”第2步是“生成报告”执行器收到这种计划根本无从下手。要解决这个我在规划器的系统提示词里会强行要求“每个步骤都必须包含具体的action名称、参数和预期产出不允许出现无法执行的动词。”第二个坑是重规划没有任何限制导致任务永远执行不完。步骤失败一次就重规划一次重规划的产出又失败来回拉扯。所以重规划上限必须硬编码到了上限直接降级成“把当前进度和失败原因打包反馈给用户”这比无限循环体面得多。第三个坑和上下文有关。执行器每完成一步都要把中间结果摘要回写到上下文但同时要清理掉无用的原始输出否则十几步之后上下文里堆积了大量数据模型反而更笨。记住上下文是一个有限的工作台只放当前步骤需要的东西。5. 第四个模式 反思与自我修正给输出加一道质检5.1 为什么一次生成不够大模型的一次生成能力再强也保不齐在某些细节上翻车。生成一份JSON结果时字段名写错、满足字数时结构散乱、使用工具答案时推理经不起推敲这些都是常见问题。如果让Agent把第一版输出直接交给用户等于上线了一个没有质检的工厂。反思模式就是在生成器之外再挂一个评审器让生成结果先被检查不合格就打回去改直到通过评审标准或者达到轮数上限。你可能觉得“让模型自己检查自己”不太可靠但实践下来只要评审标准足够具体这个模式能显著提升输出质量。5.2 生成器和评审器的最小实现最朴素的做法是同一个模型跑两套不同的系统提示词一套负责生成和修改一套负责挑毛病。生成器不管质量只管把初稿做出来评审器不负责修改只输出“通过”或者“不通过原因”。def refine_with_critic( generator, critic, request, criteria, max_rounds3 ): draft generator.generate(request) for _ in range(max_rounds): feedback critic.review(draft, criteria) if feedback.is_pass: return draft draft generator.refine( request, draft, feedback.comments ) return draft评审器的标准一定要具体。比如“输出是否包含明确的结论”“是否遗漏了不能缺省的字段”“格式是否为合法JSON”而不是笼统的“内容是否足够好”。我一般把评审标准以列表形式写在评审器的提示词里并让它逐条打分这样反馈才有可操作性。5.3 三种更可靠的评审方式纯模型评审有一个天然缺陷自我服务偏差。生成器写的东西换一个角色来检查模型仍然容易“看自己越看越顺眼”。我的应对方式有三层。第一层能用程序校验的不用模型评审。比如要求输出JSON就先做一次schematools的schema校验非法JSON直接打回。要求包含“风险等级”“建议措施”字段就做字段存在性检查。程序拦截比模型评审可靠得多。第二层评审模块独立成一次新的模型调用而不是在同一个回复里让模型“先生成再自查”。独立调用意味着新的上下文视角能减少惯性。第三层把“测试反馈”当成评审器。如果是代码生成类Agent就让它跑编译器和单测用失败信息作为反馈。这比让模型点评代码风格更接近真实质量。5.4 反思循环的收敛问题反思模式最大的风险是不收敛。评审器每次都能从初稿里挑出点毛病生成器越改越碎改完A又弄坏B几轮下来初稿的好内容反而被破坏了。我的经验是设置严格的轮数上限通常2到3轮就足够。其次要收敛修改范围评审器反馈给生成器的意见不要超过3条聚焦最影响交付的问题。最后如果某一轮修改后的输出质量评分低于上一轮就主动回退到上一版这能防止“越改越差”的螺旋。实际效果比固定保留初稿或终稿都要好。6. 第五个模式 多智能体协作拆掉单体的墙6.1 为什么一个Agent搞不定的任务交给多个就能搞定单Agent架构里所有工具、规则、知识都堆在一个上下文里。当任务复杂度上来你会发现模型经常“串味”写文案的角色突然想调用数据分析工具做客服的角色忍不住点评公司政策。这就是角色职责互相干扰。多智能体协作模式的思路是拆分把系统拆成多个更小的Agent每个Agent只干一件事配上专属工具和记忆再由一个调度者负责协作。这种模式很像公司化运作一个人不够强就建个团队各司其职再合力交付。6.2 三种常见的协作形态第一种是主管-执行者模式也常被称为supervisor模式。一个主管Agent负责听用户诉求、判断该派给谁然后把任务分发给对应的专家Agent接收结果后汇总。第二种是流水线模式。比如一个“内容生产流水线”分析Agent产出选题和要点写作Agent写成初稿校对Agent做事实和错别字检查。每个Agent只和前后的环节打交道链路清晰。第三种是辩论模式。两个Agent针对一个问题各自论证再互评对方的漏洞用来逼近一个更客观的结论适合“这个方案是否有风险”这类没有标准答案的决策问题。我最早实践的是主管-执行者模式示意代码大致长这样def supervisor_loop(request): # 主管Agent决定路由 route dispatcher.delegate(request) if route.target analysis_agent: result analysis_agent.run( route.task_payload ) elif route.target writing_agent: result writing_agent.run( route.task_payload ) # 主管汇总结果或者再交给第三个Agent审查 final_answer dispatcher.summarize( request, route.target, result ) return final_answer这里的delegate动作本身也是一次模型调用主管Agent输出的是路由目标选择而不是一段散文。把这个决策结构化后续的分配和追踪都会简单很多。6.3 多Agent协作的四条实战纪律第一通信协议要结构化。各个子Agent之间最好通过JSON报文传递“任务描述、上下文摘要、期望产出”而不是用自然语言来回辩论。自然语言通信会让系统变得不可控两个Agent能用半页纸争论一个无关词句的定义。第二上下文必须隔离。每个子Agent只需要拿到自己的那部分上下文不要共享完整对话历史。很多人觉得多Agent效果差往往是因为所有的Agent都在看同样的超长上下文模型分不清哪句话是对自己说的。第三职责边界要写在“边界说明书”里而不只是靠名字区分。给每个Agent写清楚“你负责什么、你不负责什么、你收到任务后必须输出什么格式”能极大减少互相推诿。第四多重Agent的代价是token消耗成倍增长。如果一个任务用单个Agent加规划模式就能解决就别强行上多Agent。成本翻倍但效果不一定翻倍这是最常见的过度设计。我自己的习惯是先用单Agent跑通完整流程再把最容易冲突、最值得独立的环节拆出去。比如先有了一个全能内容Agent发现它写文案时忍不住查数据、做校对我就把“校对”拆成一个独立Agent。这种演化式拆分比一开始就规划三个Agent稳得多。7. 第六个模式 记忆与检索增强让Agent拥有长期记忆7.1 大模型天生的两个短板大模型有两个先天缺陷一个是上下文窗口有限一个是对话结束之后不记得任何事情。你在上午告诉它你的偏好下午它问起来依然一脸茫然。记忆与检索增强模式就是为了补齐这两个短板。它不是一个单点功能而是把“短期记忆、工作记忆、长期记忆”分层处理的系统设计其中检索增强部分通常也叫RAG负责把外部文档和知识库变成可查询的长期记忆。7.2 三层记忆各管什么我把Agent的记忆分为三层。短期记忆就是当前对话的上下文窗口负责维持当下话题的连贯性。你不需要额外存储因为它天然存在但要注意别让无用的历史占满窗口。工作记忆类似人类的“手头便签”记录当前任务进行到哪一步、哪些中间结果已经拿到、下一步要做什么。它是规划执行模式和多Agent协作里最重要的状态载体。长期记忆是真正需要持久化的部分。它可以是向量数据库里的文本片段也可以是键值存储里的用户偏好和结论。Agent在后面的会话里通过检索召回这些内容表现出“还记得你”的效果。下面是一个简化版的记忆管理器示例class MemoryManager: def __init__(self, vector_store, embedder): self.store vector_store self.embedder embedder def save(self, user_id, text, metaNone): vector self.embedder.encode(text) self.store.add( vectorvector, payload{ user_id: user_id, text: text, meta: meta or {}, created_at: now() } ) def recall(self, user_id, query, top_k5): vector self.embedder.encode(query) hits self.store.search( vector, top_ktop_k ) return [ hit.payload[text] for hit in hits if hit.payload.get(user_id) user_id ]使用的时候并不是把召回内容简单拼进上下文就完事而是要经过一层筛选和重写只把和当前问题真正相关的部分填进去。7.3 RAG和Agent循环怎么结合RAG最常见的落地方式是把“检索”封装成一个工具供Agent在ReAct循环里调用。比如一个知识库客服Agent用户问“退货政策是什么”模型决定调用search_policy工具工具返回匹配的政策片段模型根据片段组织回答并在回答末尾附上来源编号。在实现上要注意检索结果返回的是原文片段不是“模型自己的话”。模型能做的是整合、判断、引用而不是改写事实。否则很容易出现在Agent循环里检索了一次之后的几轮就把检索原文遗忘了开始靠自己的知识硬编。我的处理办法是每轮用到一个检索结果就把结果连同来源重新放回当前上下文保证模型随时能看到原始证据。7.4 记忆系统最容易翻车的地方先说检索质量。RAG的效果上限取决于切片和检索质量而不是模型能力。切片切得太碎上下文里塞进一堆重复片段模型不知道该信哪段向量检索的召回不够准相关文档排在很后面模型被无关内容带偏。建议先把检索结果先压缩成2到3个要点再让模型使用而不是把整段文档塞进去。第二个坑是乱记。把用户每一句话都存进长期记忆只会制造噪音。我一般只保存三类东西用户的明确偏好、任务未完成的状态、已经确定的事实结论。其他对话内容属于短期记忆用完即弃。第三个坑是记忆过期。用户上周说“我想订商务舱”今天可能已经换需求了。给每条长期记忆打上时间戳并在召回时对过期的条目降权必要时主动询问用户“上回你提到XX这次还需要吗”。这比傻傻地把旧信息当成当前偏好要安全得多。8. 六种模式的组合选型与落地顺序8.1 一张选型表定位你的场景把六种模式摆在一起你可能已经发现它们并不互斥而是可以像积木一样组合。我按最常见的诉求给出一张选型建议业务诉求推荐组合简单的实时问答ReAct 函数调用知识库客服ReAct 函数调用 RAG记忆个性化助理ReAct 函数调用 三层记忆长周期报告生成规划与执行分离 反思复杂研发/数据分析规划与执行分离 多智能体 反思高可控交付型系统全套逐层叠加一张很有用的出发点是ReAct加函数调用是绝大多数Agent的地基。没有这个地基规划执行没有可以调用的能力反思只能改文字不能纠事实多智能体也只是空中楼阁。8.2 推荐的从零到一落地顺序如果你正准备从零搭一个Agent我建议按照下面的顺序演进每一步都做到可观测、可回滚再进入下一步。第一步先做一个“单轮问答加一个工具调用”的最小闭环。用户提问模型决定是否调工具宿主执行模型给出答案。这一步你能把工具定义、参数校验、错误反馈这些基础问题搞定。第二步把它升级成ReAct循环增加多轮工具调用和终止条件。此时你的Agent已经从“一次对话”变成了“一个会行动的系统”。第三步加入记忆层先做短期记忆的摘要滚动再做长期记忆的向量召回。你会发现用户体验有质的提升。第四步引入规划与执行分离开始承接多步骤长任务。同时挂上反思模式给系统输出加质检环节。最后一步等单Agent架构确实出现职责冲突时再拆多智能体。永远不要让系统一开始就进入多Agent状态那会让调试成本高到难以承受。8.3 我对这套模式的渐进式体会重构工单助理的过程中我最大的感受是所谓设计模式本质上是在给Agent设定边界。ReAct给它行动边界函数调用给它能力边界规划给它任务边界反思给它质量边界多智能体给它职责边界记忆给它时间边界。边界越多系统的可预测性越强。我也提醒自己不要为了套模式而套模式。如果你的业务只需要固定流程的自动化和模板生成直接用代码写死流程比规划执行加反思更便宜、更稳定。模式是给需要模型自主决策的场景准备的不是用来取代确定性逻辑的。如果你正在调试Agent无从下手我的建议是先拿起一个真实的小任务把“ReAct加函数调用”这个最小组合跑通再慢慢往上加东西。路要一段一段走骨架要在第一周就打正否则后面加再多模式都是给歪基础盖楼。