news 2026/10/17 16:15:49

AI Agent长期记忆系统:三层架构原理与代码实现指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent长期记忆系统:三层架构原理与代码实现指南

最近我一直在折腾Agent相关的项目,发现一个特别扎心的问题:很多看起来聪明的Agent,一聊深了就露馅——你前天刚跟它说过喜欢的回答风格,它昨天就忘得一干二净。这种“金鱼式”的对话体验,本质上是因为Agent没有记忆系统。最近某高校与某研究院联合发布了一篇关于AI Agent记忆系统的论文,我花了一个周末啃完代码和实验,又自己动手复现了一遍,今天把对小白和程序员都有用的核心内容整理出来。这篇博文会用最直白的方式拆解记忆系统的原理、论文架构、落地代码和实测踩坑,看完你就能给手头的Agent装上“长期记忆”。

1. 为什么AI Agent必须拥有记忆系统?从一次失败的对话说起

先说个我真实遇到过的场景。我用一个开源框架搭了个私人助理Agent,第一轮让它帮我查资料,它回答得头头是道。我随口说了一句“以后回答尽量给我来源链接”,它痛快答应。结果到第二轮,我让它继续上一个话题,它直接翻脸不认人,不仅忘了刚才查了什么,连“带链接”这个要求也抛到九霄云外。我那时候就在想:这玩意儿到底是智能助手,还是老年痴呆?

后来我意识到,问题出在架构上。绝大多数Agent基础框架默认是无状态的,每次对话都是一次独立的推理过程。你把用户问题扔给大模型,它根据上下文窗口里能看到的东西回答,窗口之外的信息一概不管。而真正的“智能体”应该是连续的,它会记得你三个月前的一些偏好,会在今天回答问题时主动调取上周的项目进度,会在遇到新任务时联想到旧经验。这个“跨会话的持续记忆”,就是Agent记忆系统要解决的问题。

某高校与某研究院的这篇论文,把Agent记忆系统分成了三个层次:感知层、存储层和检索层。论文的核心观点是:记忆不仅仅是一个数据库,而是一个需要主动关注的认知流程。Agent不仅要“记住”,还要知道“记住什么”、“怎么存”、“什么时候取出来用”。这三个问题,恰好是小白和程序员在搭建Agent时最容易忽略的部分。

我们普通人平常接触的AI对话,无论是网页端还是API,都有一个大模型自带的上下文Context。这个Context可以理解为短时工作记忆,它负责当前对话的连贯性。但一旦Context被清空,大模型就什么都不记得了。心理学上有个工作记忆和长期记忆的区分,Agent记忆系统的目标就是把对话中重要的信息固化下来,跨越会话甚至跨任务的保存,然后在合适的时候重新拉回Context里。

论文里有一个很关键的设计思路:记忆不是一股脑全部塞进上下文,而是建立一个“记忆分层”。短期上下文负责当前任务,长期记忆负责跨会话的偏好和经验,还有一个“工作记忆”区域用来临时组合当前任务需要的外部数据。这个分层思想,一下就把记忆系统从“囤积数据”变成了“按需取用”,这也是我复现后觉得最值得学习的一点。

2. 论文核心拆解:记忆的获取、存储与检索三层架构

论文中最核心的贡献是一个叫“记忆感知层、记忆存储层、记忆检索层”的三层架构。我用自己的话翻译一下,不搞学术黑话,讲清楚每层是干嘛的。

2.1 记忆获取层:决定什么值得记住

这一层要解决的问题是“从对话中抽什么”。论文里的做法是,不靠人工设计复杂规则,而是用一个小型分类模型去判断对话里的某句话,是否包含用户偏好、任务进度、重要实体、问题结论等信息。比如用户说“我不吃香菜”,这显然是一条偏好转长期记忆。但Agent说“好的,我知道了”,这句话就没必要存。分类模型会把信息按类型打上标签,比如Preference、TaskState、CommonSense、UserIdentity等等。

我记得论文里做实验时,直接用大模型提示词来做这个分类效果就很好,根本不用训练专门的模型。思路是给大模型一个结构化提示模板,让它输出类似“是否需要记忆:是/否、记忆类型:偏好、记忆内容:用户不喜欢香菜”的JSON格式。这种方式很实用,我在复现时也直接沿用了,代码后面会贴出来。

