老城街角的茶馆,柜台后的老掌柜手边总摆着两样东西:一张写满名字的便签纸,和一本锁在抽屉里的牛皮账本。便签记着正在招待的几位客人——谁刚续了水、谁还欠着找零;账本里却躺着几十年的老主顾——王姐忌辣、赵叔爱坐窗边、张先生每次都要听评弹。便签用完就撕,账本却要跟人一辈子。AI Agent(智能体)想成为真正“懂你”的助手,也需要同样两套记忆:对话中随手可取的短期工作记忆,和跨越时间沉淀的长期持久记忆。这篇教程,我们就借这位掌柜的账房,把记忆编码、检索增强回忆、遗忘策略、多会话管理、隐私边界与工程落地,一件件讲明白。
掌握 Agent 记忆系统,你就掌握了让 AI 从“每次都是陌生人”到“比你还记得你”的核心能力——这是智能客服、个人助理、陪伴型 Agent 等一切个性化产品的基石,也是 AI 工程师绕不开的工程必修课。
目录
可视化演示
动态展示短期长期双层架构、记忆编码存储、检索增强回忆与遗忘隐私管理机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
记忆系统架构:便签与账本双轨并行
茶馆掌柜能同时招待十几桌客人,靠的是两样东西分工明确。柜台上那张便签纸,记着正在进行的对话:这桌刚点了什么、那桌催了几遍——它又小又薄,随时更新、随时撕掉。抽屉里那本牛皮账本,却记着几十年不变的“大事”:王姐的忌口、赵叔的靠窗偏好、张先生对评弹的痴迷。便签管“当下”,账本管“长久”,缺一不可。
AI Agent 也一样。一次对话里,它需要“柜台便签”——短期工作记忆(Working Memory),保存最近几轮对话和当前任务状态;也需要“牛皮账本”——长期持久记忆(Long-Term Memory),让用户第二天回来时还记得昨天提过的需求。绝大多数大模型本身没有记忆,每次调用都像新来的伙计,什么都不记得。
Agent 记忆系统的核心是一条流水线:感知(接收输入)→ 编码(整理成可存储条目)→ 检索(从长期记忆找回相关片段)→ 生成(拼进上下文回答)→ 回写(把新信息归档)。短期工作记忆通常放在上下文窗口里,以一个固定大小的消息队列实现;长期记忆放在外部存储(如向量数据库)中,按需检索。每一轮对话,Agent 都先用检索结果扩充“便签”,结束时再把有价值的“闲谈”归档进“账本”。
就像掌柜倒茶时先扫一眼便签、再联想账本里这位客人的旧偏好,Agent 也是先“看”眼前对话,再“翻”记忆库,最后才开口。双系统协同,便是 Agent 记忆的骨架。
- M_t^{(WM)} 当前便签第 t 轮对话时短期工作记忆的内容,也就是“柜台便签”上现在写着什么
- R(M^{(LTM)}, q_t) 账本回忆用当前问题 q_t 去长期记忆里检索出的相关片段,相当于掌柜凭问题翻账本
- x_t 新来的话用户本轮新输入的原文,是刚听到的新信息
- f_θ(……) 掌柜的思考大模型把这些信息拼在一起推理,生成本轮回答
- M_{t+1}^{(WM)} 更新后的便签本轮结束后的新短期记忆,下一轮就从这里开始
记忆编码:把闲谈写成档案
客人随口一句“最近胃不大好,少放点辣”,掌柜不会把整句话背下来,而是掏出小本写“赵叔——忌辣、胃不适”。这里发生了两次转化:从语音到文字,从文字到要点。这就是记忆编码——把生活里的一句闲谈,变成账本里一个有用的条目。
Agent 的记忆编码,就是把原始对话整理成“能存、好找、用得上”的记录。一条好的记忆条目通常由三部分组成:语义内容(这条记忆讲什么)、向量表示(让机器能算相似度的“数字指纹”)、元数据(时间、来源、重要度、关联实体)。编码流程分两步:先用大模型或信息抽取器做结构化,提取关键实体并填写元数据;再用嵌入模型把文本映射成一个高维向量。此后所有检索都在向量空间中进行——两个文本语义有多接近,就看两个向量夹角有多小。
掌柜写“忌辣”而不是“今天他说他最近胃不好”,因为前者更好检索、更省空间。Agent 的编码目标也一样:话要短、信息量要大,还要保证以后“凭一个关键词就能翻出来”。
- q、m 语义指纹查询文本和记忆条目各自编码出的向量,像两个人的“气味标签”
- q·m 方向一致性点积,逐坐标相乘再求和,衡量两个向量是否“同向”
- ‖q‖×‖m‖ 长度归一用模长做除法,抵消文本长短带来的偏差
- 结果越接近 1 越像值域在 [-1, 1],接近 1 表示语义高度相似
检索增强回忆:翻账本的学问
王姐跨进店门,掌柜扫一眼,脑子里立刻浮出几行账本条目:“王姐——靠窗、普洱、怕吵。”他不是把整本账从头背到尾,而是凭几个特征快速定位。这个“定位”的动作,就是检索。
Agent 的检索增强回忆(Retrieval-Augmented Recall)解决一个核心矛盾:长期记忆可能存了成千上万条,上下文窗口塞不下,只能“挑最相关的几条”。挑的方法是:把当前问题向量化,去记忆库里找最相似的若干条。但实践中,纯向量检索(语义)会漏掉名字、编号这类精确信息,所以生产系统常用“混合检索”:向量检索负责语义相近,关键词/全文检索负责字面精确,两路得分加权融合。拿到候选后还要重排(Rerank),过滤低相关条目,再组装成上下文片段注入提示词。
就像掌柜既会凭“那位常喝普洱的女士”这种模糊描述找人(语义),也会凭“王姐”两个字直接翻档案(字面)。两路并用,关键时刻才不会掉链子。
- sim_sem 语义路向量余弦相似度,找到“意思差不多”的记忆
- sim_lex 字面路关键词与全文匹配,找到“字一模一样”的记忆
- α 两条路的权重0 到 1 之间的系数,调大 α 就更依赖语义理解
- 加权和综合排名两路得分融合后排序,取前 K 条进入上下文
遗忘清理策略:旧档案的归宿
账本不能无限厚。掌柜每月都会翻一遍:泛黄的、三年没来的客人归档进旧箱;与现状明显不符的直接划掉。遗忘,从来不是记忆系统的缺陷,而是它的健康机制。
Agent 的记忆库若不清理,会带来两个问题:噪声——无关记忆干扰检索精度;成本——存储和检索越来越慢、越来越贵。因此需要遗忘策略:把“旧、弱、不重要”的记忆降权或删除。常见策略有三:时间衰减(越久权重越低)、重要度评估(按访问频率和用户反馈打分)、容量上限(超出阈值就淘汰最低分或最久未用的)。工程上常用指数衰减:记忆强度随时间指数下滑,每被访问一次就“续命”一次。
好的遗忘是“有选择的遗忘”:贵客的信息十年不忘,一次性的问路当天就清。Agent 也应学会区分“值得记一辈子”和“说完就忘”。
- w0 初始强度记忆刚写入时的强度,通常设为 1.0
- λ 遗忘速率衰减系数,λ 越大忘得越快
- t−t0 时间差当前时间与上次强化(写入/访问)之间的间隔
- e 的负指数强度随时间指数下滑,越久越弱
- 低于阈值就清理当 w(t) 小于设定阈值时触发归档或删除
多会话记忆管理:跨天的待客之道
王姐上周来喝过普洱,这周又来。掌柜不但记得她喝什么,还记得她上次抱怨空调太冷,这次提前调开了风口。从上周到这周,“记忆”跨越了两次会话——这是长期记忆最值钱的时刻,也是最难管理的时刻。
多会话场景下,Agent 要解决三个问题:会话边界(什么时候算一次会话结束)、记忆更新(新信息来了怎么改旧档案)、冲突消解(新信息与旧记忆矛盾时信谁)。工程上常见做法:用 session_id 标识会话;会话结束时把短期记忆里尚未入库的内容批量归档;更新采用“增量覆盖”——新观察以一定权重修正旧记忆,而不是直接删除旧记录;当新旧冲突严重时,保留两条并标注时间戳,或让用户明确确认。跨会话继承通过检索实现:新会话开始时,先用问候语检索旧记忆,把相关条目作为“开场白背景”注入上下文。
掌柜不会被顾客一句“今天不想喝茶”就推翻“她爱普洱”的档案,而是记一笔“今日例外”。Agent 也一样:一次异常不推翻长期规律,但也不能完全无视——折中更新,才是跨会话服务的精髓。
- M 旧档案之前记录的旧记忆,比如“王姐忌辣”
- M_obs 新观察用户最新的表述,比如“我胃好了可以吃辣了”
- γ 更新速率0 到 1 之间的系数,控制新信息多大程度改写旧档案
- M_obs − M 差异量新旧信息差得越多,修正幅度越大
- M' 折中结果更新后的记忆,既保留经验又吸收新变化
隐私安全边界:什么该记、什么不该记
掌柜最守规矩的地方,恰恰是他“记事的分寸”。客人酒后失言的事,他绝不落笔;记下的口味偏好,也绝不在别的客人面前提起。记忆系统的边界,决定了用户敢不敢信任它。
Agent 的记忆系统会接触到大量个人信息:姓名、习惯、健康状态、支付意愿。隐私保护不是“不存数据”,而是“分级存储、按需脱敏、授权使用”。设计上要做三件事:识别敏感信息、入库前脱敏、读取时权限控制。具体手段包括:用敏感信息识别器检测姓名、电话、地址等 PII(个人身份信息);入库前做掩码/泛化(“1381234“);给每条记忆打隐私级别标签,只有具备对应权限的调用方才能读取;同时设定保留期限,到期自动删除。用户还应拥有”查看/删除”自己记忆的入口。
掌柜的账房先生也看不到所有账——不同伙计有不同的“可见范围”。Agent 记忆系统亦然:把“能看”和“该看”分开,信任才有基础。
- perm(u) 权限等级调用方 u 拥有的访问权限等级
- level(m) 敏感等级记忆条目 m 的隐私分级(如 0-3 级)
- 指示函数门槛权限 ≥ 敏感等级才放行,否则返回 0
- mask(m) 脱敏兜底即使能读,敏感字段也要被掩码处理
工程落地实践:从账房到收银系统
手工账本能用几十年,但一天要接待一万个客人时,就得换成收银系统。Agent 记忆系统从原型到生产,同样要经历一场“从账房到系统”的升级。
生产环境有四个关键决策。存储选型:记忆规模百万条以内可选 Redis、pgvector;规模更大换专用向量库(如 Milvus、Qdrant)。性能优化:检索延迟要控制在几十毫秒内,手段包括近似最近邻索引(如 HNSW)和热记忆缓存(常访问的条目驻留内存)。评估监控:上线前用 Recall@K、NDCG 等指标评估检索质量,上线后持续监控记忆命中率与用户反馈。生产经验:记忆写入要防重与去抖(同一信息反复写入时合并),批量异步入库,定期做一致性校验。
从手写纸条到收银系统,变的是万倍的吞吐和毫秒级的响应,不变的是同一个道理:记住该记住的,需要时一秒翻出来。工程化的全部努力,都指向四个字——可靠记忆。
- R 应该被找到的所有与当前问题相关的记忆集合
- T_K 实际返回的系统取回的前 K 条候选记忆
- 交集分子该找到的里面真正被找到几条
- 比值越接近 1 越好召回率越高,检索越不遗漏关键记忆
AI 应用场景
智能客服:隔月还认得你
阶段:长期持久记忆 + 检索增强回忆
解决的问题:传统客服系统每通对话都从零开始,用户要反复报设备型号与工单号,体验差、效率低。AI 记忆系统把每次服务的要点编码入库,下次对话自动检索历史,实现“续接式服务”。
具体案例:某智能硬件厂商的客服 Agent,用户 3 月报修“笔记本散热异响”,系统存入设备型号、问题、处理进度等记忆条目;4 月用户再次来访说“还是有点烫”,检索模块命中旧条目并组装上下文,Agent 直接接上话题:“您上次更换的散热垫效果如何?”并给出升级方案,用户重复描述的频次下降约 60%。
教育辅导 Agent:记住每个孩子的“口味”
阶段:多会话记忆管理 + 记忆更新
解决的问题:一对一辅导最怕“每次重新摸底”。AI 辅导 Agent 用记忆系统记录学生对各知识点的掌握度,跨会话更新学情画像,像掌柜记住客人忌口一样记住孩子的薄弱环节。
具体案例:数学辅导 Agent 在第一次课记录“分数运算薄弱、计算粗心”;第三次课开始时检索到该记忆,自动降低运算题难度并提示检查步骤;当学生连续两次正确完成同类题后,系统把记忆更新为“已掌握分数加法”,让画像保持“保鲜”。
健康助手:守口如瓶的边界感
阶段:隐私安全边界 + 记忆编码
解决的问题:健康类 Agent 会接触血压、体重、用药等高度敏感信息。它既要“记住”以提供个性化建议,又要“守口如瓶”防止泄露。
具体案例:某慢病管理助手在编码阶段对手机号、姓名等字段做掩码,给每条记忆打隐私等级标签;用户本人通过认证后可查看全部记忆并一键清空,客服角色只能读取脱敏后的统计信息,所有记忆 90 天后自动过期。
Python 演示
"""老茶馆记忆系统:演示 Agent 记忆的编码、检索、遗忘与更新一条对话先进"柜台便签",满了归档入"牛皮账本";新会话凭问题检索旧账本,旧记忆随时间衰减,弱记忆被清理。"""import mathimport re def tokenize(text): """最简编码:把中英文句子切成词条""" return re.findall(r'[\w\u4e00-\u9fa5]+', text) def cosine_sim(a, b): """两个文本的向量余弦,衡量语义相似度""" va = {} for w in tokenize(a): va[w] = va.get(w, 0) + 1 vb = {} for w in tokenize(b): vb[w] = vb.get(w, 0) + 1 inter = set(va) & set(vb) dot = sum(va[w] * vb[w] for w in inter) na = math.sqrt(sum(v * v for v in va.values())) nb = math.sqrt(sum(v * v for v in vb.values())) return dot / (na * nb) if na and nb else 0.0 class AgentMemory: """掌柜的记忆系统:短期便签 + 长期账本""" def __init__(self, max_short=2, decay=0.05): self.max_short = max_short self.decay = decay self.short_term = [] # 柜台便签:最近的对话轮次 self.long_term = [] # 牛皮账本:长期记忆条目 self.gc_count = 0 # 全局时钟,用于时间衰减 def observe(self, speaker, text): """听到一句话:先进便签,便签满了就把最老的归档""" self.short_term.append((speaker, text)) if len(self.short_term) > self.max_short: self._commit(self.short_term.pop(0)) self.gc_count += 1 def _commit(self, item): """记忆编码:原始对话 -> 结构化条目 + 元数据""" self.long_term.append({ 'speaker': item[0], 'text': item[1], 't': self.gc_count, 'strength': 1.0, }) def _match(self, query, m): """混合匹配:语义相似为主,名字命中则直接加分""" sim = cosine_sim(query, m['text']) if m['speaker'] in query or query in m['text']: sim = max(sim, 0.7) return sim def recall(self, query, top_k=2): """检索增强回忆:相关度 x 强度 x 新鲜度,取 Top-K""" scored = [] for m in self.long_term: m['strength'] *= (1 - self.decay) age = math.exp(-0.02 * (self.gc_count - m['t'])) score = self._match(query, m) * m['strength'] * age scored.append((m, score)) scored.sort(key=lambda x: -x[1]) return [m for m, _ in scored[:top_k]] def forget(self, threshold=0.3): """遗忘策略:强度低于阈值的旧记忆被清理""" self.long_term = [m for m in self.long_term if m['strength'] >= threshold] def main(): mem = AgentMemory(max_short=2) mem.observe('王姐', '最近胃不舒服,少放点辣的') mem.observe('伙计', '今日茶点:桂花糕、绿豆酥') mem.observe('王姐', '来一壶普洱,谢谢') print('短期便签:', [t for _, t in mem.short_term]) print('长期账本:', [(m['speaker'], m['text']) for m in mem.long_term]) hits = mem.recall('今天给王姐准备什么茶') print('检索回忆:', [(m['speaker'], m['text']) for m in hits]) mem.observe('王姐', '我胃好了,可以重新吃辣了') for m in mem.long_term: if '少放点辣的' in m['text']: m['text'] = '喜欢吃辣' m['strength'] = min(1.0, m['strength'] * 0.5 + 0.8) mem.long_term.append({'speaker': '路人甲', 'text': '请问厕所在哪', 't': 0, 'strength': 0.01}) mem.forget(threshold=0.3) print('更新与遗忘后:', [(m['speaker'], m['text']) for m in mem.long_term]) if __name__ == '__main__': main()
return re.findall(r'[\w\u4e00-\u9fa5]+', text)把中文英文句子切成词条,这是最朴素的“编码”return dot / (na * nb) if na and nb else 0.0余弦相似度公式落地,空文本时安全返回 0self.short_term.append((speaker, text))新信息先进“柜台便签”,短期工作记忆self._commit(self.short_term.pop(0))便签超限,最老的一条归档进“牛皮账本”m['strength'] *= (1 - self.decay)每次检索都让记忆强度轻微衰减,模拟“遗忘”score = self._match(query, m) * m['strength'] * age综合得分 = 语义相关度 × 强度 × 新鲜度scored.sort(key=lambda x: -x[1])按综合得分降序排列,取前 top_k 条作为“回忆”m['text'] = '喜欢吃辣'增量更新:用新观察修正旧档案,而不是删除重建self.long_term = [m for m in self.long_term if ...]遗忘策略:按阈值清理弱记忆,账本保持“干净”
常见误区
误区想让 Agent 记住更多?把上下文窗口调大,把所有聊天记录都塞进提示词不就行了?
正解加长上下文只是“便签纸变大”,不等于记忆系统。它有三个问题:成本随长度线性爆炸(每轮都要重算所有 token);噪声干扰(无关旧对话稀释注意力);完全没有结构——无法回答“这位用户最喜欢什么”这类聚合问题。真正的记忆系统需要编码、存储、检索、遗忘的完整闭环,而不是一味把纸加大。掌柜再厉害,也不可能把十年对话都摊在柜台上。
为何常见:大模型长上下文能力宣传很响,“塞得下”很容易让人误以为 “记得住”,忽略了调度与检索才是关键。
误区长期记忆库越大,Agent 就越了解用户;宁可多存也不删,删了就是丢用户信息。
正解存储是成本,检索是噪声放大器。百万条记忆里塞了 90% 的垃圾,检索出的 Top-K 大概率也是垃圾。遗忘策略(时间衰减、重要度评估、容量上限)恰恰是保障检索精度的关键。就像掌柜不会把客人每个喷嚏都记进账本——记太多,真正要找的那条反而被埋住。
为何常见:直觉上“大数据=更聪明”,但记忆系统里“精准的小数据”远胜“噪声里大海捞针”。
误区把对话写进数据库存好,以后要查的时候 SQL 一搜就出来了,跟文件归档一样可靠。
正解记忆写入是“有损编码”——原始对话被压缩成要点 + 向量 + 元数据,细节必然丢失;读取是“相似度检索”——只有与当前问题向量足够接近的记忆才会被召回,换个问法可能就找不到了。这就是为什么需要混合检索和重排,且关键信息在编码时要保留原文摘要。掌柜记录时会用简写,久了连自己也认不全,所以重要的账他写得格外完整。
为何常见:人容易把“数据库存储”类比成复印机,忽略了编码和检索都是概率性的、有损的。
误区隐私保护就是提供删除按钮,用户点一下,把数据库记录删了就算合规。
正解删除只解决“留存”,记忆还散落在日志、备份、第三方模型调用记录里——曾经发给模型 API 的内容可能已无法撤回。真正的边界要前置:入库前脱敏、分级授权、最小化采集、保留期限自动过期。就像掌柜的规矩不在“客人问才撕纸条”,而在“不该记的当场就不落笔”。
为何常见:“删库”是能想象到的具体动作,而权限、脱敏、传播链条是看不见的,最容易忽略。
面试题
Q1. Agent 的短期工作记忆主要负责什么?
- A.跨会话保存用户的长期偏好
- B.保存当前对话最近几轮信息← 正确答案
- C.存储模型训练好的全部参数
- D.对记忆做向量化与相似度检索
答案:B。短期工作记忆对应“柜台便签”,只保存当前对话的最近几轮,用于本轮理解和应答。A 是长期持久记忆的职责,C 是模型训练产物与记忆无关,D 是检索模块的工作。
Q2. 长期持久记忆通常存放在哪里?
- A.大模型的上下文窗口里
- B.外部存储如向量数据库← 正确答案
- C.模型的梯度参数之中
- D.浏览器本地缓存数据
答案:B。长期记忆必须放在模型之外的持久化存储中,向量数据库是主流选择。上下文窗口容量有限且每轮重算,不能充当长期记忆;梯度参数是训练产物;浏览器缓存与 Agent 服务端存储无关。
Q3. 余弦相似度衡量的本质是什么?
- A.两个向量长度相差的倒数
- B.两个向量在方向上的接近度← 正确答案
- C.两个向量逐元素乘积之和
- D.文本在语料中出现的频率
答案:B。余弦相似度 = 点积除以两向量模长,本质是“方向的一致性”,与长度无关。点积只是分子不是全部,词频是词袋编码的统计量,都不构成余弦相似度的本质。
Q4. 混合检索中语义检索主要解决什么问题?
- A.精确匹配用户名字与编号
- B.找到意思相近但字面不同的记忆← 正确答案
- C.对检索结果做时间排序去重
- D.压缩向量降低存储的成本
答案:B。语义检索用向量相似度捕捉“意思差不多”的记忆,比如“想喝点暖胃的”匹配“爱普洱”;字面精确匹配交给关键词检索。时间排序和存储压缩都不是检索的职责。
Q5. 下列哪些属于常见的遗忘清理策略?
- A.用时间衰减降低旧记忆权重← 正确答案
- B.按重要度评估淘汰弱记忆← 正确答案
- C.无限扩容保留全部记录
- D.容量超限触发淘汰清理← 正确答案
答案:ABD。遗忘策略的核心是“有选择地降低或删除”,时间衰减、重要度评估、容量上限淘汰都是常见手段。无限扩容恰恰是遗忘要解决的问题,会让噪声淹没检索。
Q6. 新记忆与旧记忆冲突时,最合理的处理方式是?
- A.直接用新信息覆盖旧记录
- B.以一定权重修正并保留差异← 正确答案
- C.完全忽略新信息维持现状
- D.把新旧内容拼接成一长段
答案:B。增量更新 M' = M + γ(M_obs − M) 体现“折中”:一次异常不推翻长期规律,但也不能无视,保留差异和时间戳供后续判断。直接覆盖可能丢失重要历史,完全忽略则无法适应用户变化。
Q7. 记忆入库前做脱敏处理的核心目的是什么?
- A.压缩存储空间降低磁盘成本
- B.让检索计算速度变得更快
- C.降低敏感信息泄露的风险← 正确答案
- D.提高记忆向量的余弦相似度
答案:C。脱敏(掩码、泛化)是把"1381234"这类处理前置到入库环节,即使存储被窃取或权限越界,敏感字段已经不可读。它不压缩空间、不加速检索,也与向量相似度无关。
Q8. 新会话继承旧记忆主要依赖什么机制?
- A.把所有历史文本写入系统提示词
- B.会话开始时检索旧记忆注入上下文← 正确答案
- C.每次会话重新训练一遍模型参数
- D.对所有会话使用相同的随机种子
答案:B。跨会话继承靠“检索增强”:新会话开始时用用户的话去长期记忆库检索相关条目,作为背景注入上下文。把所有历史塞进提示词成本太高,重训练参数既不现实也非记忆机制。
Q9. 关于记忆系统的隐私边界,下列正确的是?
- A.权限满足要求才允许读取记忆← 正确答案
- B.敏感字段入库前要做掩码处理← 正确答案
- C.用户点删除数据就绝对安全
- D.记忆应设保留期限自动过期← 正确答案
答案:ABD。隐私边界 = 权限门槛 + 脱敏前置 + 保留期限,三者缺一不可。删除按钮只能清掉主库记录,日志、备份、第三方调用链中的残留无法保证绝对安全,所以 C 错。
Q10. 关于评估指标 Recall@K,下列说法正确的是?
- A.分母是所有应被召回的相关记忆数← 正确答案
- B.分子是返回结果中的相关记忆数← 正确答案
- C.K 值增大会让召回率只升不降← 正确答案
- D.K 值越大则检索的延迟越低
答案:ABC。Recall@K = 返回的前 K 条里相关数 / 全部相关数。K 增大时返回集合只会扩大不会缩小,因此召回率单调不减;但 K 越大要拉取和重排的结果越多,延迟通常更高,所以 D 错。