一、Agent 的记忆,其实是三块不同的东西
给 Agent 加"记忆",很多人以为就是存对话历史。实际要分三块:工作记忆、情景记忆、语义记忆。
不清这三块,就会出现"记了很多但都没用"的情况。
做 Agent 做得久了会发现,效果差往往不是模型差,是记忆乱。
把三块东西混在一个向量库里,检索出来的结果永远似是而非。
分开存、分开检,效果立刻不一样。这是我改架构之后最明显的一次提升。
二、工作记忆:当前这一步需要什么
工作记忆就是上下文窗口里的内容,只服务当前任务。
它的问题不是容量,是噪音。把整段历史都塞进去,模型会被无关内容干扰,反而更容易跑偏。
做法是每一步只保留:目标、当前状态、上一步结果、约束条件。四样就够。
工作记忆的容量是有限的,这一点要认清。
它不是越大越好。塞太多,关键信息会被稀释;塞太少,模型会反复问你同样的问题。
我的经验是控制在一次任务所需的量,具体一点:当前步骤、相关的三条事实、以及一个约束。
三、情景记忆:做过什么,结果如何
情景记忆是"我上次遇到类似的事,怎么处理,结果怎样"。
它最适合存成结构化记录,而不是原文对话。
一条记录写四个字段:任务类型、采取的动作、结果、失败原因。后面检索靠这四个字段,不靠向量相似度硬怼。
情景记忆最容易写坏,因为它需要「结果」这一列。
很多人只记「做过什么」,不记「结果如何」。这样的记忆只能复述,不能学习。
我后来加了两列:成功与否、失败原因。检索的时候先按相似任务,再按成功过滤。
这一步让重复犯错的概率降了一大截。代价是要多写一个字段,值。
还有一点:情景记忆要带时间戳。没有时间的记忆,会被当成永久事实。
四、语义记忆:稳定的事实和偏好
语义记忆是长期有效的知识,比如用户偏好、系统规则、常用参数。
这块最需要"写入审慎"。Agent 自作主张把推断当事实写进去,后面每一步都会被污染。
一条实用规则:只有用户明确说过的,才写语义记忆;模型推断的,只进情景记忆。
语义记忆最容易腐烂,因为它记的是「事实」,而事实会变。
所以语义记忆要带置信度和来源。没有来源的事实,三个月后你不敢用。
我的做法是每条事实配一句「据 X 于 Y 时所述」,更新时整条替换,不做合并。
五、检索比存储更关键
存了一堆记忆,检索不出来等于没有。
三个调法:
一、先按类型筛,再算相似度。类型错了,相似度再高也没用。
二、给记忆加时间戳,同一条事实冲突时取新的。
三、限制每次注入条数。三条以内通常够用,多了就是噪音。
检索比存储更关键,这句话我想多说两句。
存储是空间问题,检索是精度问题。空间可以花钱买,精度只能靠设计。
三个设计点:一是分块大小,太大检索粗,太小丢上下文;二是混合检索,关键词加向量;三是重排,先粗排再精选。
三个都做了,效果通常能上一个台阶。只做向量,往往在专有名词上翻车。
专有名词翻车这件事,几乎每个垂直领域都会遇到。补关键词检索,是最省事的解法。
六、成本上的现实
记忆不是免费的。每次注入都要占 token,长会话里这块开销会很明显。
我的做法是分层:工作记忆每步都带,情景记忆按需检索,语义记忆只在开头注入一次。
成本这块说点实话:记忆是最容易失控的部分。
每轮都写、从不清理,三个月后一条任务要检二十万条记录。
我的做法是三层清理:会话级过期即删,任务级保留三十天,事实级长期保留但要人工确认。
三层之外还有一个兜底:定期抽样重检,看召回是否变差。变差就说明该重排了。
这套东西不复杂,但要坚持。不做清理的 Agent,最后都会变慢、变贵、变糊涂。
补一句:记忆不要做成黑箱。
你要能看见它检索了什么、为什么选这条。看不见,出了问题只能猜。
我现在都会留一个日志,记下每次注入的内容。这个日志救过我很多次。
成本上的现实:记忆检索的延迟,往往比生成还高。
一次检索要跑向量搜索加重排,几十毫秒到几百毫秒。任务步骤一多,这部分就成了瓶颈。
所以缓存很重要。同一任务内,检索结果可以复用,不必每步都查。
延伸参考
概念对照可以去前端 Wiki 翻词条,解释都不绕。
留一句:Agent 的记忆做得好不好,看它会不会忘掉该忘的。