2.2 记忆存储层:不是普通数据库,而是“语义库”

存储层的设计决定了记忆拼起来后能不能用。论文没有推荐用普通的关系型数据库存字符串,而是建议存“语义表示 + 元数据”的结构。所谓语义表示,就是把文本变成向量,也就是Embedding。你把“我不吃香菜”和“你以后点菜别放香菜”这两句话变成向量,它们在语义空间里的距离很近,这样检索的时候就很容易命中。

论文里给了一个比较实用的存储格式:记忆ID、时间戳、记忆内容、记忆类型、以及向量表示。为了控制记忆库体积,还要设计一个遗忘/合并机制。比如相同的偏好如果反复出现,就更新而不是新增;如果一段时间内用户改变了偏好,就覆盖旧记忆。论文中把这个抽象成“记忆卡片”的概念,每张卡片是一个可独立检索的单元,卡片之间可能有链接关系。

2.3 记忆检索层:时机的把握远比检索算法重要

很多人以为检索就是做个向量相似度搜索,论文里帮我点醒的一个点在于:检索时机比检索算法更重要。不是说每次对话开始前都把记忆库里的东西全部拉一遍,而是Agent内部有一个“记忆触发模块”,这个模块根据当前用户输入判断是否需要调用记忆。比如用户问“我上次让你记的那个菜谱呢”,这句显然要触发记忆检索。但如果用户说“你好”,就没必要去检索历史记录,平白加长上下文还污染注意力。

论文还对检索结果做了一次重排,不只是简单地取Top-K个向量结果,而是结合记忆类型、时效性和相关度做加权评分。比如带有用户明确指令的记忆,权重要大于闲聊时的随口一提。这个重排逻辑在实测中能明显改善回答质量,我复现时把初始的Top-5结果重新排序后,准确率至少提高了三成。

2.4 记忆更新的时机与策略

论文里有个在联调时很重要的策略:记忆并不是在对话结束后一次性全量更新的,而是采用“定期快照 + 即时时更新”。对话过程中,只要出现了重要信息点,就立刻写进存储层,保证Agent随时能查到。同时每隔一版对话轮次生成一次快照,把整个会话的摘要压缩并存入长期记忆。这个做法很像人脑,短期记忆不断巩固成为长期记忆,而不是等到谈话结束才开始回忆。

我在复现时发现一个更微妙的点:如果所有信息都即时写入,很可能把中间态的废话和错误信息也存进去。论文的处理是引入一个“置信度”字段,在信息刚出现时置信度低,当同一个信息在对话中被反复提及或得到用户确认后,置信度提高,才会进入长期记忆。这个置信度机制直接解决了记忆噪声问题,非常实用。

3. 手把手搭建一个基础记忆模块:从零写代码

前面说的全是理论,接下来动手。我会用最简单的Python代码,不依赖复杂框架,在视觉上跑通一个基础的记忆模块。这个模块只做三件事:抽取记忆、存储记忆、按要求检索记忆。你把它插进自己的Agent链路里,就能立刻见效。

3.1 初始化记忆管理类

我先定义了一个MemoryManager类,属性包括一个字典storage,用来存记忆卡片;一个list用于存储向量库的索引,这样方便测试和调试。如果之后要上规模,就把storage换成向量数据库。

import json import time from sentence_transformers import SentenceTransformer import numpy as np class MemoryManager: def __init__(self, model_name="paraphrase-multilingual-MiniLM-L12-v2"): self.storage = {} # 记忆ID -> 记忆卡片 self.vectors = [] # 用于检索的向量列表 self.model = SentenceTransformer(model_name) self.id_counter = 0

这里的SentenceTransformer模型是我本机用的一个小体积模型,如果你内存有限,也可以换成别的Embedding模型,只要保证检索文本和你查询文本是同一个模型编码的就行。

3.2 对话内容存入记忆

核心方法add_memory接收一段文本和类型标签,内部做两件事:存原始文本和元数据,并且同时生成向量保存。为了模拟论文里的置信度机制,我加了一个参数repeat_count,当同一内容多次出现时可以调用update_confidence来更新权重。

