Skip to content

第 17 周 · Day 1:记忆四分类——让 Agent 从失忆到记得你 ​

对应手册任务:学习「记忆分类:短期/长期/情景/语义」,动手画出三层记忆架构图:Redis(短期)+ PG(长期)+ 向量库(语义),当日产出一张「记忆架构图」。本篇只解决一个问题:第 12 周的 checkpoint 只会把对话原样存下来、原样放回去,轮数一多上下文爆炸,换个会话就彻底失忆,今天借认知科学的四类记忆,把「Agent 记住用户」这件事拆成能落地的三层存储。

今日目标 ​

  1. 说得清 checkpoint 的两个痛点:会话内上下文爆炸,会话间完全失忆
  2. 掌握认知科学四分类到 Agent 组件的映射:短期→对话窗口、长期→用户档案、情景→历史对话、语义→知识库
  3. 独立画出三层记忆架构图(Redis + PG + 向量库),每层标注承载的记忆类型、读写时机、生命周期,并用矩阵表逐项自查

概念讲解:为什么 checkpoint 不够用 ​

第 12 周给 Agent 装上 checkpoint 之后,对话中断能恢复,看起来它已经「有记性」了。做两个思想实验,这个错觉马上破掉。

实验一,会话内爆炸。用户和 Agent 连聊 80 轮,checkpoint 的机制是把历史消息原样存、原样放:第 81 轮发问时,前面 80 轮一个字不少全部塞回模型。token 账单跟着轮数线性起飞,而其中大部分内容对当前这一问毫无用处。checkpoint 本质是对话流水账的「原样回放」,不会挑重点,不会做总结,更不会忘。

实验二,会话间失忆。昨天用户说过「我对花生过敏」,今天新开一个会话问「晚饭吃什么」,Agent 兴致勃勃推荐宫保鸡丁。这不怪它:模型本身无状态,checkpoint 绑定在单个会话线程上,换线程一切从零。它从头到尾没有「这个用户是谁」的概念。

两个实验合起来就一句话:会话内记太多,会话间记不了。这样的人像谁?像一位能逐字背诵你们今天聊天记录的朋友,却不记得你的名字、你的口味、上周和你说过的每句话。

人脑显然不这么干活。认知科学早就把记忆切成了四类,每一类都能在 Agent 里找到对应物:

  • 短期记忆(工作记忆):心算时临时攥着的数字,几秒就散。对应对话窗口,只装当前会话最近几轮
  • 长期记忆·事实:老朋友的生日和忌口,稳定、可陈述。对应用户档案:偏好、约束、关键事实
  • 长期记忆·情景:上周三那次聊搬家的对话,是「我经历过的事」。对应历史对话事件,可检索回忆
  • 长期记忆·语义:知道花生是常见过敏原,这句话和谁说的无关。对应知识库,也就是 RAG

先把一张底牌亮出来:语义记忆你第 14 周就已经做完了,RAG 知识库就是一个不折不扣的语义记忆。所以本周不是从零造四样东西,而是把缺的三块补上,再把已有的那块归位到统一框架里。前 16 周攒下的存储组件,从这周开始重新拼装。

核心知识 ​

本节的表格替代了往常的代码块,是今天画图的全部依据,动手前建议通读一遍。

1. 四类记忆,一张映射表 ​

记忆类型人脑里的样子Agent 里的样子现状
短期(工作记忆)心算时攥着的数字当前会话最近 N 轮对话窗口checkpoint 里有,但缺裁剪
长期(事实)记得朋友的生日和忌口用户档案:偏好 / 事实 / 约束本周用 PG 新建
情景(经历)记得上周聊过搬家历史对话事件,可检索回忆本周进向量库
语义(知识)知道花生是常见过敏原RAG 知识库第 14 周已建

四行里最容易被搅在一起的是情景和语义,多说两句。情景记忆的主语是「我」:带时间、带当事人、带上下文,「用户上周说他搬家了」是情景。语义记忆和经历者无关:「花生是常见过敏原」对谁都成立。区分它们不是学院趣味,直接决定第三层怎么设计检索,坑 4 还会回头讲。

另外注意第一行的措辞:checkpoint 里「有」对话消息,但缺两样东西——轮数裁剪(所以爆炸),跨会话能力(所以失忆)。把这两样补齐,才配叫短期记忆层。

2. 三层存储:每层放什么、读什么 ​

四类记忆落进三个存储层,语义和情景共用向量库。这就是今天要画的架构,先用一张矩阵表把它说死:

