上个月我们团队内部的一个客服 Agent 出了个很离谱的事。
有个用户连续三天找同一个 Agent 咨询退款流程,第一天告诉他要上传凭证,第二天用户带着凭证来了,Agent 完全不认识这个人,又从头来了一遍。第三天用户直接骂了,说你们这个机器人是不是故意的。说实话,看到这个 case 的时候我也挺无奈的,技术上确实没做错什么,但体验上就是灾难。
问题的根源很简单,这个 Agent 没有记忆。每轮对话结束,上下文就清空了,下次再来,跟第一次见面没区别。
你可能觉得这不是常识吗,加个数据库存一下不就行了。还真没那么简单。今天就来聊聊 Agent 记忆这件事,到底该怎么设计。
上下文窗口不等于记忆
很多人把上下文窗口(context window)等同于记忆,这是一个很常见的误解。
上下文窗口更像是工作台。你把当前需要的东西摊在桌上,干完活就收走了。而记忆是你脑子里真正留下来的东西,明天、下周、下个月还能调用的那种。
举个类比。上下文窗口相当于你在白板上写东西,空间有限,写满了就得擦掉一部分。记忆则是你的笔记本,可以积累,可以翻阅,可以建立索引。
现在主流的大模型上下文窗口已经很大了,动辄 128K、200K tokens。你想想看,这么大还不够用吗?
说真的,大部分场景下确实够用。但问题在于几个维度。
第一,成本。上下文越长,推理成本越高,token 费用是实打实的钱。一个 Agent 每次对话都带 100K tokens 的历史记录,一天几千次调用,月底账单会让你心跳加速。
第二,噪声。上下文里塞太多东西,模型反而会「迷路」,注意力分散,回答质量下降。这个现象学术界叫 lost in the middle,有兴趣可以搜搜看,挺多论文的。
第三,持久性。上下文窗口是一次性的,对话结束就没了。你不可能让一个 Agent 记住用户三个月前的偏好,除非你有专门的记忆机制。
所以我们需要一个分层的记忆架构,把不同类型的信息放在不同的层里管理。
三层记忆架构
我比较认同的一种设计思路是把 Agent 的记忆分成三层,对应人类认知科学里的分类。
| 记忆层 | 类比 | 存储介质 | 生命周期 | 典型内容 |
|---|---|---|---|---|
| 工作记忆 | 白板 | 上下文窗口 | 单次对话 | 当前对话内容、工具调用结果 |
| 情景记忆 | 日记本 | 会话存储 | 跨会话保留 | 历史对话摘要、用户行为记录 |
| 语义记忆 | 百科全书 | 向量数据库 | 长期持久 | 领域知识、FAQ、业务规则 |
这三层之间不是孤立的,它们之间有信息流动。工作记忆里的重要内容会被压缩归档到情景记忆,情景记忆中反复出现的模式会沉淀到语义记忆。反过来,语义记忆和情景记忆也会被检索出来注入工作记忆,辅助当前对话。
其实吧,这个分层跟人类大脑的工作方式还挺像的。你在工作时专注处理眼前的任务(工作记忆),偶尔回忆一下昨天开会说了什么(情景记忆),需要用到专业知识时会去翻资料(语义记忆)。
工作记忆,管好你的白板
工作记忆是最基础的一层,就是你当前对话的上下文。
看起来最简单,实际上坑不少。最核心的问题是,怎么在有限的窗口里放最有价值的信息。
一个常见的做法是给上下文分区。
|
|
这几个区域的 token 预算是动态分配的。对话轮次多了,conversation 区域会膨胀,这时候就需要压缩策略。有的团队会设置硬阈值,超过 N 轮就开始做滚动截断,保留最近 K 轮加上一个压缩摘要。
还有一种更精细的做法,对每轮对话做重要性评分。用户说「谢谢」这种低信息量的消息可以早点丢掉,用户说「我对花生过敏」这种关键信息必须保留。评分可以用一个轻量模型来做,成本不高。
情景记忆,给 Agent 一本日记
情景记忆解决的是「跨会话记忆」的问题。
用户昨天问了什么,上周做了什么决定,上个月投诉过什么,这些信息在当前对话的上下文里是没有的。你需要一个地方把它们存起来,并且在需要的时候能快速检索。
实现上,情景记忆通常由两部分组成,一是结构化的会话元数据(时间、用户 ID、主题标签),二是非结构化的对话摘要。
摘要生成的时机很关键。有的系统在每轮对话结束后实时生成摘要,好处是信息不会丢失,坏处是调用频繁,成本高。有的系统在会话结束或者超时后才做一次性总结,成本低但可能漏掉中间的重要细节。
我个人比较推荐的做法是混合策略,每 N 轮做一次增量摘要,会话结束时再做一次全量总结。增量摘要保证不丢关键信息,全量总结提供一个高质量的最终版本。
摘要的质量直接影响情景记忆的效果。一个好的摘要应该包含这些要素,
- 用户的核心诉求和意图
- 关键的决策点和结论
- 未解决的问题和待办事项
- 用户表达的偏好和约束
别小看这一条。用户说过「我不喜欢太长的回复」或者「请用英文回答」,这些偏好信息如果能在下次对话中被自动加载,体验会好非常多。
语义记忆,Agent 的知识底座
语义记忆是 Agent 的「长期知识库」,存放的是相对稳定的领域知识。
这一层最常见的实现方式就是 RAG(Retrieval-Augmented Generation)。你想想看,把公司的产品文档、FAQ、操作手册、历史案例全部灌进向量数据库,Agent 在回答问题时先去检索相关内容,再结合检索结果生成回答。
RAG 这个词这两年火得有点过头了,什么场景都往上套。但说实话,在语义记忆这个场景下,RAG 确实是最务实的方案。
RAG 的工程实现可以分为几个阶段。
文档预处理,这一步决定了知识的质量上限。PDF 解析、表格提取、代码块识别、多级标题切分,每一个都是脏活累活。很多团队在这一步做得很粗糙,后面检索效果差就觉得是向量数据库的问题,其实根子在预处理。
分块策略(Chunking),把长文档切成合适大小的片段。chunk 太大,检索精度低,一个 chunk 里混了太多主题,向量表示会被稀释。chunk 太小,上下文不完整,检索到了片段但缺少前后文,模型也不好理解。经验值通常在 300 到 800 tokens 之间,具体要看文档类型。
Embedding 模型选择,这一步把文本转成向量。模型的选择直接影响检索质量。目前主流的选择包括各厂商的 embedding API 以及一些开源模型。中文场景下需要注意模型对中文的支持程度,有些在英文语料上训练的模型,中文检索效果会打折扣。
检索与排序,先从向量数据库里召回 top-K 个相关片段,再用一个 reranker 模型做精排。这一步是召回率和精度的平衡点,后面会详细聊。
向量数据库选型
说到语义记忆,就绕不开向量数据库。市面上选择太多了,到底该用哪个?
我整理了一个简单的对比,基于实际项目中的使用体验。
| 特性 | Milvus | Pinecone | Weaviate | Qdrant | Chroma |
|---|---|---|---|---|---|
| 部署方式 | 自建/云 | 全托管 | 自建/云 | 自建/云 | 嵌入式/自建 |
| 性能(百万级) | 很好 | 好 | 好 | 很好 | 一般 |
| 混合检索 | 支持 | 部分支持 | 支持 | 支持 | 基础支持 |
| 中文生态 | 好 | 一般 | 一般 | 一般 | 一般 |
| 运维复杂度 | 高 | 低 | 中 | 中 | 低 |
| 适合场景 | 大规模生产 | 快速上线 | 知识图谱结合 | 高性能场景 | 原型验证 |
怎么说呢,选向量数据库这件事,没有最好的,只有最合适的。
如果你是初创团队,想快速验证 RAG 方案可不可行,Chroma 或者 Pinecone 就挺好,几分钟就能跑起来。等你验证完了要上生产环境,再考虑迁移到 Milvus 或者 Qdrant 这种能扛大流量的方案。
有一点容易被忽略,混合检索(hybrid search)。纯向量检索有时候会漏掉精确匹配的内容。比如用户搜「A32-B7 故障码」,这种精确的编号用关键词搜索比向量相似度靠谱得多。好的做法是向量检索和关键词检索(BM25)都做,然后把结果融合排序。Milvus 和 Weaviate 都原生支持这个能力。
会话压缩,用更少的 token 记住更多的事
会话压缩是情景记忆里的核心工程问题。
当对话进行到第 50 轮的时候,上下文可能已经有 30K tokens 了。你不能全塞进去,得压缩。怎么压缩?
最朴素的方法是滑动窗口,只保留最近 N 轮对话。简单粗暴,但会丢失早期的关键信息。用户第一轮说的「我要订一张下周三去上海的机票」,到第 30 轮可能已经被滑出窗口了。
更好的做法是分层压缩。
|
|
这个方案的关键在于摘要提示词(prompt)的设计。你需要告诉模型,什么是「重要信息」,什么可以丢掉。不同的业务场景,「重要」的定义完全不同。
客服场景下,用户的问题、情绪变化、承诺的解决方案是重要的。编程助手场景下,代码修改决策、架构讨论结论、bug 修复思路是重要的。你得根据场景定制压缩策略。
还有一种思路是用结构化记忆替代自然语言摘要。与其让模型总结一段自然语言描述,不如让它输出结构化的 JSON,比如用户画像更新、任务状态变更、关键实体提取。结构化的数据更容易做精确检索,也更容易做多轮累积更新。
记忆检索的召回率与精度
记忆存进去了,还得能取出来。检索这一步,直接决定了 Agent 的记忆到底「好不好使」。
这里有一个经典的矛盾,召回率和精度的权衡。
召回率高,意味着尽量多的相关记忆被找出来。精度高,意味着找出来的都是真正相关的。这两个指标往往是跷跷板关系。
在 Agent 记忆检索的场景下,我倾向于优先保证精度。原因很简单,如果检索回来的记忆里有不相关的内容,模型会被干扰,生成的回答可能跑偏。宁可少召回一些,也要确保召回的都是靠谱的。
具体策略上有几个可以调的旋钮。
相似度阈值,设一个底线分数,低于这个分数的结果直接丢弃。这个阈值需要根据你的 embedding 模型和业务数据来调,没有通用值。建议在 0.7 到 0.85 之间做网格搜索。
检索数量(top-K),K 设大了噪声多,设小了可能漏掉关键信息。一般 3 到 5 个比较合适。如果做了 reranker,可以先召回 20 个再精排取 top-5。
多路召回,同一个查询用不同的策略各检索一遍,比如向量相似度一路、关键词匹配一路、时间衰减一路(最近的记忆权重更高),然后把结果做去重合并。
查询改写,用户说的话不一定适合直接拿去做检索。比如用户说「上次那个问题怎么样了」,这个查询拿去检索什么都搜不到。你需要把当前对话的上下文融入查询,改写成「用户上周咨询的退款流程处理进度」。这步可以用一个小模型来做。
多 Agent 共享记忆
如果你的系统里有多个 Agent 协作,记忆共享就变成一个需要认真设计的问题。
举个例子,一个客服系统里可能有三个 Agent,一个负责售前咨询,一个负责售后退款,一个负责投诉处理。用户在三个 Agent 之间跳转的时候,记忆需要跟着走。
一种设计是引入一个共享的记忆层,所有 Agent 都能读写。用户的核心信息(偏好、历史事件、重要决策)存在共享层,各 Agent 自己的对话上下文存在私有层。Agent 在处理请求时,会把共享记忆和自己的私有记忆一起加载。
这个方案的问题是冲突处理。如果售前 Agent 记录了「用户偏好 A 方案」,售后 Agent 记录了「用户已改为 B 方案」,共享记忆里应该保留哪个?
简单的做法是按时间戳,保留最新的。复杂一点的做法是加版本号,每次更新都保留历史版本,必要时可以回溯。
还有一种情况需要警惕,记忆泄露。Agent A 的记忆不应该无差别地暴露给 Agent B。比如投诉处理 Agent 记录的用户情绪信息和抱怨内容,不一定适合让售前 Agent 看到。共享记忆也需要做权限控制和字段级别的隔离。
反正我觉得,多 Agent 记忆共享这件事,架构上不难,难的是数据治理。什么该共享,什么该隔离,什么该过期,什么该永久保留,这些规则需要跟业务方一条一条对齐。
容易踩的几个坑
聊完架构,来说几个实际项目中容易翻车的地方。
记忆污染
用户有时候会开玩笑、说反话、或者给出错误信息。如果 Agent 把这些内容不加甄别地存入长期记忆,后续就会基于错误的记忆做出错误的决策。
解决方案是对存入记忆的信息做置信度评估。用户明确陈述的事实(「我的手机号是 138xxxx」)置信度高,用户随口一说的话(「我觉得你们这个功能挺难用的」)置信度低。低置信度的记忆可以被标记,在检索时降低权重。
记忆膨胀
如果什么都往记忆里塞,不加清理,时间一长,记忆库就会变成一个巨大的垃圾场。检索效率下降,噪声增多,效果反而越来越差。
你需要一套记忆清理机制。比如,设置记忆的 TTL(Time-To-Live),超过一定时间没有被检索命中的记忆自动降权或者归档。比如,定期做记忆合并,同一个用户的多条相似记忆合并成一条更精炼的版本。
隐私合规
用户的记忆数据属于个人敏感信息。你在存这些数据之前,需要确保有合法的授权。在检索和使用的时候,需要确保数据不会被泄露给不该看到的 Agent 或者用户。
GDPR、个人信息保护法这些法规对数据的存储期限、用户删除权都有明确要求。你的记忆系统需要支持「用户要求删除所有记忆」的操作,这在技术上其实不简单,尤其是当记忆已经被嵌入到向量空间里,你可能需要重建索引。
过度依赖记忆
有时候 Agent 检索回来的记忆反而会误导模型。用户半年前说「我住在杭州」,上个月已经搬到上海了,但如果检索只召回了杭州那条记忆,Agent 就会给出错误的本地化建议。
记忆系统需要一个「衰减」机制,时间越久远的记忆,权重应该越低。特别是涉及到事实性信息(地址、偏好、需求),应该优先使用最新的版本。
一个可以跑起来的简化方案
理论聊了这么多,给一个最小可行的实现思路,适合想快速搭建 Agent 记忆系统的团队。
工作记忆层直接用模型的上下文窗口,设一个 8K tokens 的硬上限,超过就做滚动截断。情景记忆层用一个 SQLite 数据库,存每次会话的摘要,按用户 ID 和时间索引。语义记忆层用 Chroma 做向量存储,预先把 FAQ 和产品文档灌进去。
每次新对话开始时,做三件事。
从 SQLite 查出该用户最近 3 次会话的摘要,拼进 system prompt。用当前用户的输入去 Chroma 里检索 top-3 相关知识片段,拼进上下文。把这两块加上当前对话历史,一起发给模型。
这个方案一个下午就能搭起来,效果不会太差。后面再根据实际使用数据逐步优化,该换向量库换向量库,该调压缩策略调压缩策略。
工程上很多事都是这样,先跑起来,再慢慢调。记忆系统也不例外。你得先有一个能用的版本,收集真实用户的交互数据,才能知道哪些记忆真的有用,哪些只是噪声。
那天我盯着那个客服 Agent 的日志看到晚上十一点,改完记忆模块的第一版代码,提交了一个 PR。第二天早上跑了几十个测试 case,退款流程那个场景终于不会再让用户重复说三遍了。窗外开始下雨,我泡了杯咖啡,准备接着调检索的召回阈值,那个数字还差一点没调到满意的位置。