def add_memory(self, content, mem_type="statement", confidence=0.6): mem_id = f"mem_{self.id_counter}" self.id_counter += 1 timestamp = time.time() card = { "id": mem_id, "content": content, "type": mem_type, "confidence": confidence, "timestamp": timestamp, } self.storage[mem_id] = card vec = self.model.encode(content) self.vectors.append((mem_id, vec)) return mem_id def update_confidence(self, mem_id, new_confidence): if mem_id in self.storage: self.storage[mem_id]["confidence"] = new_confidence

这里有一个人工传类型和置信度的步骤,实际项目中你应该让大模型来自动判断,而不是写死。后面我会给一个基于提示词抽取的版本,那段逻辑和这里完全兼容。

3.3 检索相关记忆

检索方法retrieve接收用户查询文本,先编码查询向量,计算向量库里所有记忆的余弦相似度,再结合置信度和时效性做加权。论文里的重排逻辑在这里体现,我取Top-8分数,然后按confidence和最近时间加分,输出最终的Top-3。

def retrieve(self, query, top_k=3): query_vec = self.model.encode(query) scores = [] for mem_id, vec in self.vectors: sim = np.dot(query_vec, vec) / (np.linalg.norm(query_vec) * np.linalg.norm(vec)) card = self.storage[mem_id] score = sim * 0.7 + card["confidence"] * 0.3 if time.time() - card["timestamp"] < 3600: score += 0.1 scores.append((mem_id, score)) scores.sort(key=lambda x: x[1], reverse=True) return [self.storage[mem_id] for mem_id, _ in scores[:top_k]]

这里注意,我的相似度计算没有单独搞一个向量索引,对单机测试足够了。如果你有几十万条记忆,肯定要上Faiss这类向量索引,后面会说。

3.4 把记忆注入Prompt

真正集成到Agent里的时候,检索出的记忆并不是直接丢给大模型,而是拼进System Prompt的一段“记忆上下文”里。我建议把记忆用列表的形式显性展示,让大模型知道这些是历史事实,而不是新输入。

def build_memory_context(memory_manager, user_query): mems = memory_manager.retrieve(user_query) lines = [] for m in mems: lines.append(f"- {m['content']} (salience: {m['confidence']:.2f})") if lines: return "以下是对你有用的历史记忆:\n" + "\n".join(lines) return ""

实际使用时,在大模型的多轮对话Messages里插入SystemContent,和原始SystemPrompt合并。这个设计简单粗暴,但已经能让Agent感知到用户过去说过的重要信息。

3.5 用大模型自动抽取记忆

上面加记忆的时候,人工判断类型和置信度挺累的。论文里有一个更聪明的方法:让大模型自己输出结构化记忆。我给一个常用的提示词模板,用在这个需求上非常稳。

请判断下面这句用户对话中是否有需要长期记忆的信息。 输出JSON,格式为{"should_store": "是/否", "mem_type": "preference/task_state/other", "content": "摘录需要记忆的文本", "confidence": 0.0-1.0} 用户对话:{{user_input}}

这个提示词对GPT系列和开源模型都适用。注意confidence的设定,我通常让模型把置信度定为0.6到0.9之间,不设太低,因为一旦该存没存就损失了。你可以在实际对话中慢慢调。

接到Agent链路后,每当用户发来一条消息,先跑一次这个抽取提示词,如果should_store为“是”,就调用add_memory写入。这样记忆系统就闭环了:自动识别、自动存储、按需检索、注入上下文。

4. 让记忆更聪明:向量检索与持久化技巧

上一节代码是最小可用版本,能在小范围内跑通,但离实用还有距离。要在真正的业务场景里用起来,你需要解决两个大问题:检索性能和记忆持久化。我接着把这两块的实战经验补完。

4.1 为什么简单相似度检索不够用

在小数据集上,直接遍历所有记忆向量算余弦相似度没有问题。我有一次在一个两千条的测试集上跑,单个查询也就几十毫秒,体感很快。但当记忆库冲到十万级甚至百万级,纯遍历的耗时就没办法接受,因为每次查询都要把全量向量从内存里过一遍。