存储层承载记忆放什么读什么何时写何时读生命周期
Redis短期当前会话最近 N 轮消息最近对话上下文每轮对话后追加组装 prompt 前TTL,会话结束即过期
PG长期提炼后的用户事实档案「用户是谁」的稳定信息从对话提炼出事实时会话开始注入 + 需要时查询永久,可更新覆盖
向量库情景 + 语义历史对话的 embedding、知识库文档相似场景回忆、知识片段会话结束归档语义检索命中时长期,只增可标废弃

三个层各有一个必须守住的设计决定。

Redis 层,靠 TTL 和截断守住「会忘」。短期记忆的全部价值就是容量小、用完即丢:TTL 保证会话凉了数据跟着凉,截断保证读回来时只有最近 N 轮。两个阀门少一个,Redis 就退化成第二个 checkpoint 垃圾场。

PG 层,只放提炼后的事实。存「花生过敏」这条结构化记录,不存用户说这句话时的那 20 轮原文。原文在向量库自有去处,档案只要结论。这样档案才能直接拼进 prompt;哪天用户说「过敏好了」,更新就是覆盖一条记录的事。

向量库层,是回忆不是背诵。历史对话转成 embedding 存进去,检索时按相似度捞最相关的几段,而不是全量重放。它和第 14 周的知识库同居一个存储,但两块数据必须能区分开,具体做法见坑 4。

3. 和 checkpoint 的关系:互补,不是替代 ​

维度checkpoint(第 12 周)记忆(本周)
绑定对象会话线程用户
存什么运行状态:消息、工具调用轨迹提炼后的信息:事实、事件、知识
目的同一个对话中断后能续命不同对话共享认知
生命周期随会话跨会话

一句话记住分工:checkpoint 让对话接着聊,记忆让 Agent 越用越懂你。运行时的完整流程是三层协同——会话开始,从 PG 注入用户档案;每轮应答,读写 Redis 的最近上下文;会话结束,把值得留的提炼进 PG、原文归档进向量库。checkpoint 在这一切底下继续负责中断恢复,谁也不抢谁的活。

动手任务:三层记忆架构图一步一步 ​

手册任务:画出 Redis(短期)+ PG(长期)+ 向量库(语义/情景)三层记忆架构图。拆成 5 步,全程约 20 分钟。用 Excalidraw 或纸笔即可——下面每一步先把结构用文字列清楚,你在画布上照着摆。

第 1 步:起画布。 在本周练习目录新建 memory-architecture.md 记录结构草图,画布用 Excalidraw(或纸笔)。下面每一步的结构都记进去,画完它就是当日产出。

第 2 步:立起三个存储框。 先不管箭头,把三层按「离 Agent 的远近」从上往下摆:短期最近在最上,长期居中,向量库垫底。

  • 短期记忆 · Redis:当前会话最近 N 轮
  • 长期记忆 · PG:用户档案——偏好 / 事实 / 约束
  • 情景 + 语义 · 向量库:历史对话事件 + RAG 知识库

第 3 步:加 Agent 和读写箭头。 Agent 画在最顶上,对每层各画一条双向标注的箭头,标签写清动作和时机,时机全部来自矩阵表,不要发明新动作:

  • Agent 运行时 ⇄ 短期记忆(Redis):每轮追加写入 / 组 prompt 时读最近上下文
  • Agent 运行时 ⇄ 长期记忆(PG):提炼事实后写入 / 会话开始注入
  • Agent 运行时 ⇄ 情景 + 语义(向量库):会话结束归档 / 相似度检索

六组读写动作画完后,拿矩阵表逐行核对:每个「何时写」「何时读」在图上都能找到对应的箭头吗?找不到就补,多余的就删。

第 4 步:标注生命周期,得到终稿。 给每个存储框补上第二行——Redis 加「TTL,会话结束即过期」,PG 加「永久,可更新覆盖」,向量库加「长期,只增可标废弃」。标生命周期不是装饰,它回答「这层数据什么时候死」,是本周写代码时 TTL 参数和清理策略的直接依据。终稿的结构:

  • 短期记忆 · Redis——当前会话最近 N 轮|生命周期:TTL,会话结束即过期
  • 长期记忆 · PG——用户档案:偏好 / 事实 / 约束|生命周期:永久,可更新覆盖
  • 情景 + 语义 · 向量库——历史对话事件 + RAG 知识库|生命周期:长期,只增可标废弃
  • 读写关系(Agent 居顶,三条双向箭头):每轮追加 / 组 prompt 时读最近上下文;提炼后写 / 会话开始注入;结束时归档 / 相似度检索

第 5 步:四问自查。 对着终稿问自己:每层标了承载的记忆类型吗?每个箭头标了读写时机吗?每层标了生命周期吗?情景和语义在向量库里画成可区分的两块了吗?最后一问如果答否,把向量库节点拆成上下两行(终稿的写法),或加一条注释说明实现时用 metadata 区分。

