你是一位多模态 Agent 的“记忆管家”:它看一眼图片、听一段语音、读一句话,都能形成记忆。今天我们用一间办公室来理解它——白板贴最近的话、文件柜存重要档案、抽屉里还有一本“按意思查”的卡片索引。三样东西各有分工:白板写满就擦、档案定期归档、索引卡片帮你在海量旧纸堆里一秒找到“去年那次聊过蛋糕过敏”。短期记忆缓存当前对话,长期记忆沉淀历史经验,向量记忆让检索不再靠翻页而靠“语义相近”。三者配合,Agent 才能在长任务中不迷路、不重复、不遗漏。
掌握多模态记忆的层次与检索机制,是构建可商用的多模态 Agent(客服、助手、具身智能)的基本功。它决定了模型能否在长对话、跨会话场景中真正“用起来”。
可视化演示
展示短期、长期、向量三种记忆的写入、检索与遗忘过程,切换模式观察数据流动方向和容量变化。
本节配有交互动效(进入视口后自动加载,约 3KB)。
写入流程 输入数据进入短期记忆 重要信息巩固到长期记忆 向量化后存入向量记忆
记忆类型对比
办公室里的记忆管家有三件家具:白板、文件柜、语义索引卡片。白板写最近五分钟的对话和图片草稿,容量小、随写随擦;文件柜存归档的完整档案,容量大、但按编号逐份翻阅太慢;语义索引卡片不存原文,只存“这段话的大意指向哪份档案”,查起来最快。多模态 Agent 的记忆也由这三层组成——短期记忆(白板)、长期记忆(文件柜)、向量记忆(语义索引),三者各有应用场景,缺一不可。
用数学语言概括,记忆系统可以拆成三个子系统的并集:
日常工作中,Agent 先用白板理解“现在在聊什么”,再用语义索引快速翻出“以前有没有聊过类似内容”,最后从文件柜取出完整档案。三件家具缺任何一件,长任务都会卡壳——没有白板会失忆,没有档案会丢失经验,没有索引会翻箱倒柜找不到东西。
- 总记忆声明$M$ 表示 Agent 能调用的全部记忆内容,是所有模态(文本、图像、音频)记录的集合。
- 短期成员$M_{\text{short}}$ 是短期记忆,等价于白板,保存最近几十轮以内的多模态上下文,容量小、读写快。
- 长期成员$M_{\text{long}}$ 是长期记忆,等价于文件柜,保存跨会话的完整历史记录,容量大、读取慢。
- 向量成员$M_{\text{vector}}$ 是向量记忆,等价于语义索引卡片,存的是“可检索的向量表示”而非原文。
- 并集合成$\cup$ 表示三部分合在一起,构成完整的记忆系统,协作时按需读取。
短期记忆缓存
白板有个天然限制:写满了就得擦掉旧的再写新的。Agent 短期记忆的实现也遵循这个逻辑——用一个滑动窗口缓存最近 $k$ 步的多模态上下文,窗口的大小就是白板面积。关键约束是:短期记忆的容量上限决定了“当前上下文”能塞多少内容,一旦超出,最旧的记录被挤出。
假设第 $t$ 步收到的多模态输入是 $m_t$(可能包含图像特征、文本 token、音频片段),短期记忆缓存当前窗口内的内容:
白板机制的最大价值是让每步读取的上下文长度可控,模型不会因为无限累积历史而卡顿。代价是:如果一条关键信息发生在窗口之外,它就会被擦掉——所以 Agent 必须依赖另外两层记忆,把值得长期保留的内容先归档再遗忘。
- 时刻定义$t$ 表示当前交互的第几步,每收到一条新的多模态输入就加一。
- 缓存集合$M_{\text{short}}^{(t)}$ 表示第 $t$ 步时短期记忆中的全部内容,是一个有序集合。
- 窗口左边界$m_{t - k + 1}$ 是窗口内最旧的一条记录,窗口长度 $k$ 是设计超参。
- 窗口右边界$m_t$ 是刚到达的最新记录,位于窗口最右端,最先被模型读取。
- 遍历窗口$\{m_{t-k+1}, \ldots, m_t\}$ 表示从旧到新依次排列的连续 $k$ 条记录,构成当前上下文。
长期记忆持久化
文件柜不会把白板上的每一笔都照单全收,而是每天下班后做一次归档:把当天的对话整理成摘要、去掉重复的草稿、按主题归档。长期记忆同理,它由历史交互的精炼沉淀组成,通常由短期记忆定期压缩合并而来,而非实时逐条写入。
任一时刻的长期记忆可视为过去所有归档步骤的聚合:
其中 $\phi(\cdot)$ 表示“压缩归档”操作——摘要、去重、提炼要点。
回到办公室比喻:文件柜里不会堆满便利贴原件,而是整整齐齐的“会议纪要”。长期记忆因为做了压缩,容量增长远慢于原始数据,而且跨会话存续——Agent 重启进程后仍能读到上一轮任务的结论。长期记忆的代价是写入有延迟(压缩耗时),读取也相对慢,所以每次准备回答前,先靠向量记忆快速定位,再决定要不要动文件柜。
- 聚合符号$\bigcup_{\tau=1}^{T}$ 表示把所有归档时刻产生的条目合并成一个长期记忆集合。
- 归档时刻$\tau$ 是第 $\tau$ 次归档,可能每隔若干轮对话触发一次。
- 压缩函数$\phi(\cdot)$ 表示一个“摘要+提炼”操作,将原始多模态上下文压成精炼记录。
- 时刻上限$T$ 是到目前为止的归档总次数,对应任务的会话轮数。
向量语义检索
语义索引卡片不存原文,只存“这段话概括成的一个编号”,编号相近的卡片,内容意思也相近。向量记忆就是这套“按意思索引”的机制:把文本、图像、音频分别编码成高维向量,检索时计算查询向量与候选向量之间的相似度,取最相似的前若干条。
常用的相似度指标是余弦相似度,衡量两个向量的方向是否一致:
检索时,Agent 把文本查询和图像查询融合成一个向量,然后在向量库里做最近邻搜索,一次性查出“去年聊过芒果过敏”“上个月客人不吃坚果”等记忆。之所以多模态检索能跨格式命中,是因为不同模态的样本被编码进同一个语义空间——“蛋糕的图片”和“蛋糕的文字”在空间里离得很近。这层记忆只看语义、不看原文,所以速度快到毫秒级,代价是需要额外的嵌入计算和向量索引维护。
- 查询向量$\mathbf{q}$ 是当前任务的查询向量,例如用户发来的一张“芒果蛋糕”图片编码成的嵌入向量。
- 候选向量$\mathbf{v}$ 是记忆库中的某一条候选记录向量,可能是某段历史文本、某张旧图片的编码。
- 点积运算$\mathbf{q} \cdot \mathbf{v}$ 是两向量逐维相乘后求和,衡量投影对齐程度。
- 模长归一$\|\mathbf{q}\|$ 与 $\|\mathbf{v}\|$ 分别是两个向量的长度,用来消除向量绝对大小对相似度的影响。
协同管理机制
记忆管家从来不会只用一件家具:新任务来了,先看白板上的“进行中”,再翻语义索引找“相关旧案”,必要时从文件柜取完整档案,最后把结论写回白板。Agent 的协同管理就是这条工作流——每次决策前,将短期、长期、向量三层记忆的检索结果拼接成一个完整的上下文包:
执行完成后,Agent 把新产生的多模态内容写回白板;当白板接近容量上限,定时器触发归档——内容被压缩进文件柜,同时为新内容生成向量索引卡片。协同的要点是“读多路、写一路、定期归档”:决策时从三层都取数据,执行后只写短期记忆,由后台进程决定何时凝固为长期记忆。
- 拼接符号$\oplus$ 表示把多段内容拼接成一个超长上下文,拼接顺序通常为“当前窗口→相关档案→完整记录”。
- 当前窗口$M_{\text{short}}^{(t)}$ 提供最近几轮对话的完整内容,保证连贯性。
- 语义召回$\text{TopK}\bigl(\text{sim}(q, M_{\text{vector}})\bigr)$ 表示用查询 $q$ 在向量记忆里做相似度检索,取最相似的前 $K$ 条。
- 完整档案$M_{\text{long}}\bigl[\text{topK\_ids}\bigr]$ 表示根据检索返回的编号,从长期记忆里取出对应的完整原文档案。
遗忘与优化
文件柜再大也有填满的一天,白板更是写着写着就必须擦。遗忘不是 bug,而是容量约束下的主动设计。Agent 对每条记忆维护一个保留分数,按“最近用没用过”和“重不重要”加权,得分过低的记录先被遗忘:
优化体现在两个层面:检索效率方面,向量索引让召回从全量扫描变成近似最近邻(ANN)搜索,复杂度从 $O(N)$ 降到 $O(\log N)$;存储效率方面,定期压缩、低分清除,让有限容量始终留给高价值记忆。白板、文件柜、索引卡片三件家具在遗忘策略的调度下,才能长期稳定运转——记住该记住的,忘掉该忘掉的,Agent 才能在无尽的长任务中既高效又知轻重。
- 记忆条目$m$ 表示一条待评估的记忆,可以是短期窗口内的一条,也可以是长期档案中的一条。
- 保留得分$\text{score}(m)$ 是这条记忆的综合保留分数,越高越优先保留。
- 近因权重$\alpha \cdot \text{recency}(m)$ 中,$\text{recency}$ 用最近一次被访问的时间计算,越久未用得分越低。
- 重要度权重$\beta \cdot \text{importance}(m)$ 中,$\text{importance}$ 表示这条记忆对任务目标的关键程度,例如用户偏好、安全约束得高分。
AI 应用场景
多模态客服:跨会话的用户偏好记忆
阶段:多模态 Agent 的上下文管理与记忆检索
解决的问题:传统客服机器人在每通新对话中都“从零开始”,用户上次说过“对芒果过敏”、发过产品外包装照片,下次提问时全部丢失。AI 通过三层记忆协同,可将历史偏好自动带入新会话。
具体案例:用户再次发来一张蛋糕照片询问“这个能不能吃”时,Agent 先将图片编码为查询向量,在向量记忆中检索到“芒果过敏”的历史记录,再从长期记忆取回当时完整对话摘要,短期记忆则缓存当前轮次的所有消息。答案不再是泛泛解释,而是直接给出“这款含芒果,建议避开”。整个召回过程在数百毫秒内完成。
长视频审阅助手:跨片段关联检索
阶段:长程多模态任务中的记忆调度
解决的问题:审阅 2 小时视频时需要反复比对不同时间段的画面和台词,人工回拖效率极低。传统方法无法将“画面内容”与“历史评论”做语义关联。
具体案例:审阅助手每分钟自动生成一个“视觉+文本”摘要,存入长期记忆并建立向量索引。当用户提问“找出所有出现过红色警示牌的片段”,Agent 用文本查询向量召回全部相关片段编号,逐段取出图像缩略图拼接成时间轴结果。因为向量检索只比较语义,画面中并没有“警示牌”文字也能被正确召回。
Python 演示
import numpy as np class VectorMemory: def __init__(self, dim=128): self.dim = dim self.entries = [] self.vectors = [] def add(self, text, vec): self.entries.append(text) self.vectors.append(vec) def search(self, query_vec, top_k=2): scores = [] for v in self.vectors: dot = float(np.dot(query_vec, v)) norm = float(np.linalg.norm(query_vec) * np.linalg.norm(v)) scores.append(dot / norm if norm > 0 else 0.0) top_idx = np.argsort(scores)[::-1][:top_k] return [self.entries[i] for i in top_idx] class ShortTermBuffer: def __init__(self, capacity=4): self.capacity = capacity self.items = [] def push(self, item): self.items.append(item) if len(self.items) > self.capacity: self.items.pop(0) def get_all(self): return list(self.items) class LongTermArchive: def __init__(self): self.records = [] def consolidate(self, short_list): summary = "|".join(short_list) if summary not in self.records: self.records.append(summary) def fetch_by_ids(self, ids): return [self.records[i] for i in ids if i < len(self.records)] def build_query_vec(text_len=128, seed=42): rng = np.random.default_rng(seed) vec = rng.random(text_len).astype(np.float32) vec = vec / np.linalg.norm(vec) return vec def main(): vm = VectorMemory(dim=128) st = ShortTermBuffer(capacity=4) lt = LongTermArchive() vm.add("user prefers lactose-free milk", build_query_vec(seed=1)) vm.add("user allergic to mango", build_query_vec(seed=2)) vm.add("user asks about cake photo", build_query_vec(seed=3)) st.push("user sent cake image") st.push("assistant asks about ingredients") query = build_query_vec(seed=2) hits = vm.search(query, top_k=2) print("vector recall:", hits) lt.consolidate(st.get_all()) ids = [0, 1] print("long-term archive:", lt.fetch_by_ids(ids)) st.push("assistant mentions mango risk") print("short-term buffer:", st.get_all()) if __name__ == "__main__": main()
class VectorMemory向量记忆类:以语义检索为目标,用余弦相似度实现 TopK 召回。self.entries` 与 `self.vectors分别保存原文和嵌入向量,查询时逐条比对。scores.append(dot / norm ...)计算余弦相似度,把文本差异转化为数值距离。top_idx = np.argsort(scores)[::-1][:top_k]按相似度降序取前 K 个结果。class ShortTermBuffer短期记忆白板:容量为 4,超容量时弹出最旧记录。self.items.pop(0)先入先出策略,模拟白板写满擦除旧内容。class LongTermArchive长期记忆档案:只保存压缩后的摘要记录。lt.consolidate(...)把短期窗口内容合并进入长期档案,去重后追加。vm.search(query, top_k=2)执行一次“按意思检索”,返回最相近的历史记录。
常见误区
误区初学时常以为模型的 context window 就是短期记忆,上下文窗口多大,短期记忆就多大,两者完全等价。
正解上下文窗口是“模型一次能看到的输入长度上限”,它由模型结构决定;短期记忆是“Agent 为维持对话连贯而主动缓存的内容集合”,它由调度策略决定,通常远小于窗口上限。白板面积可以小于房间面积,短期记忆往往只占上下文窗口的一部分,余量要留给检索到的档案和系统提示词。
为何常见:两者的作用范围重叠——都管“当前对话”——但一个是被动结构、一个是主动策略,混为一谈后容易误解上下文窗口可以无限扩容。
误区认为向量检索就是更快的全文搜索,关键词不出现就召回不到结果。
正解向量检索做的是语义匹配,“蛋糕图片”和“甜品照片”即使没有共同词也能因为语义相近而命中。但它对精确 ID、型号、编号这类需要精确匹配的内容反而容易误召回——所以生产系统常用“向量召回+关键词精排”双通道。语义索引卡片负责找大意,精确编号还得靠原文索引。
为何常见:习惯了搜索引擎的倒排索引思维,容易低估向量对“同义不同词”的泛化能力,又高估它对精确匹配的可靠性。
误区认为 Agent 应该把所有交互记录都存进长期记忆,反正存储便宜,存得越全回答越准。
正解存储不是零成本的——检索时,无关记忆会稀释上下文质量,还会拖慢召回速度。长期记忆是“压缩归档”而非“备份硬盘”,必须做摘要、去重、遗忘。文件柜塞满废纸后,找到真正重要的那份反而更慢;主动遗忘才能保证长期记忆“少而精”。
为何常见:“记住一切”听起来更安全,但忽略了检索噪声和容量衰减对性能的隐性伤害。
误区把向量维度类比成硬盘容量,以为嵌入维度从 128 升到 1024 就能记住更多东西。
正解向量维度决定“表示精度”,不决定“存储条目数”;记忆容量取决于向量的条数,而非每个向量的长度。维度太高反而带来高维稀疏、检索退化的问题,实践中还要结合分块(chunking)和索引结构来提升召回质量。卡片索引的“编号位数”决定卡片能表达多细,但卡片柜子的容量由卡片张数决定。
为何常见:维度越高=信息越多,是直觉上容易形成的错误等价关系。
误区认为只要把所有模态数据丢进一个数据库,Agent 就能自动跨模态检索。
正解真正的多模态记忆依赖“统一嵌入空间”:图片、音频、文本经过各自编码器映射到同一向量空间,检索才能跨模态进行。同一个库里如果向量不在同一空间,余弦相似度毫无意义——就像中文卡片和英文卡片放在同一个抽屉,按中文意思翻不出英文内容。统一空间是前提,统一存储只是表象。
为何常见:数据库层面的“放在一起”很直观,但嵌入空间的一致性才是跨模态检索的关键。
面试题
Q1. 多模态 Agent 的三种记忆分别对应办公室里的哪些物品?
- A.白板、文件柜、语义索引← 正确答案
- B.纸张、黑板、便签条
- C.硬盘、内存、显卡
- D.缓存、镜像、日志
答案:A。白板对应短期记忆,随写随擦;文件柜对应长期记忆,归档完整档案;语义索引对应向量记忆,按意思快速定位。B 的三个物品之间没有“读速/容量/语义”的分工关系,C 是硬件概念,D 是工程概念,均不贴合记忆分层设计。
Q2. 短期记忆的滑动窗口满了之后,最可能发生什么?
- A.窗口自动扩大以容纳新内容
- B.最旧的记录被移出窗口← 正确答案
- C.所有记录全部清空
- D.模型停止接收新输入
答案:B。滑动窗口采用先入先出策略,新元素到来时最旧元素被挤出,保持窗口长度恒定。A 会破坏上下文长度可控的优点,C 会造成记忆断层,D 不符合在线交互场景——白板写满时总是擦旧写新,而不是停笔等人。
Q3. 使用余弦相似度做向量检索时,两个向量的相似度越接近 1 说明什么?
- A.两个向量长度完全相等
- B.两个向量方向越一致,语义越接近← 正确答案
- C.两个向量都来自同一模态
- D.两个向量的所有维度数值相同
答案:B。余弦相似度只关注方向夹角,不关心模长。夹角越小越接近 1,表示语义方向一致;A 是模长概念,C 与相似度无关,D 是向量相等的极端情况,都误解了余弦相似度的几何含义。
Q4. 关于长期记忆的“压缩归档”,下列说法正确的是?
- A.把短期记忆内容原样复制到长期存储中
- B.定期对白板内容做摘要、去重、提炼后再存入← 正确答案
- C.每次对话结束必须立即归档所有消息
- D.只有图像数据需要归档,文本不需要
答案:B。长期记忆通过 $\phi(\cdot)$ 压缩函数处理短期内容,生成的是精炼记录而非原始拷贝。A 会造成存储爆炸,C 的“每次立即归档”成本过高,通常按固定间隔触发,D 错误——文本和图像都是多模态记忆的归档对象。
Q5. 为什么向量检索能实现“跨模态”命中?
- A.因为不同模态内容被压缩得很小
- B.因为不同模态被编码进同一个语义向量空间← 正确答案
- C.因为数据库统一存储了所有格式的文件
- D.因为模型会先做 OCR 识别再做关键词匹配
答案:B。文本、图像、音频经过各自的编码器映射到同一个嵌入空间,语义相近的内容在空间中相邻,因此文字查询可以命中图片记忆。A 与语义空间无关,C 只是存储层面的统一,D 是关键词方案的变体,都无法实现真正的跨模态语义召回。
Q6. 在协同管理机制中,Agent 为生成下一次回答需要做哪些记忆操作?
- A.从短期记忆读取当前窗口上下文← 正确答案
- B.用查询向量在向量记忆中做 TopK 召回← 正确答案
- C.将全量历史记忆直接拼接进输入
- D.从长期记忆取出检索到的完整档案← 正确答案
答案:ABD。协同管理公式显示,上下文由短期记忆窗口、向量召回结果、长期完整档案三部分拼接而成。实践中不会把全量历史直接拼接,因为上下文长度有限、无关记忆会引入噪声,所以 C 错误,A、B、D 正是三路读取的标准操作。
Q7. 假设短期记忆缓存了最近 8 轮对话,而用户在第 2 轮提到“对花生过敏”,第 9 轮发来一盒含花生的点心图片。Agent 最可能在哪里找回这条关键信息?
- A.短期记忆仍缓存第 2 轮,直接读取
- B.短期记忆已溢出,只能靠向量记忆语义召回← 正确答案
- C.模型参数中已隐式记住,无需检索
- D.只能重新追问用户是否有过敏史
答案:B。窗口长度 8,第 2 轮信息已在第 10 轮到来前被挤出。向量记忆存有“花生过敏”的语义向量,点心图片查询可以在嵌入空间命中它,再从长期记忆取回完整记录。A 与窗口先入先出矛盾,C 错误——模型参数不存储用户私有记忆,D 忽略了向量检索的能力。
Q8. 关于遗忘策略中“保留得分”的组成,下列表述正确的有哪些?
- A.得分只由记忆被访问的次数决定,与内容无关
- B.近因权重衡量多久没使用这条记忆,越久未用得分越低← 正确答案
- C.重要度权重由用户偏好、安全约束等任务目标决定← 正确答案
- D.低分记忆应在容量紧张时被优先清理← 正确答案
答案:BCD。保留得分 = 近因 × 重要度,近因看“多久没用过”,重要度看“对任务多关键”,低分先清是遗忘策略的落点。A 忽略了重要度维度,因此错误——只说访问次数不够,还必须结合记忆内容的价值判断。
Q9. 向量检索中 TopK 召回的含义是?
- A.返回相似度最高的 K 条记忆← 正确答案
- B.返回时间最近的 K 条记忆
- C.返回长度最长的 K 条记忆
- D.返回随机抽取的 K 条记忆
答案:A。TopK 指按相似度降序排序后取前 K 条,目的是用最相关的少量记忆支撑决策。B 是时间排序,C 是长度排序,D 是随机采样,三者都与“语义最相关”的目标无关。
Q10. 生产环境中为什么通常采用“向量召回 + 关键字精排”的混合方案?
- A.因为向量检索速度太慢需要关键字加速
- B.因为向量负责语义泛化,关键字负责精确匹配,互补短板← 正确答案
- C.因为关键字无法匹配图片内容,只能靠向量找图
- D.因为数据库只支持一种检索方式
答案:B。向量检索擅长同义改写、跨模态泛化,但对型号、编号等精确信息易误召回;关键字检索精确但无法处理语义变体。两者结合才能同时满足“找得准”和“找得全”。A 说反了,向量检索通常更快更慢取决于索引;C 是关键词的固有缺点而非混合方案的动机;D 与事实不符。