工业界的做法是引入近似最近邻索引,比如HNSW、IVF这类。原理我简单解释一下:它们先把向量空间分成一个个区域,检索时只搜索查询向量附近的桶,而不是全量计算。印象里我当时用HNSW,在召回率和速度之间找到平衡点,十万条数据查询延迟从50毫秒降到1毫秒以内,效果显著。

你可以把向量索引换成Faiss或者轻量一些的向量数据库。我的经验是:如果你只是个人项目,直接装一个sqlite-vss就够了,简单又能持久化。如果你想做点正经产品,建议用成熟的向量数据库,比如Qdrant或者Chroma,都支持本地运行和HNSW索引。

4.2 数据持久化:断电不丢失

代码里MemoryManager只存在内存里,进程一关就全没了。真实Agent一定要把记忆落到磁盘。我的做法是先用JSON把storage目录定期导出,后面再把这个设计改造成sqlite存储。

一个很实用的姿势是把JSON备份文件和向量索引分开保存。向量索引每隔几分钟增量更新,JSON文件在每次会话结束后全量导出。这样即使向量索引损坏,也能从文本库里重建,不至于彻底丢记忆。

def save_to_json(self, path="memory_dump.json"): with open(path, "w", encoding="utf-8") as f: json.dump({ "storage": self.storage, "id_counter": self.id_counter }, f, ensure_ascii=False, indent=2) def load_from_json(self, path="memory_dump.json"): try: with open(path, "r", encoding="utf-8") as f: data = json.load(f) self.storage = data["storage"] self.id_counter = data["id_counter"] self._rebuild_vectors() except FileNotFoundError: pass

注意,load_from_json里有一个_rebuild_vectors方法,就是把storage里所有的content重新编码一次,以便重新填充self.vectors。这个过程可能比较费时,但能保证完整性。

4.3 时效性衰减与记忆合并

刚才我提到时效性权重,简单加0.1分的方式太粗糙。论文里给了一个更优雅的时间衰减函数:score = sim * 0.6 + confidence * 0.3 + recency_weight,其中recency_weight = exp(-hour_diff / half_life),half_life按记忆类型设置。用户偏好这种长期稳定的信息,half_life设大一些,比如720小时;任务状态这种短期信息,half_life设小一些,比如12小时。

记忆合并也很关键。比如同一用户对同一话题多次发言,但内容高度相似,你可以直接把旧的记忆卡片替换成新的,而不是不断新增。我在复现时实现了一个“去重合并”逻辑:检索出历史Top-5相关的同一类型记忆,如果他们的向量相似度超过0.9,就把先前的记录替换为当前的最新内容,同时更新置信度。

这个合并机制最大的好处是控制记忆库体积,不会因为用户反复强调“我不吃辣”就往库里塞一百条重复记忆,拖慢检索还污染上下文。

4.4 集合大量记忆时的上下文注入策略

如果检索出来的相关记忆太多,不可能全部塞进上下文。论文里的做法是设定“最大记忆条数”和“最大记忆token量”上限。我的经验是:普通任务只放Top-5记忆,每个记忆压缩到一句话的长度,足以覆盖上下文需求。如果某个任务明显依赖更多背景,再单独调大上限。

这里有一个值得注意的细节:记忆注入的位置非常关键。如果放在SystemPrompt里,大模型的注意力会更均匀;如果直接放在用户对话后面,模型可能误当成当前指令。我试验下来,把记忆放在一个单独的“记忆区块”,并用指令明确“这些不是当前用户输入,只是历史事实参考”,效果最好。

5. 实测中的坑:记忆混淆、过期与上下文超限

论文读起来很完美,但真正落地的时候总会翻车。我花了两个晚上在模拟项目和某跨平台系统上做了一些实测,把最有代表性的三个坑单独拎出来说,这些坑我估计很多人绕不开。

5.1 记忆混淆:多条相似记忆打架

最典型的坑叫做“误伤召回”。我在一个客户信息管理Agent里测试,用户之前提到“甲项目本周交付”,后来又提到“乙项目本周交付”。检索时如果只做相似度,这两个记忆极易互相干扰,而如果盲目拼接进Prompt,大模型就可能把甲项目和乙项目的截止日期搞混。