画图工具

Excalidraw 画三色横条堆叠、Agent 居顶、实线箭头表示读、虚线表示写,导出 PNG 存档当本周的施工图;上面几组结构列表留在笔记里,代码一动就对照更新。

常见踩坑 ​

坑 1:想拿记忆替代 checkpoint(或反过来)。 两个方向都有人踩。删掉 checkpoint 只留记忆,多轮工具调用的中间状态和中断恢复就没了,Agent 一打断就废;只留 checkpoint 不做记忆,就是开篇那两个思想实验。判断标准一句话:对话内的运行状态归 checkpoint,跨会话的认知归记忆,两边是叠加关系。

坑 2:把原始对话直接塞进 PG 当长期记忆。 档案要的是「花生过敏」四个字,不是那 20 轮聊天记录,原文的去处是向量库。塞错了付两笔代价:档案膨胀到没法直接拼进 prompt;事实更新时(用户改口说「过敏好了」)你面对两段互相矛盾的原文,没法合并。长期记忆必须过「提炼」这道工序,这是它和 checkpoint 最本质的分界线。

坑 3:Redis 不设 TTL、不截断轮数。 短期记忆的全部意义就是会忘。不设 TTL,Redis 里堆着三个月前的死会话,它就成了第二个垃圾场;不截断 N,读回时上下文爆炸原样复发。N 取多少不是拍脑袋:10 到 20 轮起步,拿第 16 周建的评估集去量不同 N 下的答案质量,让数据替你选。

坑 4:情景和语义在向量库里不分家。 同一个库两类数据,用户问「我上次提过什么需求」,检索结果里混进一堆知识库科普文档,体验直接崩掉。最低成本的防线是给每条向量挂 metadata:source: conversation 或 source: knowledge,检索时按需过滤。图上画成一个框两个区,实现时就是 metadata 过滤的区别。

坑 5:把图当一次性作业。 这张架构图是本周每天的施工蓝图,代码一动它就得跟。周中你大概率会发现 PG 档案要加「事实更新时间」字段来处理冲突,发现就该回图上补一行。我的判断标准:图和代码差一次,是代码没跟上;差三次,就是图错了,重画。

自测问题 ​

先自己答,再展开对照。答不上来的,回对应小节看一遍。

  1. checkpoint 和记忆的核心区别是什么,各解决什么问题?
参考答案

checkpoint 是对话恢复机制,绑定会话线程,存运行状态(消息、工具调用轨迹),目的是同一个对话中断后能续命。记忆是跨会话知识,绑定用户,存提炼后的信息(事实、事件、知识),目的是让不同对话共享认知、越用越懂用户。两者互补不是替代,运行时叠加使用。

  1. 情景记忆和语义记忆都放向量库,怎么区分?
参考答案

情景是「我经历过的事件」,带时间和当事人,来源是历史对话;语义是世界知识,和经历者无关,来源是知识库文档。实现上给每条向量挂 metadata(source: conversation / source: knowledge)或分 namespace,检索时按需过滤,避免互相污染结果。

  1. 为什么长期记忆必须提炼后入库,不能存对话原文?
参考答案

档案要能直接注入 prompt,还得可更新。原文有两个致命点:体积大,拼进上下文等于换个地方爆炸;事实变更时两段原文互相矛盾,无法合并。提炼成结构化事实后,读是一条记录,更新是覆盖一条记录。

  1. Redis 层的 TTL 和轮数截断分别防什么?
参考答案

TTL 防跨会话的垃圾堆积,会话结束数据跟着过期,守住「该忘就忘」;截断防单会话内读回时上下文爆炸,只保留最近 N 轮。两个阀门分别对应「会话间失忆需求」和「会话内不爆炸需求」,少一个 Redis 就退化成第二个 checkpoint。

  1. 四类记忆里哪一类其实已经实现过了?在哪一周?
参考答案

语义记忆。第 14 周建的 RAG 知识库就是完整的语义记忆实现:知识文档进向量库,按语义相似度检索。本周只是把它归位到记忆框架里,并让它和情景记忆在同一个向量库里可区分地共存。

延伸阅读 ​

今天的产出 memory-architecture.md(结构清单 + 手绘图)留好,这是本周的施工蓝图:接下来几天会把它一层层变成真代码,Redis 对话窗口、PG 用户档案、向量库情景检索依次落地,进度对照本周计划推进。从第 1 周的 createResponse<T> 走到今天的记忆架构,零件早已齐备,这周正式拼装。