AI Agent 的记忆工程:从短期上下文到长期记忆的三层架构设计

聚焦 Agent 记忆管理的技术实现,对比 RAG、向量数据库、会话压缩的工程化方案

上个月我们团队内部的一个客服 Agent 出了个很离谱的事。

有个用户连续三天找同一个 Agent 咨询退款流程,第一天告诉他要上传凭证,第二天用户带着凭证来了,Agent 完全不认识这个人,又从头来了一遍。第三天用户直接骂了,说你们这个机器人是不是故意的。说实话,看到这个 case 的时候我也挺无奈的,技术上确实没做错什么,但体验上就是灾难。

问题的根源很简单,这个 Agent 没有记忆。每轮对话结束,上下文就清空了,下次再来,跟第一次见面没区别。

你可能觉得这不是常识吗,加个数据库存一下不就行了。还真没那么简单。今天就来聊聊 Agent 记忆这件事,到底该怎么设计。

上下文窗口不等于记忆

很多人把上下文窗口(context window)等同于记忆,这是一个很常见的误解。

上下文窗口更像是工作台。你把当前需要的东西摊在桌上,干完活就收走了。而记忆是你脑子里真正留下来的东西,明天、下周、下个月还能调用的那种。

举个类比。上下文窗口相当于你在白板上写东西,空间有限,写满了就得擦掉一部分。记忆则是你的笔记本,可以积累,可以翻阅,可以建立索引。

现在主流的大模型上下文窗口已经很大了,动辄 128K、200K tokens。你想想看,这么大还不够用吗?

说真的,大部分场景下确实够用。但问题在于几个维度。

第一,成本。上下文越长,推理成本越高,token 费用是实打实的钱。一个 Agent 每次对话都带 100K tokens 的历史记录,一天几千次调用,月底账单会让你心跳加速。

第二,噪声。上下文里塞太多东西,模型反而会「迷路」,注意力分散,回答质量下降。这个现象学术界叫 lost in the middle,有兴趣可以搜搜看,挺多论文的。

第三,持久性。上下文窗口是一次性的,对话结束就没了。你不可能让一个 Agent 记住用户三个月前的偏好,除非你有专门的记忆机制。

所以我们需要一个分层的记忆架构,把不同类型的信息放在不同的层里管理。

三层记忆架构

我比较认同的一种设计思路是把 Agent 的记忆分成三层,对应人类认知科学里的分类。

记忆层 类比 存储介质 生命周期 典型内容
工作记忆 白板 上下文窗口 单次对话 当前对话内容、工具调用结果
情景记忆 日记本 会话存储 跨会话保留 历史对话摘要、用户行为记录
语义记忆 百科全书 向量数据库 长期持久 领域知识、FAQ、业务规则

这三层之间不是孤立的,它们之间有信息流动。工作记忆里的重要内容会被压缩归档到情景记忆,情景记忆中反复出现的模式会沉淀到语义记忆。反过来,语义记忆和情景记忆也会被检索出来注入工作记忆,辅助当前对话。

其实吧,这个分层跟人类大脑的工作方式还挺像的。你在工作时专注处理眼前的任务(工作记忆),偶尔回忆一下昨天开会说了什么(情景记忆),需要用到专业知识时会去翻资料(语义记忆)。

工作记忆,管好你的白板

工作记忆是最基础的一层,就是你当前对话的上下文。

看起来最简单,实际上坑不少。最核心的问题是,怎么在有限的窗口里放最有价值的信息。

一个常见的做法是给上下文分区。

1
2
3
4
[System Prompt]  → 角色定义、行为规则
[Memory Block]   → 从情景/语义记忆检索的相关内容
[Conversation]   → 当前对话历史
[Tool Results]   → 工具调用的返回结果

这几个区域的 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 轮可能已经被滑出窗口了。

更好的做法是分层压缩。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 伪代码,展示分层压缩的思路
def compress_conversation(history, max_tokens):
    # 最近 5 轮保持原文
    recent = history[-5:]
    older = history[:-5]
    
    # 中间部分做摘要压缩
    middle_summary = llm.summarize(older, 
        prompt="提取关键信息,包括用户意图、已确认事项、待办任务")
    
    # 最早的部分做高度概括
    early_summary = llm.summarize(older[:10],
        prompt="用一句话概括这段对话的核心主题")
    
    return [early_summary, middle_summary] + recent

这个方案的关键在于摘要提示词(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,退款流程那个场景终于不会再让用户重复说三遍了。窗外开始下雨,我泡了杯咖啡,准备接着调检索的召回阈值,那个数字还差一点没调到满意的位置。

本博客文章采用 CC BY-NC-SA 4.0 许可协议
服务器推荐

腾讯云 · 新用户专属优惠

本博客部署在腾讯云服务器,稳定运行一年多。如果你是新用户或想搭建个人项目,推荐试试腾讯云的优惠活动。

查看优惠详情 →
阅读 0
上一篇
事件驱动架构在金融交易系统中的落地:从 Kafka 到 Event Sourcing 的选型决策
下一篇
LLM 应用的评估工程:从人工测评到 Eval-Driven Development 的方法论升级
广告

📚 关注公众号,免费获取技术材料

扫码关注公众号,回复「资料」领取:

  • 📘 企业架构设计模板
  • 📗 数据治理实施指南
  • 📙 工业软件技术白皮书
公众号二维码

长按或扫描二维码