解决办法有两个。第一,在存储层给记忆加上更强的领域元数据,比如项目名、用户主键,检索后先做元数据过滤,再做向量排序。第二,把记忆内容的格式规范得更明确,比如每条记忆强制以“实体-属性-值”的结构存储,可以大幅减少活用的语义偏差。比如“甲乙项目”分别作为Entity字段,检索结果里就很容易区分。

5.2 过期记忆反复出现

另一个常见是记忆过期。比如用户在两周前告诉Agent“我这个季度目标是完成500万销售额”,现在这个季度快结束了,管理员把目标改成了800万。但Agent还保留着旧目标,每次催促进度时就引用旧数据,用户体验极其糟糕。

论文里提到的置信度机制本来就该管这个事,但我在实现时没做好“逆反馈”。我的经验是每次记忆被检索出来并投入使用之后,要有一个记忆反馈回路:用户的后续反馈如果明确否定了旧记忆,就要下调置信度,甚至直接删除。我在自己项目里加了一个简单的“否定检测”模块,如果用户回复中包含“不对”、“不是”、“错了”等词,就主动检查相关记忆卡片并修正,实测后记忆准确性基本稳定。

5.3 上下文超限与“记忆污染”

第三个坑是记忆注入之后,大模型的上下文窗口爆掉。这个问题在小模型上更明显。我曾在一个上下文窗口只有4K的模型上做测试,给Agent注入了三条记忆再加上历史对话,直接超出长度限制。

后来学到一个有效策略:记忆的部分只保留摘要,摘要由轻量模型生成。比如把用户一段充满细节的描述压缩成一句话,“用户偏好简约风、预算5000以内”,这样就极大缩减token消耗。最终我采用双层的记忆结构:顶层是精简摘要,底层是详细原文,上下文里只放摘要,详细原文在需要时才临时拉取。这个方法对长对话特别管用。

6. 从入门到实践:小白与程序员各自的学习路径

最后区分一下小白和程序员的入门路径,因为这两种人遇到的问题完全不同。小白可能不碰代码,就想理解原理并用现成工具搭一个能持久记忆的Agent;程序员则想彻底掌控底层,搭出自己的记忆管理系统。

6.1 小白先跑通“记忆插件”再倒追原理

如果你是代码不太熟悉的小白,第一步不用管论文里的向量索引和置信度公式,先去找那种自带记忆模块的Agent框架。现在很多现成的项目已经内置了记忆能力,配置一个向量数据库路径就能开箱即用。你先用起来,每天写点测试对话,直观感受一下那些记忆到底是怎么被记住的。

第二周再回头看我上面的代码,你会发现,原来记忆就是“存文本-算相似度-拼进Prompt”这样一个简单流程。到时候理解起论文的分层架构就毫无压力。我给小白总结的学习路线:

  • 阶段一:用现成工具把记忆功能跑通,记录自己的感受。
  • 阶段二:用最简Python代码把内存版记忆管理器复现一遍。
  • 阶段三:把内存版升级为JSON持久化,模拟重启后记忆恢复。
  • 阶段四:加入向量检索和重排序,感受语义检索比关键词检索强在哪。

小白的核心瓶颈通常不在算法,而在工程习惯。遇到报错先学会看日志,学会把问题拆成“存储错误”还是“检索错误”,就不会一头雾水。我在带A同学时发现,他最后动手改代码只花了半天,前面大半天都在跟我讲项目里哪些场景需要记住哪些信息。想清楚这个,比代码本身重要得多。

6.2 程序员直接上完整链路

程序员上手就更直接了。我给一个建议:把论文的三层架构真正落地到一个带数据库的项目里。参考实现顺序是:

  1. 把MemoryManager改成异步并发,通过消息队列接收记忆写入请求。
  2. 接入向量数据库,用HNSW索引替换之前的朴素数组。
  3. 设计数据库表结构,记忆ID做主键,元数据和向量索引冗余存储。
  4. 设计一个记忆审计接口,允许用户后台直接查看和编辑Agent记忆。
  5. 上线后持续记录记忆命中率,观察哪些场景检索结果命中率低,再针对性调整重排权重。

程序员开发时需要注意性能基准。我在某模拟项目里做压测时发现,记忆写入的延迟如果高于200毫秒,用户就能明显感觉Agent“卡一下”。所以写入链路一定要用异步任务,或者是消息队列。检索链路控制在5毫秒以内,才能让记忆在线路不拖后腿。

6.3 无论什么基础,都要练一个“记忆心态”

最后说点软性的东西。设计记忆系统和设计普通业务逻辑不一样,它的核心是“设身处地”想Agent该记住什么。程序员很容易写出存储一切的程序,但那样效率特别低。我从论文里学到最深刻的一点是:记忆系统的价值不在存储,而在遗忘和取舍。该记的记,不该记的果断丢,这才是长期可用的Agent。

我建议每个人都可以给自己的Agent做一张“记忆卡片清单”,列出十种你想让它长期记住的事实。比如“用户偏好、已确认的任务目标、重要截止日期、沟通风格”。然后以清单为基准,去测试你的记忆模块有没有准确捕获和检索这些信息。以这种模式进行迭代,记忆系统才能真正为Agent赋能。

最后分享一个我踩过很多次后养成的习惯:每次做记忆相关的改造,我都会强制自己写一条用户对话作为验收用例,比如“请问我上次说的预算上限是多少?”然后跑一遍完整链路,看Agent能不能从冷启动状态准确回答。只要这种验收用例能稳定通过,我对这套记忆系统的信心就会越来越强。这个检测思路,推荐给所有正在给Agent加记忆的人。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 16:14:53

数据库关系代数与SQL实战入门:从课本习题到可执行查询

简介&#xff1a;本资源是《数据库系统概论&#xff08;第5版&#xff09;》王珊、萨师煊主编教材的配套课后习题详细答案&#xff0c;面向高校计算机、软件工程、信息管理等专业本科生及数据库初学者&#xff0c;用于巩固关系模型、关系代数、SQL语言等核心理论知识&#xff0…

作者头像 李华
网站建设 2026/10/17 16:14:40

Coze技能与工作流实战拆解:从机制到选型

聊Coze的技能&#xff08;Skill&#xff09;和工作流&#xff08;Workflow&#xff09;&#xff0c;我最近特别有发言权。为了把一个实际项目跑通&#xff0c;我把这两个功能从产品逻辑到落地细节&#xff0c;从界面操作到底层机制&#xff0c;里里外外拆了个遍&#xff0c;踩过…

作者头像 李华
网站建设 2026/10/17 16:13:40

Qt WebEngine加载百度/谷歌地图:从WebView到QWebChannel的完整实践

简介&#xff1a;面向Qt开发者的地图集成参考示例&#xff0c;演示如何在Qt Widgets应用中通过QWebView加载百度地图与谷歌地图。资源将C与Web前端结合&#xff0c;给出从创建QWebView、设置地图URL、调用JavaScript API到实现交互的完整编码思路&#xff0c;适合初、中级Qt开发…

作者头像 李华
网站建设 2026/10/17 16:12:51

AI Agent社交网络ClawLink:多智能体协作与任务编排机制全解析

一直有一种感觉&#xff0c;市面上单个AI Agent已经不少了&#xff0c;能写代码的、能画图的、能查数据的&#xff0c;单独拎出来都能干活。但一旦涉及到真实企业里的业务流程&#xff0c;立刻卡壳。为什么&#xff1f;因为真实业务从来不是一个人能包圆的。做一个活动页面&…

作者头像 李华
网站建设 2026/10/17 16:11:57

V8引擎如何让JavaScript更快?AST、字节码、JIT与去优化全解析

写技术科普最怕一个情况&#xff1a;讲的人自己都没想清楚&#xff0c;却在文里塞满术语。这篇想聊的 V8&#xff0c;恰恰是 JS 性能话题里最容易聊成"名词堆砌"的领域。随便一个帖子都能看到 AST、字节码、JIT、去优化&#xff0c;可真要把这四者串成一条能讲清&quo…

作者头像 李华