最近在调一个带记忆的智能体,被“上下文丢失”折磨得够呛。明明模型窗口已经给到非常充足,可聊着聊着,它还是忘了用户最开始提到的关键约束。后来我把市面上的 Agent Memory 方案整体捋了一遍,发现大家在讨论的 MATTS、CFGM、MIRIX 并不是三个可以随便替换的“插件”,而是三种完全不同的记忆组织思路。这篇正好可以作为《解密prompt系列》里专门讲 Agent Memory 的部分,把它们的定位、底层逻辑、和提示词工程的配合方式一次性说清楚。
如果你是做大模型应用、智能体开发,或者只是好奇为什么 prompt 写了一大堆,模型还是“记性差”,这篇都值得往下看。我会先讲清楚 Agent Memory 为什么是现在提示词工程绕不开的话题,然后把 MATTS、CFGM、MIRIX 逐个拆开,再给一份可以直接参考的选型和排坑经验。
1. Agent Memory 是什么,为什么必须搞懂它
1.1 上下文是智能体的“工作台”,记忆是“仓库”
做 Agent 应用的人都有过这种体验:前几分钟模型还清楚地知道用户的预算、项目周期、团队偏好,等中间穿插了几轮无关话题,它就突然“失忆”了。你翻日志发现上下文没有被截断,该传的信息也都传了,但模型就是抓不住重点。
原因其实不难理解。上下文窗口再大,也只是模型面前的“工作台”,工作台上面能摆下的材料有限。对于一个持续运行几小时、甚至跨几天的 Agent 来说,真正需要依赖的是工作台之外的“仓库”——也就是 Agent Memory。仓库负责把工作台上放不下的历史对话、用户画像、业务规则、临时结论都存下来,等需要时再把相关部分搬回工作台。
这里的难点在于:搬回哪些、按什么顺序搬、以什么形式搬,都会直接影响模型输出质量。过去我们学的提示词工程,重心在“怎么把指令写清楚”;但带记忆的 Agent 出现之后,重心正在变成“怎么把上下文拼得又精又准”。这也是为什么我现在越来越愿意把这块叫做“上下文工程”,而不是传统的提示词工程。
1.2 三种记忆形态:时序流、图谱、索引库
把市面上的 Agent Memory 方案翻过一圈后,会发现绝大多数实现逃不开三种基本形态。
第一种是时序流形态,代表是 MATTS。它把对话和行为按时间戳记录成一条流,构造上下文时不是把原始列表整段塞进去,而是按时间切片、合并、压缩后再重新拼接。它解决的问题是“顺序和时效”:模型需要知道哪个信息是最新的,哪个已经过期,哪个应该优先采信。
第二种是关联图形态,代表是 CFGM。它不只把记忆当成独立文本,还会记录条目之间的条件关系,形成一个有依赖、有互斥的图谱。回答问题时先命中相关节点,再把相关节点按条件关系融合成连续材料。它解决的问题是“关联和因果”:不少上下文问题不是缺信息,而是信息之间怎么串联、怎么推导。
第三种是索引库形态,代表是 MIRIX。它把记忆存储和使用解耦,所有记忆先落到带索引的外部库,生成前通过检索把最相关的内容捞出来,可能走向量检索、关键词检索、甚至多路召回。它解决的问题是“海量记忆中的精确命中”。
这三种形态并不互斥,成熟 Agent 往往是混合使用。但混用之前,得先理解每一种的脾气,否则很容易做成四不像。
1.3 提示词工程的新战场:从“写指令”到“拼上下文”
以前学 prompt engineering,大家关注焦点无非是角色设定、输出格式、上下文演示。但开始做带记忆的 Agent 后,我发现日常工作重心已经完全变了:同一套 prompt 模板,这轮要填 A 用户的名字,下轮要填 B 用户的订单信息,再下轮还要带上项目背景文档里的某段内容。
模板本身往往是稳定的,变的是动态插入的记忆内容。难点在于,模板里的占位符越多,记忆内容越关键,模型也越容易因为记忆格式混乱而误读指令。比如你把编程规范、用户偏好、历史报错全部塞在一个 section 里,模型很可能分不清哪句是约束、哪句是废话。所以做 Agent Memory,本质上是在给 prompt 做“预清洗和预组织”:先把记忆变成合格的材料,再喂给模型。
理解了这一点,后面看 MATTS、CFGM、MIRIX 的差别就会很顺。它们其实都在回答同一个问题:记忆材料应该以什么形式组织,才能在拼进 prompt 时不超窗口、又不丢重点。
2. MATTS:把记忆做成带时间戳的流
2.1 从“堆对话”到“拉时间线”
MATTS 我习惯理解为 Memory-Augmented Time-series Transformer State,也就是带时间语义的流式记忆。它的核心想法一句话就能讲完:记忆不应该是一堆无序文本,而应该是一条带时间戳的时间线。
为什么要强调时间戳?因为大模型本身对“时间”没有天然感知。给它一段对话历史,它可以复述内容,却不知道哪句话是五分钟前说的、哪句话是上周说的。很多 Agent 答错问题,不是没记住内容,而是把过时信息和最新信息当成同等权重处理。比如用户上周说“月底前预算 1 万”,这周说“预算升级到 2 万”,如果两条记录看起来都在上下文里,模型很容易一脸困惑,不知道到底该听哪个。
MATTS 的做法是给每条记忆打上时间标记,并维护一个紧凑的状态向量。时间标记负责排序和时效判断,状态向量负责压缩与合并。这样生成 prompt 时,同一类信息只会保留最新有效版本,而不是把所有历史版本一股脑堆上去。
2.2 四个环节逐个拆:记录、分组、合并、切片
实现 MATTS 风格记忆,通常分四步走。
第一步是记录。每轮对话结束后,把用户消息、系统行为、关键输出结果转成一条记忆项,附上时间戳和类型标签。这里的关键是记什么:不是把整轮对话都存,而是只存“会影响后续决策”的信息,例如用户偏好、项目状态、明确承诺。
第二步是分组。按类型把记忆分开,比如用户偏好、项目状态、临时任务。分组可以简单用 tag 实现,也可以建一个轻量结构体。分组的价值在于,后面做合并和切片时有明确边界,不会把不同类型的信息混在一起污染上下文。
第三步是合并。对同一类型内的记忆做去重和状态更新,过期的标记为失效,发生冲突的以后写入者为准。这一步决定了记忆素材是否干净,是整个 MATTS 最关键的地方。如果跳过合并,只做记录和切片,那其实还是“假记忆”:确实存下来了,但内容越来越臃肿,模型反而被噪声干扰。
第四步是切片。构造 prompt 时,按时间窗口取切片。比如近一小时内的记忆完整保留,最近七天内的做摘要保留,更早的只保留状态向量。切片的目的很直接:把有限的上下文空间留给真正重要的近期内容。
一个简化示例:
{ "memory_id": "mem_00123", "timestamp": "2025-06-10T14:22:01+08:00", "type": "user_preference", "content": "用户要求所有输出保持简洁,不要分段过多", "state": "active" }实际项目中,我会再加一个expire_at字段,用来做软过期,避免手动清理。这么做的好处是,检索和切片时可以很方便地在 SQL 或内存过滤器里加条件。
2.3 模板里必须写清楚“时效优先级”
MATTS 和提示词层配合时,有一个很容易被忽略的点:你以为只要在数据里带上了时间字段,模型就会自动按时间理解,其实并不会。数据结构想让模型“看见”,还得在 prompt 里翻译成明确规则。
我习惯在动态生成的记忆区之前加一段时效说明:
以下记忆按时间从新到旧排列。用户最近一次反馈优先于早期反馈。 如果早期信息与最新信息冲突,以最新信息为准。这段说明看起来不起眼,但它把数据层的时间偏好翻译成了模型能直接执行的规则。少了这一段,模型只能靠“猜”哪条重要;加上这一段,记忆的时间性才真正在推理层面生效。我踩过的坑是:只把记忆按时间排序,不在 prompt 里声明优先级,结果模型仍然在两个旧条目之间反复摇摆,甚至采信了过时信息。所以按时间排序只是准备工作,显式声明优先级才是落地的关键一步。
2.4 适合场景与边界
MATTS 适合对话型和任务延续型场景。客服机器人、智能助手、长时间运行的项目管理 Agent 都适用,因为这些场景的核心诉求是“别忘事、别用错版本”,刚好是时间线的强项。
它的局限也很明显:对跨事件的因果理解比较弱。时间线能告诉你哪个先发生、哪个后发生,但很难回答“为什么 A 发生了会导致 B”。如果 Agent 需要做复杂推理,牵涉到多段记忆之间的因果关系,光靠时序流就不够用了。这时候就需要下面要说的 CFGM。
3. CFGM:把记忆做成条件关联图
3.1 从“相似”到“有条件地相关”
CFGM 我理解为 Conditional Fusion Graph Model,条件融合图模型。它和 MATTS 最大的区别在于:MATTS 关心“什么时候发生”,CFGM 关心“什么跟什么有关、在什么条件下有关”。
普通做法里,我们检索记忆通常只看相似度:和当前问题最像的几条历史记录拉出来。但问题往往不是“像不像”,而是“成不成立”。举个例子:用户说“我明天要用会议室”,记忆库里有三条记录,分别是“会议室 A 可容纳 10 人”“会议室 A 正在装修”“用户之前提到过会议室太吵影响状态”。单看每一条,它们都和当前问题相关,但放到一起,关系是有条件的:会议室 A 可用的前提是装修结束,而用户对噪音敏感,所以 A 并不是好选择。
单独做相似度检索,很难把这种条件关系组织起来。CFGM 要解决的就是“相关性背后的成立条件”问题,它在 prompt 拼装之前,先对记忆做一层条件推理。
3.2 节点检索、条件推理、融合生成
CFGM 的实现思路可以拆成三步。
第一步是节点检索。从记忆库中把与当前问题有直接或间接关联的记忆条目都捞出来。这一步和常见检索很像,可以先宽后窄,宁可多召回几个候选,也不要漏掉关键节点。
第二步是条件推理。这一步是核心,也是难点。需要分析候选条目之间的关系类型,比如依赖、互斥、前置、冲突、优先级。再把这些关系整理成一条推演链或条件集。比如上面开会室的例子,推理结果是“会议室 A 存在噪音风险,且当前处于装修中,因此不推荐”。这一步如果做得好,模型拿到的就不是散点式记忆,而是一个已经拼好的结论骨架。
第三步是融合生成。把关系整理成一段连贯的上下文材料,连同当前问题一起交给模型。此时 prompt 里出现的不是三条割裂记录,而是带有逻辑关系的组织化摘要,模型可以在这个基础上继续推理或直接输出。
3.3 把“关系摘要”写进 prompt,而不是丢原文
CFGM 给 prompt 带来的最大改变是:你不只是塞记忆,而是会塞入一段关系摘要。同样是塞三条会议室记录,普通做法是把原文罗列上去;CFGM 做法会先给模型一段关系摘要,再放原文。
上下文说明: - 会议室A与用户状态之间存在噪音条件关系,用户曾明确表示噪音干扰严重。 - 会议室A当前状态为装修中,可用性依赖于装修完成。 - 因此,会议室A暂不具备推荐条件。 原始记忆片段: (这里放相关记忆原文)在 prompt 里放这段“上下文说明”,实际上是让模型站在一个已经整理好的推演结果上继续作答,而不是自己从原始记录里找关系。这样做的好处是节省 Token 推理成本,也显著降低模型在长上下文中折返阅读的概率。我实际对比过同一批记忆内容,带关系摘要的回答一致性明显好于只塞原文。
3.4 关系图谱常见问题:判定过脆、图谱膨胀、人工成本
CFGM 型方案在使用中常遇到三类问题。
第一类,关系判定过脆。模型把“用户曾表示噪音干扰严重”过度泛化成“用户永远讨厌这间会议室”,导致后面用户改了偏好,系统还是按旧关系输出。解决办法是给关系加上时间和条件限定,比如标注“近期一次具体反馈”,而不是无期限的全局结论。
第二类,图谱膨胀。节点和边会随着记忆累积越变越多,检索速度变慢,prompt 中的关系摘要也越来越长。个人经验是定期做图谱剪枝:超过有效期、且没有继续支撑当下业务的关系尽早删除,只保留核心节点和活跃关系。
第三类,维护成本。图结构如果全部依赖人工维护,成本很高;如果完全交给模型自动抽取,又容易引入幻觉。折中方案是人工定义关系类型,模型自动抽取关系实例。这样既能把关系种类控制在稳定范围,又能让数量规模化。
4. MIRIX:把记忆做成可检索的外部索引库
4.1 不要把宝全押在模型脑子里
MIRIX 我倾向理解为 Memory Indexed Retrieval with Information eXchange,也就是以“记忆索引+多源检索+经验回放”为特征的外部索引库形态。它的核心立场很直接:不要试图把所有记忆塞进上下文,也不要指望模型自己记住,把记忆放到外部存储,每次生成时按需检索。
这个概念落地时其实已经有很多成熟组件,比如向量数据库、BM25、倒排索引、混合检索。MIRIX 的特点是把这些统一成一个对外服务接口,让 Agent 只通过检索 API 拿记忆,不自己管理存储细节。它和 RAG 的边界有点模糊,但我的理解是:RAG 更偏“用外部知识增强回答”,MIRIX 偏“用历史经验调整行为”,数据源不只有文档,还包括 Agent 自己过去的行为记录和结果反馈。
4.2 五段链路:写入、向量化、多路召回、重排、组装
一套 MIRIX 风格记忆系统,至少包含五个环节。
第一,写入链路。每轮对话结束后,把关键信息切片、清洗、去重后写入索引库。写入要做两件事:一条记忆原文,一条结构化元数据,比如用户 ID、会话 ID、内容类型、时间戳。
第二,向量化。为每条记忆生成 embedding,同时保留原文和元数据字段。这里要注意 embedding 模型的选择,领域差别比较大的时候,通用向量模型可能匹配不准。
第三,多路召回。查询时并行跑向量检索、关键词检索和规则召回。比如按用户 ID、任务 ID 做精确过滤,把候选范围大幅缩小,再在候选里排序。这比单靠向量检索稳定得多。
第四,重排。把多路结果合并去重后,用轻量级排序模型或规则按相关度、时间新鲜度、权限范围重排。重排规则不能只看相似度,时间新鲜度很关键。
第五,组装。取 topN 条记忆按模板拼进 prompt。组装时要注意 prompt 模板中的容量上限,避免塞入过多记忆导致上下文过载。
下面是一个伪代码示例,用来展示 MIRIX 式结构:
def build_memory_context(query, user_id, top_k=5): candidates = [] candidates += vector_search(query, filters={"user_id": user_id}, limit=20) candidates += keyword_search(query, filters={"user_id": user_id}, limit=20) candidates += rule_recall(user_id=user_id, recent_days=7, limit=10) deduped = dedup_by_memory_id(candidates) ranked = rerank_by_score_and_freshness(deduped) selected = ranked[:top_k] blocks = [ f"[记忆#{m.memory_id}:{m.content}]" for m in selected ] context = "\n".join(blocks) return context4.3 用“引用型格式”把记忆放进模板
MIRIX 在 prompt 层整合时,有一个很实际的建议:把检索到的记忆用引用型格式放进上下文,并保留来源标签。例如:
参考素材: [记忆#101:用户偏好 - 喜欢简洁回复] [记忆#204:项目状态 - 前端联调已完成] 注意:以上素材为历史记录,若与当前对话冲突,以当前对话为准。给记忆编号不是为了装样子,而是为了让模型在生成时能够指向具体来源,也方便排查是哪条记忆误导了输出。保留“冲突时以当前对话为准”这句话则是为了防止模型被过期记忆带偏,这一点在 MIRIX 里格外重要,因为外部检索到的历史记忆很可能和当前语境有冲突。
4.4 实测体会:检索质量决定 Agent 质量
在实际项目里用 MIRIX 风格做过一轮之后,我最想强调的体会是:模型再聪明,喂给它的记忆不对,输出也不可能对。与其反复调 prompt,不如先花时间调检索。有个项目里我们把 topK 从 5 调到 10,上下文变长,回答反而变差,因为带进了更多语义相似但实际无用的记忆。后来改成多路召回加规则过滤,精确率明显提升。
延迟成本也要提前盘算。每次回答前都检索,会多出几十毫秒到几百毫秒的开销。对交互型 Agent 来说,这个延迟会直接影响体验。优化方向包括把高频记忆做成本地缓存、批量检索、以及把部分检索步骤和对话生成并行。如果只求效果不管延迟,上线后一定会被慢响应拖后腿。
5. MATTS、CFGM、MIRIX:横向对比与选型参考
5.1 一张表看懂三个框架的真实差异
把三个选项放在一起,可以从组织单位、记忆关系、上下文组织方式、典型缺陷和适用场景五个维度做对比。
| 对比维度 | MATTS | CFGM | MIRIX |
|---|---|---|---|
| 组织单位 | 带时间戳的记忆流 | 记忆节点与关系边 | 外部索引库中的记忆条目 |
| 记忆关系 | 时效、顺序、版本 | 依赖、冲突、前置、互斥 | 相似度、关键词、精确属性 |
| 上下文组织方式 | 按时间切片和合并 | 关系摘要加原文 | 多路召回加重排,引用格式 |
| 典型缺陷 | 缺乏跨事件因果理解 | 关系维护成本高、易膨胀 | 检索质量依赖索引和向量化 |
| 最适配场景 | 对话延续、状态维护 | 复杂决策、多条件判断 | 海量记忆、按需精确获取 |
| 实现成本 | 低 | 中高 | 中,但组件生态成熟 |
这张表不是要给三个框架排名次,而是帮你快速对号入座。如果你做一个高频多轮对话的客服机器人,MATTS 最省心;如果你做重度决策系统,需要把很多条件综合判断,CFGM 会更对口;如果你的记忆量已经大到人工整理不过来,MIRIX 是必然方向。
5.2 选型时先问三个问题
问第一个问题:记忆量有多大。几百条以内,MATTS 足够;上万条甚至更多,尽早考虑 MIRIX。索引库的规模优势,只有在记忆量真的滚起来之后才明显。记忆量小时强行上外部索引,反而增加服务复杂度和延迟。
问第二个问题:决策复杂度有多高。客服回答退货政策,直接检索就行,用 MIRIX 简单高效;但如果是排产、调度、风险判断这类多因素交叉的决策,CFGM 的关系建模就值得投入。关系建模前期可能费时间,但在复杂决策上的收益也是看得见的。
问第三个问题:团队能承受多少维护成本。MATTS 实现最简单,逻辑清晰,适合小团队快跑;MIRIX 实现最复杂,但成熟组件多,核心工作集中在链路调参;CFGM 在维护图谱和关系规则方面成本最重,需要长期投入。选择不是越高级越好,而是越匹配资源越好。
5.3 三种方式怎么组合才能不打架
实际项目里,三个框架不是非要选一个。比较常见的组合是 MIRIX 做底层仓库,MATTS 做会话内的短期记忆管理,CFGM 负责对关键决策场景做关系推理。
组合使用最大的问题是职责边界不清。同一个记忆片段,短期流里有一份,索引库里还有一份,图谱里又存了一个节点,更新时容易出现不一致。我的建议是在组合前先定一个事实源:每个信息字段只有一份权威存储,其他位置存引用或缓存。这样能避免大量“记忆打架”。另外,记忆更新要做成消息驱动,写入事实源后广播更新,而不是三个地方各自为政。
6. 实操踩坑与排查实录
6.1 高频翻车现场,先说五个坑
第一个坑是上下文被塞爆。无论是哪种记忆方案,只要没有合并和裁剪,prompt 迟早会越来越长。最开始我天真地以为窗口大就无所谓,直到发现模型在长上下文里对中段信息极不敏感,才意识到上下文窗口标称长度和有效注意力根本不是一个概念。
第二个坑是记忆注入导致提示词污染。从记忆库里拿到的文本如果没清洗,可能带上特殊符号、代码片段甚至异常文本,直接拼进 prompt 会干扰指令执行。处理办法是:进入 prompt 前做一轮格式化和字段过滤,记忆原文不能无脑拼。
第三个坑是记忆更新不同步。用户中途改了口径,但旧记忆和新记忆都还留在库里,检索出来还是旧版本。这个问题在 MATTS 和 MIRIX 里都很常见,解决方式并不高深:写库时做版本替换,检索时做时效加权,prompt 中声明冲突以当前内容为准。
第四个坑是 topK 迷信。总以为多塞几条记忆更稳,结果相似但无关的内容大量进入上下文,稀释了真正关键的信息。这个坑我在 MIRIX 上踩过,后来靠重排规则和多路召回解决。
第五个坑是只测单轮不测长程。单独一两个问题验证时效果很好,连续跑 20 轮对话后,记忆模块就开始劣化。所以做 Agent Memory 测试,一定要做长会话压测,观察记忆累积时的变化。
6.2 调试记忆系统前,先做三件事
想少踩坑,我建议在正式调试前先做三件事。
第一件,把记忆系统单独抽出做测试。不要让 Agent 整体链路包着记忆系统,否则出了问题,你分不清是模型逻辑问题还是记忆检索问题。单独给记忆系统喂固定查询,看返回结果是否稳定可用。
第二件,给记忆打全集标注。找一批代表性用户会话,把应该被记住的关键信息人工标出来,再对照系统实际记住的内容算命中率。没有这个标注集,你只能靠感觉判断记忆对不对,很容易被偶发的良好效果误导。
第三件,固定一个基线 prompt。调试记忆方案时,先固定一套模板,只改记忆组织方式,观察效果变化。要是你同时改模板、改检索、改模型参数,出了问题完全无法定位。
6.3 记忆类排查清单:现象、原因、顺序
下面是我自己排查记忆问题时常用的速查表。
| 现象 | 可能原因 | 排查顺序 |
|---|---|---|
| 回答与最新事实不符 | 过期记忆未过滤或未加权 | 先查写入时间,再查检索排序 |
| 上下文超 Token 限制 | 未做合并裁剪或 topK 过大 | 先看记忆总量,再调 topK 与切片窗口 |
| 回答内容重复啰嗦 | 相似记忆被重复注入 | 检查多路召回后的去重逻辑 |
| 用户改需求后仍按旧需求走 | 记忆版本替换未生效 | 检查写入链路的覆盖策略与冲突声明 |
| 检索相关但答非所问 | 相似度匹配代替语义匹配 | 换 embedding 模型,或增加规则召回 |
| 长对话中期记忆漂移 | 中段记忆没有切片保留 | 增加分段摘要,缩短切片窗口 |
| 同一事实两套说法 | 多处存储未统一事实源 | 检查写入链路是否单点更新 |
| 延迟突然升高 | 检索链路无缓存,候选集过大 | 增加本地缓存或降低候选召回量 |
排查时记住一个原则:先数据后模型。先确认记忆库里到底存了什么、检索到底拿到了什么,再怀疑模型理解能力。多数问题出在前面,后面只是背锅。
6.4 避坑经验和一点个人心得
最后说几个踩过坑之后沉淀下来的经验。
记忆库的字段设计要预留元数据。常见的有时间戳,但真正好用还需要用户 ID、会话 ID、内容类型、来源、有效性状态。一开始我也觉得字段越多越麻烦,后来才发现这些元数据是排查问题的钥匙。没有用户 ID,你就很难定位“这个用户为什么看到别人的记忆”。
prompt 里的记忆模块不要和其他指令混写。很多模板把系统指令、用户指令、记忆内容全拼在一段,模型分不清优先级。我习惯把记忆区用明确的标记隔开,并在模板里写明模型应该把记忆当背景而非指令。这样能显著减少“模型自作主张执行历史指令”的概率。
定期做记忆体检。每月跑一次脚本,统计记忆数量、过期比例、重复率。不做体检的系统表面上能用,实际上已经在悄悄劣化。等发现问题时,上下文中可能已经充满垃圾条目,回滚成本非常高。
从 MATTS 的时序流,到 CFGM 的条件关联图,再到 MIRIX 的外部索引库,这三套思路背后没有绝对优劣,只有适配问题。我现在接到新项目,会先拿三个框架当坐标轴,把场景往里一放,马上就能判断记忆模块应该重点投入在哪。希望这篇能帮你少走一些弯路,也欢迎在落地时回来交流你的实际数据。