1. 从“会聊天的模型”到“能办事的Agent”:我为什么要啃架构去年帮一个做电商的朋友看他们的客服系统,他们接了一个大模型接口,本意是想让AI自动处理退换货咨询。结果上线第一周就翻车了:用户问“我上周买的鞋能退吗”,模型很礼貌地回复“一般来说七天无理由退货”,然后就没有然后了——它既没查订单,也没判断时间,更没触发退款流程。朋友很郁闷地问我:“这玩意儿不是挺聪明的吗,怎么一到干活就掉链子?”这个问题其实特别典型。大多数人第一次接触大模型,体验的都是“对话”能力:你问它答,聊得头头是道。但一旦你希望它自主完成一件多步骤的事,比如“查一下我最近的订单,把符合条件的退掉,然后发一封确认邮件”,纯对话模型就露馅了。它没有手,没有记忆,也没有“先做什么后做什么”的规划能力。AI Agent(智能体)要解决的,正是这个“从说到做”的鸿沟。简单讲,Agent就是给大模型装上手脚、记忆和一套做事的方法论,让它能感知环境、拆解目标、调用工具、根据反馈调整,最终把一个模糊的需求落地成具体的结果。而“Agent架构”这个词,说的就是这套“手脚+记忆+方法论”是怎么搭起来的——模块怎么划分、数据怎么流转、决策在哪一步做、出错怎么兜底。我写这篇东西,不是要给你一份学术综述。市面上讲Agent概念的文章已经够多了,但真正把架构拆开、告诉你“为什么这么设计”“哪一层最容易出问题”“自己动手该从哪下手”的内容并不多。这篇内容适合三类人:一是想从“调API写Demo”进阶到“做能落地的Agent系统”的开发者;二是产品/项目负责人,需要判断一个Agent方案靠不靠谱;三是纯粹好奇“这东西到底怎么运转”的技术爱好者。我会尽量用大白话把架构讲透,该上表格上表格,该给伪代码给伪代码,读完你至少能自己画出一张Agent的架构图,并且知道每个模块的坑在哪。2. Agent架构的四根支柱:拆开看每个模块到底在干什么在动手之前,得先把架构的骨架认清楚。我见过不少人一上来就堆框架、调库,结果出了问题完全不知道是哪一层的事。其实不管用什么框架,一个完整的Agent架构基本都逃不出四个核心模块:感知(Perception)、规划(Planning)、记忆(Memory)、执行(Action)。这四个词听着像教科书,但我用实际项目里的理解给你翻译一遍。2.1 感知层:Agent的“眼睛和耳朵”不只是读用户输入很多人以为感知层就是“接收用户消息”,这理解太窄了。感知层的本质是把外部世界的各种信号转成Agent能处理的内部表示。用户输入只是其中一种,还包括:系统返回的工具调用结果、环境状态的变化(比如某个文件被修改了)、甚至时间流逝这种隐式信号。举个例子,你让Agent“每天早上帮我汇总行业新闻”。这里的感知不只是“收到这条指令”,还包括它得知道“现在几点了”“今天是不是工作日”“上次汇总是什么时候”。如果感知层只处理文本输入,这个任务根本没法触发。感知层还有一个容易被忽略的职责:输入清洗和意图归一化。用户说的话往往是模糊的、带情绪的、有歧义的。比如“这破订单怎么还没到”,感知层要能提取出核心意图是“查询订单物流状态”,而不是被“破”这个情绪词带偏。我一般会在这一层做一个轻量的意图分类,把输入映射到预定义的任务类型上,后面规划层就好处理得多。提示:感知层不要做太重的逻辑判断。它的定位是“翻译官”,把外部信号翻译成内部格式,判断和决策留给规划层。职责混在一起,后期调试会非常痛苦。2.2 规划层:决定“先干什么后干什么”的大脑规划层是Agent架构里最核心、也最难做好的部分。它要回答的问题是:给定一个目标,我应该拆成哪几步?每步用什么工具?如果某步失败了怎么办?最朴素的规划就是“让大模型直接输出一个步骤列表”,也就是所谓的ReAct模式(Reasoning + Acting)。模型先想一步(Reason),再做一个动作(Act),看结果,再想下一步。这种方式灵活,但有个致命问题:它没有全局视野。模型可能走到第三步才发现第一步的方向就错了,然后开始来回打转。所以成熟一点的架构会引入分层规划。上层做一个粗粒度的任务分解,比如“查订单→判断是否符合退货→执行退款→发通知”,下层针对每个子任务再做细粒度的工具选择和参数填充。这就像公司里老板定战略、主管定战术、员工做执行,层次分明,每层只关心自己范围内的决策。我实测下来,分层规划最大的好处是可调试。当Agent行为异常时,你能快速定位是“战略错了”还是“战术错了”。如果只有一层,你面对的就是一坨黑盒,只能靠反复改提示词碰运气。规划方式优点缺点适用场景单层ReAct实现简单,灵活容易跑偏,无全局观简单单步任务分层规划可调试,稳定实现复杂,需要设计层级多步骤复杂任务预定义工作流最稳定可控不灵活,无法处理意外流程固定的业务2.3 记忆层:别把“上下文窗口”当成记忆这是新手最容易踩的坑。很多人觉得“大模型不是有上下文窗口吗,把历史对话都塞进去不就是记忆了?”短期看行得通,但一旦任务变长、交互变多,上下文窗口就会被撑爆,而且模型对超长上下文的注意力会衰减——中间的信息它经常“看不见”。真正的记忆层要解决三个问题:存什么、怎么存、怎么取。存什么?不是所有对话都值得记。我一般把记忆分成三类:事实记忆(用户是谁、偏好什么)、经验记忆(上次类似任务是怎么解决的)、工作记忆(当前任务的中间状态)。事实和经验是长期记忆,工作记忆是短期的,任务结束就可以清掉。怎么存?简单的事实可以用键值对,复杂的经验得用向量数据库做语义检索。这里有个经验:不要什么都往向量库里塞。向量检索适合“模糊匹配相似经验”,但如果你要精确查“用户ID是12345的收货地址”,用结构化存储快得多也准得多。我通常会用混合方案:结构化数据走数据库,非结构化经验走向量库。怎么取?这是最考验设计的地方。取多了,上下文被无关信息污染;取少了,Agent又缺信息。我的做法是按需检索:规划层在每一步决策前,主动告诉记忆层“我现在需要什么信息”,记忆层再去取,而不是一股脑全塞进去。2.4 执行层:工具调用不是“调个API”那么简单执行层负责把规划层的决策变成实际动作——调用API、读写文件、发消息等等。听起来简单,但坑特别多。第一个坑是工具描述的质量。大模型靠工具的自然语言描述来决定用哪个工具、传什么参数。如果你的工具描述写得含糊,模型就会乱用。我见过一个案例,两个工具一个叫“查询订单”,一个叫“查询物流”,描述都写得很简略,结果模型经常搞混。后来把描述改成“查询订单:根据订单号返回订单的商品、金额、状态;查询物流:根据订单号返回包裹的当前位置和预计送达时间”,混淆率立刻降下来了。第二个坑是参数校验。模型生成的参数不一定合法,比如日期格式不对、ID不存在。执行层必须做校验,不合法就返回明确的错误信息给规划层,让它重新决策。这里的关键是错误信息要具体,不能只说“参数错误”,要说“订单号ABC123不存在,请确认后重试”,这样模型才知道怎么改。第三个坑是幂等性。Agent可能会因为重试机制重复调用同一个工具。如果这个工具是“扣款”,重复调用就是灾难。所以执行层对写操作必须做幂等设计,比如用唯一请求ID去重。3. 一次完整的Agent运转:把架构跑起来是什么样光讲模块还是抽象,我拿一个具体任务把整个流程串一遍。任务是这样的:“帮我看看最近三天有没有未发货的订单,有的话催一下。”3.1 从用户一句话到任务分解的完整链路第一步,感知层接收输入。它识别出这是一个“订单查询+催发货”的复合任务,提取关键约束“最近三天”“未发货”。同时,感知层从记忆层拿到当前用户的身份信息(比如用户ID),因为“我的订单”需要知道“我”是谁。第二步,规划层做任务分解。上层规划把任务拆成:①查询用户最近三天的订单;②筛选出状态为“未发货”的订单;③对每个未发货订单执行催发货操作。下层规划针对第一步,决定调用“查询订单”工具,参数是用户ID和时间范围。第三步,执行层调用工具。它调用订单查询接口,拿到一批订单数据。假设返回了5个订单,其中2个未发货。第四步,结果回到规划层。规划层看到有2个未发货订单,决定对每个执行催发货。这里它需要调用“催发货”工具,参数是订单号。第五步,执行层再次调用工具。催发货接口返回成功。规划层确认所有子任务完成,生成最终回复:“已为您催促2个未发货订单,订单号分别是XXX和YYY。”第六步,记忆层更新。把这次操作记录到经验记忆里,下次遇到类似任务可以更快处理。整个链路里,规划层和执行层之间是一个循环:规划→执行→观察结果→再规划。这个循环什么时候停?要么任务完成,要么达到最大步数限制,要么遇到无法恢复的错误。我一般会设一个步数上限(比如15步),防止Agent陷入死循环烧钱。3.2 循环、分支与终止条件:Agent什么时候该停Agent的运转本质上是一个带终止条件的循环。但“什么时候停”这件事,比想象中微妙。最理想的情况是任务完成,Agent自己知道“搞定了”。但模型经常过度自信,明明没查完就说“已完成”。所以我会在规划层加一个完成度检查:每个子任务完成后打勾,所有子任务都打勾才允许终止。这比让模型自己判断靠谱得多。第二种终止是达到步数上限。这是兜底机制,防止无限循环。但步数设多少有讲究:设太小,复杂任务做不完;设太大,出问题时浪费资源。我的经验是,根据任务平均步数的2-3倍来设,比如平均5步完成的任务,上限设15步。第三种是遇到不可恢复错误。比如工具连续失败、权限不足、关键信息缺失。这时候Agent应该优雅地退出,并告诉用户“我卡在哪了、需要什么帮助”,而不是硬着头皮瞎试。还有一种容易被忽略的终止:任务目标本身发生变化。比如用户中途说“算了不用催了”,Agent得能感知到并中止当前流程。这要求感知层在循环中持续监听新输入,而不是只在开头听一次。3.3 工具调用的参数是怎么“填”进去的很多人好奇:模型怎么知道该传什么参数?答案是靠提示词里的工具定义+上下文信息。工具定义一般长这样(伪代码):{ "name": "query_orders", "description": "根据用户ID和时间范围查询订单", "parameters": { "user_id": {"type": "string", "description": "用户唯一标识"}, "start_date": {"type": "string", "description": "开始日期,格式YYYY-MM-DD"}, "end_date": {"type": "string", "description": "结束日期,格式YYYY-MM-DD"} } }模型看到这个定义,再结合上下文里“用户ID是12345”“最近三天”这些信息,就能生成调用参数。但这里有个细节:“最近三天”这种相对时间,模型不一定能准确转成具体日期。所以我会在感知层或规划层做一个时间归一化,把“最近三天”转成“2024-06-01到2024-06-03”再传给模型。这比让模型自己算靠谱得多。另一个细节是可选参数的处理。如果某个参数不是必填的,模型可能不传,也可能传个空值。执行层要能区分“没传”和“传了空”,前者用默认值,后者可能报错。4. 架构落地时最容易翻车的几个地方架构图看着清晰,真动手做的时候,坑一个接一个。我挑几个最有代表性的说说。4.1 规划层“想太多”和“想太少”的平衡规划层最常见的毛病是两个极端:过度规划和规划不足。过度规划的表现是:一个简单任务被拆成十几步,每步都要调工具、查记忆,结果又慢又贵,还容易在某个无关紧要的步骤上卡住。我见过一个Agent,用户就问“今天天气怎么样”,它先查用户位置、再查天气API、再格式化输出、再检查格式、再……其实一步就能搞定。规划不足的表现是:该拆的不拆,指望模型一步到位。比如“帮我订一张明天去某地的票”,模型直接调订票接口,但没先查有没有余票、没确认用户偏好,结果订错了。怎么平衡?我的经验是按任务复杂度动态调整。简单任务(单工具、单步骤)直接执行,不规划;中等任务(多工具、有依赖)做一层规划;复杂任务(多阶段、有条件分支)才上分层规划。判断复杂度可以靠感知层的意图分类,也可以让模型自己评估“这个任务需要几步”。4.2 记忆检索的“相关性陷阱”记忆检索最怕的是检索到看似相关实则误导的信息。比如用户上次退货是因为“尺码不对”,这次咨询的是“颜色不喜欢”,如果记忆层把上次的退货原因直接推给规划层,Agent可能就会按“尺码问题”的流程去处理,跑偏了。避免这个陷阱的关键是检索时带上任务上下文。不要只拿用户输入去检索,要把当前任务类型、当前步骤也作为检索条件。比如当前是“退货原因判断”步骤,检索时就优先找“退货原因”相关的记忆,而不是泛泛的“退货”记忆。还有一个技巧是给记忆加时效权重。越近的记忆权重越高,太老的记忆除非特别相关,否则不推。这符合直觉:用户上个月的偏好,可能这个月就变了。4.3 工具返回结果“太长”把上下文撑爆工具返回的数据经常很长,比如查询订单返回一个包含几十个字段的JSON。如果原样塞回上下文,几轮下来上下文就满了,而且模型会被无关字段干扰。我的处理方式是在执行层做结果裁剪。工具返回后,先提取规划层真正需要的字段,把其余的都丢掉。比如订单查询只需要“订单号、状态、金额”,那就只保留这三个字段。这需要在工具定义里就声明“返回哪些字段有用”,或者让规划层在调用时指定“我只要状态字段”。如果结果确实很长又没法裁剪(比如返回一篇长文档),那就得做摘要压缩。让一个小模型或者规则引擎先把长文本压成几句话,再塞回上下文。这一步会增加延迟,但能显著提升稳定性。4.4 错误处理:Agent卡住时到底该重试还是放弃Agent执行过程中出错是常态。关键是要区分可重试错误和不可重试错误。可重试错误包括:网络超时、临时限流、参数格式小错。这类错误重试一两次往往就好了。但重试要有退避策略,不能立刻重试,否则可能加剧限流。我一般用指数退避:第一次等1秒,第二次等2秒,第三次等4秒。不可重试错误包括:权限不足、资源不存在、逻辑矛盾。这类错误重试多少次都没用,应该直接上报给规划层,让它换方案或者终止。还有一种模糊错误,比如“操作失败”但没说原因。这种最麻烦。我的做法是让执行层尽量把原始错误信息带回来,哪怕是一串错误码,也比“失败”两个字强。规划层看到具体错误码,才有可能做出正确判断。错误类型例子处理策略可重试超时、限流指数退避重试2-3次不可重试权限不足、资源不存在上报规划层,换方案或终止模糊错误无具体信息的失败尽量获取原始错误,再判断5. 从零搭一个最小可用Agent:我的实操路径理论讲完了,说说怎么动手。我不建议一上来就用重型框架,那样你学到的只是“怎么用框架”,而不是“Agent怎么运转”。我的路径是先用最原始的方式跑通一个最小闭环,再逐步加模块。5.1 先跑通“感知-规划-执行”最小闭环第一步,写一个最简单的循环:接收用户输入→调用大模型→如果模型说要调工具就调→把结果喂回模型→重复直到模型说完成。这个阶段不需要记忆层,不需要分层规划,甚至不需要复杂的工具定义。就一个工具,比如“查天气”,把整个链路跑通。目的是理解循环是怎么转的、消息是怎么在模型和工具之间传递的。伪代码大概长这样:messages = [{"role": "user", "content": user_input}] for step in range(MAX_STEPS): response = llm.chat(messages, tools=tool_definitions) if response.has_tool_call: result = execute_tool(response.tool_call) messages.append(response.tool_call) messages.append({"role": "tool", "content": result}) else: return response.content跑通这个,你就理解了Agent的“心跳”。5.2 给Agent加上第一层记忆最小闭环跑通后,加记忆。先加最简单的工作记忆:把每一步的工具调用结果存下来,下一步规划时带上。这能解决“模型忘了上一步做了什么”的问题。然后加事实记忆:用一个简单的键值存储,存用户的基本信息。每次规划前,把相关事实取出来塞进上下文。这个阶段你会遇到第一个真正的挑战:上下文越来越长。这时候你就自然理解了为什么需要结果裁剪和摘要压缩。5.3 把单层规划升级成分层规划当任务变复杂,单层规划开始力不从心时,引入分层。上层规划只输出“子任务列表”,下层规划针对每个子任务做具体决策。实现上,可以用两个不同的提示词模板:一个用于任务分解,一个用于具体执行。任务分解的提示词强调“只输出步骤,不要执行”;执行提示词强调“根据当前步骤和上下文,决定调什么工具”。这个升级不是必须的,但如果你要做多步骤复杂任务,早晚得走这一步。5.4 用真实任务压测:我踩过的三个坑搭好之后,别急着上线,用真实任务压测。我踩过的坑:坑一:模型在工具调用失败后反复重试同一个错误参数。原因是错误信息不够具体,模型不知道错在哪。改成具体错误信息后解决。坑二:Agent在某个步骤卡住后,把之前成功的步骤也重做了。原因是工作记忆没记录“哪些步骤已完成”。加上完成度标记后解决。坑三:多个工具功能重叠,模型选错工具。原因是工具描述没写清楚边界。把每个工具的“适用场景”和“不适用场景”都写进描述后解决。6. 关于Agent架构,我踩过坑之后的一些个人体会做Agent架构这件事,最深的体会是:它更像搭积木而不是写代码。代码写错了编译器会报错,但Agent架构设计错了,它不会报错,只会“表现得很奇怪”——答非所问、绕圈子、该停不停。你得靠观察和日志去反推是哪一层出了问题。另一个体会是不要追求一步到位的完美架构。我见过有人一上来就设计五层记忆、三层规划、动态工具路由,结果调试成本高到根本跑不起来。正确的做法是先用最小闭环跑通,遇到问题再加模块,每个模块的引入都要有明确的“它解决了什么之前解决不了的问题”。还有一点:Agent的稳定性很大程度上取决于工具的质量。工具描述清晰、参数校验严格、错误信息具体,Agent的表现就会好很多。与其花时间调提示词,不如先把工具层做扎实。最后分享一个我常用的调试技巧:把Agent的每一步决策都打日志,包括它看到了什么上下文、做了什么决策、调了什么工具、拿到了什么结果。出问题时,把日志从头到尾看一遍,90%的问题都能定位。这比盯着最终输出瞎猜高效得多。
1. 从“会聊天的模型”到“能办事的Agent”:我为什么要啃架构去年帮一个做电商的朋友看他们的客服系统,他们接了一个大模型接口,本意是想让AI自动处理退换货咨询。结果上线第一周就翻车了:用户问“我上周买的鞋能退吗”,模型很礼貌地回复“一般来说七天无理由退货”,然后就没有然后了——它既没查订单,也没判断时间,更没触发退款流程。朋友很郁闷地问我:“这玩意儿不是挺聪明的吗,怎么一到干活就掉链子?”这个问题其实特别典型。大多数人第一次接触大模型,体验的都是“对话”能力:你问它答,聊得头头是道。但一旦你希望它自主完成一件多步骤的事,比如“查一下我最近的订单,把符合条件的退掉,然后发一封确认邮件”,纯对话模型就露馅了。它没有手,没有记忆,也没有“先做什么后做什么”的规划能力。AI Agent(智能体)要解决的,正是这个“从说到做”的鸿沟。简单讲,Agent就是给大模型装上手脚、记忆和一套做事的方法论,让它能感知环境、拆解目标、调用工具、根据反馈调整,最终把一个模糊的需求落地成具体的结果。而“Agent架构”这个词,说的就是这套“手脚+记忆+方法论”是怎么搭起来的——模块怎么划分、数据怎么流转、决策在哪一步做、出错怎么兜底。我写这篇东西,不是要给你一份学术综述。市面上讲Agent概念的文章已经够多了,但真正把架构拆开、告诉你“为什么这么设计”“哪一层最容易出问题”“自己动手该从哪下手”的内容并不多。这篇内容适合三类人:一是想从“调API写Demo”进阶到“做能落地的Agent系统”的开发者;二是产品/项目负责人,需要判断一个Agent方案靠不靠谱;三是纯粹好奇“这东西到底怎么运转”的技术爱好者。我会尽量用大白话把架构讲透,该上表格上表格,该给伪代码给伪代码,读完你至少能自己画出一张Agent的架构图,并且知道每个模块的坑在哪。2. Agent架构的四根支柱:拆开看每个模块到底在干什么在动手之前,得先把架构的骨架认清楚。我见过不少人一上来就堆框架、调库,结果出了问题完全不知道是哪一层的事。其实不管用什么框架,一个完整的Agent架构基本都逃不出四个核心模块:感知(Perception)、规划(Planning)、记忆(Memory)、执行(Action)。这四个词听着像教科书,但我用实际项目里的理解给你翻译一遍。2.1 感知层:Agent的“眼睛和耳朵”不只是读用户输入很多人以为感知层就是“接收用户消息”,这理解太窄了。感知层的本质是把外部世界的各种信号转成Agent能处理的内部表示。用户输入只是其中一种,还包括:系统返回的工具调用结果、环境状态的变化(比如某个文件被修改了)、甚至时间流逝这种隐式信号。举个例子,你让Agent“每天早上帮我汇总行业新闻”。这里的感知不只是“收到这条指令”,还包括它得知道“现在几点了”“今天是不是工作日”“上次汇总是什么时候”。如果感知层只处理文本输入,这个任务根本没法触发。感知层还有一个容易被忽略的职责:输入清洗和意图归一化。用户说的话往往是模糊的、带情绪的、有歧义的。比如“这破订单怎么还没到”,感知层要能提取出核心意图是“查询订单物流状态”,而不是被“破”这个情绪词带偏。我一般会在这一层做一个轻量的意图分类,把输入映射到预定义的任务类型上,后面规划层就好处理得多。提示:感知层不要做太重的逻辑判断。它的定位是“翻译官”,把外部信号翻译成内部格式,判断和决策留给规划层。职责混在一起,后期调试会非常痛苦。2.2 规划层:决定“先干什么后干什么”的大脑规划层是Agent架构里最核心、也最难做好的部分。它要回答的问题是:给定一个目标,我应该拆成哪几步?每步用什么工具?如果某步失败了怎么办?最朴素的规划就是“让大模型直接输出一个步骤列表”,也就是所谓的ReAct模式(Reasoning + Acting)。模型先想一步(Reason),再做一个动作(Act),看结果,再想下一步。这种方式灵活,但有个致命问题:它没有全局视野。模型可能走到第三步才发现第一步的方向就错了,然后开始来回打转。所以成熟一点的架构会引入分层规划。上层做一个粗粒度的任务分解,比如“查订单→判断是否符合退货→执行退款→发通知”,下层针对每个子任务再做细粒度的工具选择和参数填充。这就像公司里老板定战略、主管定战术、员工做执行,层次分明,每层只关心自己范围内的决策。我实测下来,分层规划最大的好处是可调试。当Agent行为异常时,你能快速定位是“战略错了”还是“战术错了”。如果只有一层,你面对的就是一坨黑盒,只能靠反复改提示词碰运气。规划方式优点缺点适用场景单层ReAct实现简单,灵活容易跑偏,无全局观简单单步任务分层规划可调试,稳定实现复杂,需要设计层级多步骤复杂任务预定义工作流最稳定可控不灵活,无法处理意外流程固定的业务2.3 记忆层:别把“上下文窗口”当成记忆这是新手最容易踩的坑。很多人觉得“大模型不是有上下文窗口吗,把历史对话都塞进去不就是记忆了?”短期看行得通,但一旦任务变长、交互变多,上下文窗口就会被撑爆,而且模型对超长上下文的注意力会衰减——中间的信息它经常“看不见”。真正的记忆层要解决三个问题:存什么、怎么存、怎么取。存什么?不是所有对话都值得记。我一般把记忆分成三类:事实记忆(用户是谁、偏好什么)、经验记忆(上次类似任务是怎么解决的)、工作记忆(当前任务的中间状态)。事实和经验是长期记忆,工作记忆是短期的,任务结束就可以清掉。怎么存?简单的事实可以用键值对,复杂的经验得用向量数据库做语义检索。这里有个经验:不要什么都往向量库里塞。向量检索适合“模糊匹配相似经验”,但如果你要精确查“用户ID是12345的收货地址”,用结构化存储快得多也准得多。我通常会用混合方案:结构化数据走数据库,非结构化经验走向量库。怎么取?这是最考验设计的地方。取多了,上下文被无关信息污染;取少了,Agent又缺信息。我的做法是按需检索:规划层在每一步决策前,主动告诉记忆层“我现在需要什么信息”,记忆层再去取,而不是一股脑全塞进去。2.4 执行层:工具调用不是“调个API”那么简单执行层负责把规划层的决策变成实际动作——调用API、读写文件、发消息等等。听起来简单,但坑特别多。第一个坑是工具描述的质量。大模型靠工具的自然语言描述来决定用哪个工具、传什么参数。如果你的工具描述写得含糊,模型就会乱用。我见过一个案例,两个工具一个叫“查询订单”,一个叫“查询物流”,描述都写得很简略,结果模型经常搞混。后来把描述改成“查询订单:根据订单号返回订单的商品、金额、状态;查询物流:根据订单号返回包裹的当前位置和预计送达时间”,混淆率立刻降下来了。第二个坑是参数校验。模型生成的参数不一定合法,比如日期格式不对、ID不存在。执行层必须做校验,不合法就返回明确的错误信息给规划层,让它重新决策。这里的关键是错误信息要具体,不能只说“参数错误”,要说“订单号ABC123不存在,请确认后重试”,这样模型才知道怎么改。第三个坑是幂等性。Agent可能会因为重试机制重复调用同一个工具。如果这个工具是“扣款”,重复调用就是灾难。所以执行层对写操作必须做幂等设计,比如用唯一请求ID去重。3. 一次完整的Agent运转:把架构跑起来是什么样光讲模块还是抽象,我拿一个具体任务把整个流程串一遍。任务是这样的:“帮我看看最近三天有没有未发货的订单,有的话催一下。”3.1 从用户一句话到任务分解的完整链路第一步,感知层接收输入。它识别出这是一个“订单查询+催发货”的复合任务,提取关键约束“最近三天”“未发货”。同时,感知层从记忆层拿到当前用户的身份信息(比如用户ID),因为“我的订单”需要知道“我”是谁。第二步,规划层做任务分解。上层规划把任务拆成:①查询用户最近三天的订单;②筛选出状态为“未发货”的订单;③对每个未发货订单执行催发货操作。下层规划针对第一步,决定调用“查询订单”工具,参数是用户ID和时间范围。第三步,执行层调用工具。它调用订单查询接口,拿到一批订单数据。假设返回了5个订单,其中2个未发货。第四步,结果回到规划层。规划层看到有2个未发货订单,决定对每个执行催发货。这里它需要调用“催发货”工具,参数是订单号。第五步,执行层再次调用工具。催发货接口返回成功。规划层确认所有子任务完成,生成最终回复:“已为您催促2个未发货订单,订单号分别是XXX和YYY。”第六步,记忆层更新。把这次操作记录到经验记忆里,下次遇到类似任务可以更快处理。整个链路里,规划层和执行层之间是一个循环:规划→执行→观察结果→再规划。这个循环什么时候停?要么任务完成,要么达到最大步数限制,要么遇到无法恢复的错误。我一般会设一个步数上限(比如15步),防止Agent陷入死循环烧钱。3.2 循环、分支与终止条件:Agent什么时候该停Agent的运转本质上是一个带终止条件的循环。但“什么时候停”这件事,比想象中微妙。最理想的情况是任务完成,Agent自己知道“搞定了”。但模型经常过度自信,明明没查完就说“已完成”。所以我会在规划层加一个完成度检查:每个子任务完成后打勾,所有子任务都打勾才允许终止。这比让模型自己判断靠谱得多。第二种终止是达到步数上限。这是兜底机制,防止无限循环。但步数设多少有讲究:设太小,复杂任务做不完;设太大,出问题时浪费资源。我的经验是,根据任务平均步数的2-3倍来设,比如平均5步完成的任务,上限设15步。第三种是遇到不可恢复错误。比如工具连续失败、权限不足、关键信息缺失。这时候Agent应该优雅地退出,并告诉用户“我卡在哪了、需要什么帮助”,而不是硬着头皮瞎试。还有一种容易被忽略的终止:任务目标本身发生变化。比如用户中途说“算了不用催了”,Agent得能感知到并中止当前流程。这要求感知层在循环中持续监听新输入,而不是只在开头听一次。3.3 工具调用的参数是怎么“填”进去的很多人好奇:模型怎么知道该传什么参数?答案是靠提示词里的工具定义+上下文信息。工具定义一般长这样(伪代码):{ "name": "query_orders", "description": "根据用户ID和时间范围查询订单", "parameters": { "user_id": {"type": "string", "description": "用户唯一标识"}, "start_date": {"type": "string", "description": "开始日期,格式YYYY-MM-DD"}, "end_date": {"type": "string", "description": "结束日期,格式YYYY-MM-DD"} } }模型看到这个定义,再结合上下文里“用户ID是12345”“最近三天”这些信息,就能生成调用参数。但这里有个细节:“最近三天”这种相对时间,模型不一定能准确转成具体日期。所以我会在感知层或规划层做一个时间归一化,把“最近三天”转成“2024-06-01到2024-06-03”再传给模型。这比让模型自己算靠谱得多。另一个细节是可选参数的处理。如果某个参数不是必填的,模型可能不传,也可能传个空值。执行层要能区分“没传”和“传了空”,前者用默认值,后者可能报错。4. 架构落地时最容易翻车的几个地方架构图看着清晰,真动手做的时候,坑一个接一个。我挑几个最有代表性的说说。4.1 规划层“想太多”和“想太少”的平衡规划层最常见的毛病是两个极端:过度规划和规划不足。过度规划的表现是:一个简单任务被拆成十几步,每步都要调工具、查记忆,结果又慢又贵,还容易在某个无关紧要的步骤上卡住。我见过一个Agent,用户就问“今天天气怎么样”,它先查用户位置、再查天气API、再格式化输出、再检查格式、再……其实一步就能搞定。规划不足的表现是:该拆的不拆,指望模型一步到位。比如“帮我订一张明天去某地的票”,模型直接调订票接口,但没先查有没有余票、没确认用户偏好,结果订错了。怎么平衡?我的经验是按任务复杂度动态调整。简单任务(单工具、单步骤)直接执行,不规划;中等任务(多工具、有依赖)做一层规划;复杂任务(多阶段、有条件分支)才上分层规划。判断复杂度可以靠感知层的意图分类,也可以让模型自己评估“这个任务需要几步”。4.2 记忆检索的“相关性陷阱”记忆检索最怕的是检索到看似相关实则误导的信息。比如用户上次退货是因为“尺码不对”,这次咨询的是“颜色不喜欢”,如果记忆层把上次的退货原因直接推给规划层,Agent可能就会按“尺码问题”的流程去处理,跑偏了。避免这个陷阱的关键是检索时带上任务上下文。不要只拿用户输入去检索,要把当前任务类型、当前步骤也作为检索条件。比如当前是“退货原因判断”步骤,检索时就优先找“退货原因”相关的记忆,而不是泛泛的“退货”记忆。还有一个技巧是给记忆加时效权重。越近的记忆权重越高,太老的记忆除非特别相关,否则不推。这符合直觉:用户上个月的偏好,可能这个月就变了。4.3 工具返回结果“太长”把上下文撑爆工具返回的数据经常很长,比如查询订单返回一个包含几十个字段的JSON。如果原样塞回上下文,几轮下来上下文就满了,而且模型会被无关字段干扰。我的处理方式是在执行层做结果裁剪。工具返回后,先提取规划层真正需要的字段,把其余的都丢掉。比如订单查询只需要“订单号、状态、金额”,那就只保留这三个字段。这需要在工具定义里就声明“返回哪些字段有用”,或者让规划层在调用时指定“我只要状态字段”。如果结果确实很长又没法裁剪(比如返回一篇长文档),那就得做摘要压缩。让一个小模型或者规则引擎先把长文本压成几句话,再塞回上下文。这一步会增加延迟,但能显著提升稳定性。4.4 错误处理:Agent卡住时到底该重试还是放弃Agent执行过程中出错是常态。关键是要区分可重试错误和不可重试错误。可重试错误包括:网络超时、临时限流、参数格式小错。这类错误重试一两次往往就好了。但重试要有退避策略,不能立刻重试,否则可能加剧限流。我一般用指数退避:第一次等1秒,第二次等2秒,第三次等4秒。不可重试错误包括:权限不足、资源不存在、逻辑矛盾。这类错误重试多少次都没用,应该直接上报给规划层,让它换方案或者终止。还有一种模糊错误,比如“操作失败”但没说原因。这种最麻烦。我的做法是让执行层尽量把原始错误信息带回来,哪怕是一串错误码,也比“失败”两个字强。规划层看到具体错误码,才有可能做出正确判断。错误类型例子处理策略可重试超时、限流指数退避重试2-3次不可重试权限不足、资源不存在上报规划层,换方案或终止模糊错误无具体信息的失败尽量获取原始错误,再判断5. 从零搭一个最小可用Agent:我的实操路径理论讲完了,说说怎么动手。我不建议一上来就用重型框架,那样你学到的只是“怎么用框架”,而不是“Agent怎么运转”。我的路径是先用最原始的方式跑通一个最小闭环,再逐步加模块。5.1 先跑通“感知-规划-执行”最小闭环第一步,写一个最简单的循环:接收用户输入→调用大模型→如果模型说要调工具就调→把结果喂回模型→重复直到模型说完成。这个阶段不需要记忆层,不需要分层规划,甚至不需要复杂的工具定义。就一个工具,比如“查天气”,把整个链路跑通。目的是理解循环是怎么转的、消息是怎么在模型和工具之间传递的。伪代码大概长这样:messages = [{"role": "user", "content": user_input}] for step in range(MAX_STEPS): response = llm.chat(messages, tools=tool_definitions) if response.has_tool_call: result = execute_tool(response.tool_call) messages.append(response.tool_call) messages.append({"role": "tool", "content": result}) else: return response.content跑通这个,你就理解了Agent的“心跳”。5.2 给Agent加上第一层记忆最小闭环跑通后,加记忆。先加最简单的工作记忆:把每一步的工具调用结果存下来,下一步规划时带上。这能解决“模型忘了上一步做了什么”的问题。然后加事实记忆:用一个简单的键值存储,存用户的基本信息。每次规划前,把相关事实取出来塞进上下文。这个阶段你会遇到第一个真正的挑战:上下文越来越长。这时候你就自然理解了为什么需要结果裁剪和摘要压缩。5.3 把单层规划升级成分层规划当任务变复杂,单层规划开始力不从心时,引入分层。上层规划只输出“子任务列表”,下层规划针对每个子任务做具体决策。实现上,可以用两个不同的提示词模板:一个用于任务分解,一个用于具体执行。任务分解的提示词强调“只输出步骤,不要执行”;执行提示词强调“根据当前步骤和上下文,决定调什么工具”。这个升级不是必须的,但如果你要做多步骤复杂任务,早晚得走这一步。5.4 用真实任务压测:我踩过的三个坑搭好之后,别急着上线,用真实任务压测。我踩过的坑:坑一:模型在工具调用失败后反复重试同一个错误参数。原因是错误信息不够具体,模型不知道错在哪。改成具体错误信息后解决。坑二:Agent在某个步骤卡住后,把之前成功的步骤也重做了。原因是工作记忆没记录“哪些步骤已完成”。加上完成度标记后解决。坑三:多个工具功能重叠,模型选错工具。原因是工具描述没写清楚边界。把每个工具的“适用场景”和“不适用场景”都写进描述后解决。6. 关于Agent架构,我踩过坑之后的一些个人体会做Agent架构这件事,最深的体会是:它更像搭积木而不是写代码。代码写错了编译器会报错,但Agent架构设计错了,它不会报错,只会“表现得很奇怪”——答非所问、绕圈子、该停不停。你得靠观察和日志去反推是哪一层出了问题。另一个体会是不要追求一步到位的完美架构。我见过有人一上来就设计五层记忆、三层规划、动态工具路由,结果调试成本高到根本跑不起来。正确的做法是先用最小闭环跑通,遇到问题再加模块,每个模块的引入都要有明确的“它解决了什么之前解决不了的问题”。还有一点:Agent的稳定性很大程度上取决于工具的质量。工具描述清晰、参数校验严格、错误信息具体,Agent的表现就会好很多。与其花时间调提示词,不如先把工具层做扎实。最后分享一个我常用的调试技巧:把Agent的每一步决策都打日志,包括它看到了什么上下文、做了什么决策、调了什么工具、拿到了什么结果。出问题时,把日志从头到尾看一遍,90%的问题都能定位。这比盯着最终输出瞎猜高效得多。