告别失忆 Agent:LangChain Memory 双轨制完全指南
从 Checkpoint 到 Store,让 Agent 真正"记住"你
🎯 写在最前
想象这样一个场景:
用户:「你好,我叫李雷,是个男生。」
Agent:「你好李雷!」(第二天)
用户:「我是谁?」
Agent:「抱歉,我不知道。」
这就是典型的失忆 Agent——它只能看到当前对话窗口里的内容,一旦会话结束或上下文超出 token 限制,就什么都不记得了。
在 LangChain 中,这套"记忆"机制被抽象成了短期记忆与长期记忆两条平行轨道:
- 短期记忆 负责单次会话的对话上下文(Checkpointer)
- 长期记忆 负责跨会话、跨用户的持久化数据(Store)
本文会带你:
- ✅ 理解 LangChain Memory 的核心概念(Checkpoint / Thread / Store / Namespace)
- ✅ 掌握短期记忆的构建与自定义 State
- ✅ 用工具让模型"看见"自己的记忆
- ✅ 落地长期记忆的最佳实践
- ✅ 通过对比表一眼看懂二者差异
📑 目录
一、为什么 Agent 必须有记忆?
记忆是维持历史交互信息的系统,对 Agent 至关重要——它能让 Agent 从反馈中学习、根据用户偏好调整策略。没有记忆的 Agent,本质上只是一个"函数调用器",而不是真正的对话伙伴。
Agent 内部以「消息序列」的形式保存交互历史,典型的 Agent State 长这样:
Agent State 是一个不断追加的消息队列,既包含人类、AI 的发言,也包含工具调用的结果。
二、核心概念 60 秒速览
在 LangChain/LangGraph 中,「记忆」由两条平行的轨道组成:
执行流程上,每个 super-step 都会产出一个 Checkpoint,组成一条 Thread:
💡 简单记忆法:
- Thread = 一次会话
- Checkpoint = 会话中的一个时间切面
- Super-step = 一次「模型推理 + 工具执行 + 状态更新」的最小单元
三、短期记忆:让对话"接着聊"
LangChain 通过 Checkpointer(检查点管理器) 实现短期记忆:
检查点管理器会在每个 super-step 保存图的状态快照(checkpoint)。这些 checkpoint 被保存到一个 thread 中,可以在图执行后访问。由于线程允许在图执行后访问其状态,因此可以实现多种强大功能。
3.1 最简示例:两个独立会话
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model=model,
checkpointer=InMemorySaver(),
)
# 会话 1:李雷
agent.invoke(
{"messages": [{"role": "user", "content": "你好,我叫李雷,是男生"}]},
{"configurable": {"thread_id": "1"}},
)
# 会话 2:韩梅梅
agent.invoke(
{"messages": [{"role": "user", "content": "你好,我叫韩梅梅,是女生"}]},
{"configurable": {"thread_id": "2"}},
)
# 同 thread_id 共享历史
res = agent.invoke(
{"messages": [{"role": "user", "content": "我是谁?"}]},
{"configurable": {"thread_id": "1"}},
)
# "你就是李雷——你之前告诉我你是个男生,名字叫李雷。"
3.2 进阶:自定义 State
记忆的本质是记录数据。除了默认的 messages,我们可以给 State 加任意字段:
from typing import Literal
from langchain.agents import AgentState
class CustomAgentState(AgentState):
user_name: str
gender: Literal["male", "female"]
memory_saver = InMemorySaver()
agent = create_agent(
model=model,
checkpointer=memory_saver,
state_schema=CustomAgentState, # 注入自定义字段
)
res = agent.invoke(
{
"messages": [{"role": "user", "content": "你好!"}],
"user_name": "李雷",
"gender": "male",
},
{"configurable": {"thread_id": "1"}},
)
print(res["user_name"], res["gender"])
⚠️ 重要提醒:模型默认只能看到
messages。自定义字段必须通过 Middleware(中间件) 或工具显式注入到 prompt,否则 Agent 不会感知到。
四、用工具让模型主动读取记忆
上一节提到,自定义字段默认不可见。LangChain 提供了两种主流方案让模型"看见"记忆:
- 工具读取(Tool-based):模型主动调用工具读 state
- 中间件注入(Middleware):在
before_model/after_model钩子里读写
4.1 通过工具读取 State
from langchain.tools import tool, ToolRuntime
@tool
def get_user_info(runtime: ToolRuntime) -> str:
"""查看用户信息。"""
user_name = runtime.state["user_name"]
gender = runtime.state["gender"]
return f"User Name: {user_name}, Gender: {gender}"
agent = create_agent(
model=model,
tools=[get_user_info],
checkpointer=memory_saver,
state_schema=CustomAgentState,
)
res = agent.invoke(
{"messages": [{"role": "user", "content": "我是谁?"}]},
{"configurable": {"thread_id": "1"}},
)
# "你是李雷,一位男性。需要我帮你做些什么吗?"
整条 tool-calling 链路如下:
4.2 通过中间件注入(更灵活)
我们还可以灵活地自定义 before_model、after_model 中间件,实现多样化的记忆写入和读取。中间件适合需要批量、隐式处理记忆的场景,比如:
# 伪代码示意
@before_model
def inject_user_info(state: AgentState):
"""把 state 里的 user_name 拼进 system prompt"""
user_name = state.get("user_name", "游客")
return {
"messages": [
SystemMessage(f"当前用户:{user_name}"),
*state["messages"],
]
}
五、长期记忆:跨会话的知识库
短期记忆解决了"同一会话上下文"问题,但用户的偏好、知识、习惯需要跨会话复用——这就是长期记忆的舞台。
LangChain 将长期记忆以 JSON 文档的形式存储在 store 对象中。store 中的每个记忆都组织在自定义命名空间和独立键下。命名空间通常包含用户或组织 ID 或其他标签,便于信息组织。
5.1 基本读写
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
# 定义命名空间(本质是元组路径)
namespace_1 = ("六年级", "1班")
namespace_2 = ("五年级", "2班")
# 写入
store.put(namespace_1, "张三", {
"exam": "期末考试",
"scores": {"英语": 90, "数学": 95},
"date": "2026-01-15",
})
store.put(namespace_1, "李四", {
"exam": "期末考试",
"scores": {"英语": 80, "数学": 85},
"date": "2026-01-15",
})
store.put(namespace_2, "王五", {
"exam": "期末考试",
"scores": {"英语": 95, "数学": 100},
"date": "2026-01-15",
})
# 读取
li_info = store.get(("六年级", "1班"), "李四")
# Item(namespace=['六年级','1班'], key='李四', value={...}, ...)
5.2 Store 内部结构
💡 命名空间本质就是元组路径,例如
("六年级", "1班")等价于文件系统里的\六年级\1班\,非常适合做层级化组织。
5.3 长期记忆的杀手锏:search
Store 除了 put / get / delete,还内置了基于 embedding 的语义搜索:
# 嵌入语义搜索:输入"张三的英语成绩"
results = store.search(("六年级", "1班"), query="张三的英语成绩")
这让长期记忆天然适合 RAG 和知识库场景。
六、短期 vs 长期:一表看懂
| 维度 | 短期记忆 InMemorySaver | 长期记忆 InMemoryStore |
|---|---|---|
create_agent 入参 | checkpointer | store |
| 查询方式 | thread_id + checkpoint_id | (namespace, key) 任意查询 |
| 数据关联 | 与特定 thread 绑定 | 与 Store 对象绑定,可跨 thread |
| 运行时访问 | runtime.state / runtime.context | runtime.store |
| 存储内容 | 完整 State 快照(含对话历史) | 键值对(不含对话内容) |
| 持久化实现 | InMemorySaver / PostgresSaver / SqliteSaver | InMemoryStore / PostgresStore / SqliteStore |
| 典型场景 | 对话上下文、长任务执行 | 用户画像、知识库 |
| 常用方法 | put / get / get_tuple / list | put / get / delete / search / list_namespaces |
二者分工可以用下面这张图总结:
七、实战选型建议
✅ 用短期记忆的场景:
- 多轮对话机器人
- 长任务执行(需要回滚到某个 step)
- 工具调用密集、需要上下文传递的 Agent
✅ 用长期记忆的场景:
- 用户偏好 / 画像("用户喜欢简洁回答")
- 跨会话知识("上次问过什么")
- RAG 知识库
✅ 进阶组合:两者经常一起用
🚀 生产环境三件套:
- 把
InMemorySaver→PostgresSaver - 把
InMemoryStore→PostgresStore - 加上 中间件 做日志、审计、人机协同
八、写在最后
LangChain 的 Memory 体系看起来组件众多,但本质上就是两条平行的轨道:
短期记忆告诉你「刚才发生了什么」,
长期记忆告诉你「用户是谁、世界是什么」。
二者配合,Agent 才能从「金鱼记忆」进化成「老朋友」。
希望这篇笔记能帮你把 Memory 系统彻底拿下。Happy hacking!🎉
📌 参考: