第3节 长期记忆的定义与持久化管理#
在上一节内容「 Agent 短期记忆管理实战:上下文窗口限制、消息列表维护与压缩修剪策略详解」中,我们详细介绍了短期记忆的机制以及它在 LangGraph 中的使用方法,尽管 短期记忆把“一次会话”里的上下文兜住了,但 Agent 想要真的“懂你”,光这样还不够——它还需要一种跨会话也能调起来用的记忆,这就是本节内容要讲的长期记忆。
为了能更加深刻地理解长期记忆,下面先来看人类是如何描述长期记忆的,然后再看 LangGraph 设计 Agent 记忆时借鉴的认知架构(CoALA),然后逐个拆解 3 类记忆各自存什么、怎么用,最后落到 PostgresStore 的 PostgreSQL 持久化实现。
3.1 从人类长期记忆分类讲起#
你想过没有,人的脑子里其实有好几套并行的“记忆系统”?尽管不同的研究者对“记忆怎么分类”各有说法,但几个核心概念已经基本形成共识:
首先,刚刚发生、还没来得及消化的,归为短期记忆(前面第2节讲过);其次,凡是能被保存下来、以后还能回忆出来的,都算长期记忆。而人类的长期记忆,又大致可以分成下面 3 类。
(1)情景记忆(Episodic Memory)
当你回忆起某个具体事件或“片段”时,调动的是情景记忆。它能唤起你对细节的感知——小时候的特殊经历、二十分钟前跟朋友聊天的感受等,时间跨度可以从几分钟前到几十年前。
(2)语义记忆(Semantic Memory)
语义记忆是人们长期储存知识的一种方,它由各种信息组成,包括学校里学到的事实、概念及其相互关系、特定词语的定义等。比如“今年是哪一年”“中国首都是哪里”“机器学习是什么意思”——这些都是典型的语义记忆。
(3)程序性记忆(Procedural Memory)
程序性记忆指的是对如何做事(包括生理和心理方面)的长期记忆,它覆盖从生理技能到心理技能两个层面——从习以为常的基本技能到需要大量练习才能掌握的复杂技能。比如多年没骑自行车,再坐上去依然知道怎么蹬——这就是程序性记忆在起作用。
3.2 CoALA 认知架构借鉴#
短期记忆靠 thread_id 隔离上下文,长期记忆则存进自定义的 namespace,能跨任意会话被读写。但说到底,长期记忆的设计并没有标准答案,在动手实现之前我们需要先把这两个问题想清楚:“存什么类型的记忆?”和“什么时候写入?”。这两个问题的答案直接决定整个长期记忆系统的架构。
LangGraph 在设计 Agent 记忆系统时,借鉴了心理学对人类记忆的分类。按存储内容不同,长期记忆分 3 种类型,如表3-1所示。
| 记忆类型 | 存储内容 | 人类类比 | Agent 应用场景 |
|---|---|---|---|
| 语义记忆 | 事实与知识 | 课堂上学到的知识 | 用户偏好、个人信息、事实陈述等 |
| 情景记忆 | 经历与事件 | 做过的事情 | 过去执行过的操作和案例等 |
| 程序记忆 | 规则与指令 | 骑自行车的肌肉记忆 | Agent 的系统提示词、代码逻辑、模型权重等 |
从表3-1 可以看到,语义记忆、情景记忆和程序记忆这种划分方式,刚好能满足智能体长期记忆的归纳和管理。这种架构有个专门的名字——语言智能体认知架构(Cognitive architectures for language agents,CoALA)[2],全貌如图3-1所示。
如图3-1所示,它展示的是一个典型的智能体记忆系统架构,于2024年由普林斯顿大学提出。CoALA 借鉴了人类认知科学中的记忆分类方式,把智能体的记忆分成 4 类:程序记忆、语义记忆、情景记忆,以及工作记忆(即短期记忆)。
在智能体运行过程中,外部环境会持续给智能体输入观察,短期记忆负责对当前任务状态做临时的存储和推理;智能体再去检索不同类型的记忆,并结合决策过程生成对应动作,跟数字世界、物理世界以及对话场景进行交互。同时,系统还能把新经验反向写回长期记忆,做到了动态学习和持续进化。可以说,整个架构体现了 AI 智能体从“单次生成”到“具备长期记忆 + 自主学习能力”的关键演进。
3.3 语义记忆:事实与个人偏好#
语义记忆存的是“事实性”信息——关于用户、组织或其他实体的具体事实、规则和结构化信息。最典型的应用就是个性化:让 Agent 记住你的姓名、年龄、偏好、工作等,也就有了知识检索和持续学习的能力。
具体落地有两种存法:用户画像和记忆集合 [4]。
(1)用户画像(Profile)
用户画像把所有需要记住的信息维护成一份可持续更新的 JSON 文档,原理如图3-2所示。
如图3-2所示,当用户和大模型在交互过程中有新的息产生时,旧的用户画像和新的信息将会一同被传给大模型,让模型生成更新后的用户画像。这种方法的优点是信息集中、上下文完整;但缺点也很明显,随着画像内容大增加更新过程会很容易出错,也容易丢失细节,可能需要拆分文档或使用严格解码来确保 JSON 格式有效。关于用户画像的详细记忆示例可以参见 Code/Chapter05/profile_memory.json 文件。
(2)记忆集合(Collection)
记忆集合把每条记忆单独存储为一个文档,随着交互不断追加新的文档,原理如图3-3所示。
如图3-3所示展示的是由多个相互独立的记忆片段组成的列表,优点是粒度小、不易丢信息,新信息直接追加即可;缺点是删除或更新旧条目比较复杂(容易“过度插入”或“过度更新”),过度分散的片段也缺乏全局关系的统一视图。
这里过度插入指模型有可能错判当前信息为新的信息而直接插入导致记忆间出现有冲突;过度更新则是指模型表现过于“积极”,在处理新信息时,会错误地覆盖或修改原本不该变动的现有记忆。对于这类问题,可以借助像 Trustcall [5] 这样的工具包来精确管理增删改查。完整示例参见 Code/Chapter05/collection_memory.json。
总结起来就是,如果用户信息相对固定、字段清晰可预设(如偏好语言、常用工具),用户画像是更简洁的选择;如果信息随时间持续积累、难以预设所有字段,记忆集合的扩展性则更好。
3.4 情景记忆:从过往经历中学习#
情景记忆存的不是事实,而是 Agent 过去执行任务的经历(操作序列、结果等)。最常见的实现方式是少样本示例(Few-shot Examples),把历史上成功的输入输出案例存起来,新任务到来时检索出最相关的案例放入提示词,引导大模型模仿预期行为,让系统基于过去经验做决策优化。
一句话总结:情景记忆就是让 Agent 通过 “看到” 过去具体成功的案例来学习复杂任务的处理模式,面对相似场景时能给出更准确、更符合预期的结果。
3.5 程序记忆:规则与能力存储#
程序记忆保存的是模型能力与执行规则——包括大语言模型本身和智能体的代码逻辑,决定了 Agent 在执行任务、调用工具、做决策时到底是什么行为方式。现代 AI 智能体里的程序记忆通常由 3 部分组成:系统提示词 + 智能体代码 + 大模型权重。其中,模型权重里藏着通过大规模预训练或微调学到的“隐式知识”(比如语言理解、推理、任务执行模式);代码和提示词则承载“显式知识”(工作流、工具调用、检索逻辑、决策规则等)。
和语义记忆、情景记忆不一样,程序记忆一般需要在系统初始化阶段由开发者预先设定。比如智能体启动时加载的提示词、工作流代码、模型本身,这些都是程序性记忆的重要组成。
这里有一点要注意:修改程序记忆的风险通常更高——提示词写错、代码逻辑有 bug、或者换了模型,都可能让智能体行为偏离预期,甚至引发系统漏洞。所以工业级 AI 系统里,更常见的做法是尽量保持代码层的确定性和稳定性,把灵活性交给大模型的推理过程,靠这种分工来平衡“可控”和“能适应”这两件事。
3.6 记忆写入方式:热路径 vs 后台#
在清楚存什么记忆以后另一个重要的设计决策就是什么时候写入记忆。根据提供的来源或之前的对话,写入记忆在智能体架构中被视为一种学习行为,即将信息从短期记忆或外部观察持久化到长期记忆中。在 LangGraph 等框架中,长期记忆的写入主要有两种方式,其原理示意如图3-4所示。
在图3-4中,左右两边分别是热路径写入和后台写入的原理示意图,他们各自具有不同的优缺点和应用场景。
(1)热路径写入(In the Hot Path)
热路径的核心是实时写:智能体在响应用户的回答之前,先通过推理判断哪些信息值得记住,立即写入存储库。优势是即时性和透明性——新记忆下次交互马上就能用,你也能直观看到“记忆已更新”的提示(用过 ChatGPT 的话,应该见过右侧偶尔弹出来的那个提示)[3]。后续实战里主要用的也是热路径。
(2)后台写入(In the Background)
后台写入则是把“保存”做成独立的异步任务:智能体在对话流程里不负责写入,而是交给定时任务、触发器,或者另一个专门的服务,让它会话结束后再去梳理历史、提取记忆 [4]。优势是性能好 + 逻辑分离——主进程里不再有写入的延迟,应用逻辑和记忆管理完全解耦;不足是如果更新频率太低,其他并行的会话线程可能拿不到最新的上下文。
3.7 长期记忆持久化方法#
原理讲完,接下来看怎么落库。如果是本地 JSON 文件,用 json.dumps() 写盘就行(第4节会专门演示);如果走 LangGraph 存到数据库,就用 PostgresStore——注意区分一下,短期记忆用的是 PostgresSaver。以下完整示例代码参见 Code/Chapter05/C08_long_memory_store.py 文件。
第1步:写一个函数生成命名空间。
1 def get_namespace(memory_root=MEMORY_ROOT,
2 urer_id: str = USER_ID) -> tuple[str, str]:
3 return memory_root, urer_id需要注意的是,命名空间可以由任意多个字段构成,例如这里指定了一个根标识和用户 ID。后续,在数据库中将会以 memory_root. urer_id 来标识每个用户对应的所有记忆。
第2步:用 PostgresStore 中的 put 方法把记忆写进数据库。
1 def save_normal_memory(store: PostgresStore, namespace: tuple[str, ...]):
2 store.put(namespace, key=str(uuid.uuid4()),
3 value={"memory_type": "语义记忆",
4 "created_at": datetime.now().isoformat(timespec="seconds"),
5 "text": "用户最喜欢的编程语言是 Python。"})
6 store.put(namespace, key=str(uuid.uuid4()),
7 value={"memory_type": "情景记忆",
8 "created_at": datetime.now().isoformat(timespec="seconds"),
9 "text": "2026-01-31,用户完成了一次 RAG 检索结果为空的排查。"})
10 print("新保存记忆结束2(条)\n")在上述代码中,一共写入了2条记忆,其中 put() 方法中最核心的3个参数 namespace、key 和 value 分别表示将记忆存储到哪个命名空间中,对应的记忆 ID 是什么以及记忆内容是什么。需要注意的是, value 是一个字典形式,可以携带任何需要的内容,将作为一个字段被保存到数据库中。
进一步,在记忆入库保存后,可以通过 store.delete(namespace, key) 和 store.get(namespace, key) 分别根据记忆ID来删除和获取对应命名空间下的记忆内容,当然获取记忆来说更常见的场景是根据用户提问检索相关记忆,这部分内容将在后续小节中进行介绍。
第3步:跑通整个流程。
1 if __name__ == "__main__":
2 namespace = get_namespace()
3 with PostgresStore.from_conn_string(DB_URI) as store:
4 store.setup()
5 clear(store, namespace)
6 save_normal_memory(store, namespace)
7 print_search_result(store, namespace)上面这段代码:第 3~4 行连接数据库 + 初始化环境,LangGraph 会自动创建 store 和 store_migrations 两张表(记忆文本就存在 store 表里);第 5~7 行分别是清空当前 namespace 下的所有记忆、写入新记忆、输出当前 namespace 的内容。
上述代码运行结束以后,将会看到类似如下内容:
01. key=078e620b-15e2-4e30-b39c-f214e04f8527
value={'text': '2026-01-31,用户完成了一次 RAG 检索结果为空的排查。', 'created_at': '2026-06-11T20:21:42', 'memory_type': '情景记忆'}
02. key=34de62ff-dc0e-4d25-ad3b-f7732784595f
value={'text': '用户最喜欢的编程语言是 Python。', 'created_at': '2026-06-11T20:21:42', 'memory_type': '语义记忆'}同时,在数据库中 store 表里将会出类似如下内容:
prefix | key | value |created_at|updated_at|expires_at|ttl_minutes
-----------+------+-------------------------+----------+----------+----------+-----------
memory.user|ea7...|{"text": "用户最...", ...}|2026-06...|2026-06...| NULL | NULL
memory.user|9d5...|{"text": "2026-...", ...}|2026-06...|2026-06...| NULL | NULL在上述结果中,prefix、key 和 value 分别对应上吗 store.put(namespace, key, value) 中的这3个参数;created_at 和 updated_at 是 LangGraph 框架自动填写的内容;expires_at 和 ttl_minutes 则是设置记忆的过期时效,后续内容将会介绍。
到这里,长期记忆的原理和持久化就讲完了。下一节(第4节 从零实现 MiniChatGPT)会用最朴素的方式把短期、长期记忆串起来——不依赖任何 LangGraph 框架,纯手工跑通一个能跨会话记住你的最小版本。
引用#
[1] https://docs.langchain.com/oss/python/concepts/memory.md
[2] Sumers T, Yao S, Narasimhan K R, et al. Cognitive architectures for language agents[J]. Transactions on Machine Learning Research, 2023. https://arxiv.org/pdf/2309.02427
[3] ChatGPT 的记忆功能和新控件 https://openai.com/zh-Hans-CN/index/memory-and-new-controls-for-chatgpt/
[4] https://github.com/mlwithme/memory-template
[5] https://github.com/hinthornw/trustcall