ARTICLE DETAIL

资讯详情

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

015、Agent的核心模块:规划、记忆、工具

015、Agent的核心模块:规划、记忆、工具 015、Agent的核心模块规划、记忆、工具那次是半夜改一个多步检索Agent日志里刷出来一串诡异行为它反复用同一个工具去查同一个用户ID查了三次第三次结果和第一次一模一样然后它才继续走下一步。中间没有任何记忆痕迹也没有重新规划意图纯粹是“失忆”了。更让我火大的是它在查之前明明已经推理出了答案方向却因为缺了“记住自己查过什么”的能力活活浪费了小半分钟接口配额。当时我盯着日志想这玩意儿哪配叫Agent就是个带轮子的if-else。调完那个bug我把Agent拆了三块发现每个模块都在不同程度上恶心过我。规划Planning负责决定下一步做什么记忆Memory负责记着做过了什么工具Tools负责真正去执行动作。三个模块互相依赖但很多人写Agent时把三坨代码揉在一起最后调起来像解一团被猫玩过的毛线。下面是我踩坑后的理解只讲实战不贴教科书定义。规划的坑别让大模型每次都从零想一开始我写规划模块就是无脑把用户问题丢给大模型让它输出“下一步动作”。结果就是它经常抽风明明上一步已经获取了所有必要信息规划器却坚持“还需要再查一次天气”因为它的上下文里压根没记录上一步的结论。后来我意识到规划不是“凭空想下一步”而是“基于当前状态做决策”。状态从哪来从记忆模块来。规划里最容易踩的坑是“计划过深”。我试过让模型一次生成完整的多步计划比如“先搜索A再根据A搜索B然后对比A和B最后总结”。看起来很美但现实是搜索A的结果可能完全出乎意料原计划全部作废。后来我改成“只规划下一步”或者最多规划两步像下棋一样看一步走一步。每一步执行完把实际结果塞回记忆再让规划器基于新状态决定下一步。这比硬憋一份五步计划可靠得多。大模型不是神它预测不了搜索结果的变数。还有一个细节规划器一定要能“放弃”。我遇到过一个案例Agent在规划中反复决策“再搜索一次”因为前几次搜索结果都不理想它试图通过重复搜索来“碰运气”。这本质是规划器没有成本概念也没意识到“重试不会改变结果”。我在规划模块里加了一个简单的“同动作check”——如果当前要执行的工具和参数与上一步完全一样强行打断并让模型换思路。就这么一行逻辑救了不少次调试点。记忆的坑别把对话历史当记忆很多人的Agent“记忆”就是拿着所有历史消息一股脑塞进上下文把大模型当无限长窗口。效果嘛token消耗爆炸而且早期关键信息被淹没在后面的碎碎念里。真正的记忆应该分两层短期工作记忆和长期事实记忆。短期工作记忆管“这一步执行完了没”“当前任务的目标字段是什么”长期记忆管“这个用户的偏好是什么”“上次任务得出了什么结论”。我实现记忆模块时用了一个非常土的数据结构——全局字典加操作日志。全局字典存“当前状态”比如current_query、fetched_data、final_answer。操作日志存“每一步干了什么”包括工具名、参数、返回摘要。规划器每次只看字典和一份压缩过的日志摘要而不是原始历史。这就避免了“失忆”问题也减少了token浪费。一个关键技巧记忆里的每一条数据都要带“新鲜度”。比如我让Agent记住某次搜索结果但缓存结果可能过了两分钟就变了。所以记忆模块里存数据时顺手记个时间戳规划器在读取记忆时判断是否过期。有个血泪教训我一开始没加时间戳Agent拿着昨天的股价数据回答今天的行情被测试组当成严重bug打回。后来所有记忆条目统一加ts字段读的时候带个“只看最近N分钟”的过滤器。工具的坑别让Agent瞎试参数工具模块看起来最简单不就是给Agent几个函数调用吗但实际用起来最容易出问题的是“参数幻觉”。大模型经常会凭空生成一个不存在的参数值比如调用查询函数时给一个没见过的ID或者把start_date和end_date顺序搞反。我后来在工具定义里写死了参数JSON schema并在调用前做一个强校验——不合法就直接抛错不让工具内部去容错。容错等于纵容Agent会越试越离谱。还有一个我反复强调的点工具描述要写得像“给新员工的说明书”而不是API文档。别写“get_user_info(user_id)”要写“根据用户ID获取基本信息用户ID通常在对话中由用户提供如果找不到ID请先向用户询问”。很多规划出错是因为模型根本不知道这个工具在什么场景下用你描述得越上下文化它选得越准。另外工具执行后返回的结果要“精简”。有一次Agent调用搜索工具返回了30条结果每条几百字直接把后续上下文撑爆规划器看不清楚开始胡说八道。后来我在工具返回时做了截断和摘要——只保留标题、链接、相关片段、时间戳其他全扔。这招立竿见影Agent的下一步决策质量瞬间提升。三者怎么协作我的调试现场回到开头那个“反复查同一个用户ID”的bug。根因就是规划模块没有检查短期记忆里已经有过这个工具调用记录。修复方案很简单在规划器执行动作前先让记忆模块做一次“查重”——如果当前计划动作的参数与某条最近日志完全匹配且时间间隔小于阈值就提示规划器“你已经查过了结果是XXX请基于该结果继续”。这样Agent就不会无脑重复。同时工具模块里每次调用都强制写入日志并带时间戳记忆模块才能查得到。另一个协作场景多轮任务中用户中途改了需求。比如一开始让Agent“查找北京的天气”Agent查完了用户说“那上海呢” 如果记忆模块正确处理规划器看到新意图后应该知道“北京已查过不要重复”直接查上海即可。但如果记忆只存对话原始消息没有提取“任务状态”Agent很可能把北京又查一遍才能反应过来。所以记忆模块不光存数据还要存“当前任务进度”比如location_checked: [北京]。规划器一旦发现目标地点已经查过就跳过搜索直接走总结流程。工具模块在这里也有责任。它的每次调用都应该返回结构化状态比如{status: success, data: {...}, context: weather_query}这个状态直接更新到记忆里。如果工具返回值长的是纯文本规划器和记忆模块都没法高效地做逻辑判断。所以我在工具实现里强制要求返回JSON哪怕内部是拼字符串输出也必须是字典。个人经验给正在写Agent的同行几条私货第一别迷信“一步规划到位”的酷炫手法把规划器做成“小步快跑”模式哪怕慢一点至少可控。第二记忆模块一定要独立出来别跟主流程耦合。哪怕用最简单的全局变量也请单独放在一个类里别散落在各处。第三工具的注册表要维护好每加一个工具先写清楚“使用场景”和“参数限制”别急着上线先自己模拟几个典型对话调调。第四所有模块的日志都要带时间戳和调用链ID。调试Agent不用分布式追踪但至少让每轮调用的规划、记忆、工具日志能串起来。我之前就是靠日志里那个反复出现的用户ID才定位到记忆缺失的。写这篇笔记时我又想起来那个“反复查ID”的深夜。其实当时最简单的替代方案是——直接给Agent加一个“不许重复查询”的硬编码规则一劳永逸。但那样做Agent就失去了对“什么该重复、什么不该重复”的理解力。后来的版本里我会让规划器自己判断“重复是否有意义”比如用户明确要求“再查一次”时重复就是合理的。所以规则别写死而是把“已有记忆”作为条件告诉规划器让它自己决定。这才是Agent不是if-else。如果你也在调这类问题建议你打开日志找一个反复执行的工具调用追一下代码路径。你会发现八成是规划器没看记忆记忆没写状态工具没留痕。三兄弟任何一个偷懒Agent都会变成复读机。把这三个模块的边界划清楚你的Agent才算真正开始“记事”和“长心眼”。
返回列表