LangGraph Agent Checkpointer 持久化完全指南:从内存到生产的实战
很多人写 LangGraph Agent 只跑通内存版本:进程一重启,全部状态灰飞烟灭,任务中途崩溃只能从头跑,token 白白浪费。
Checkpointer 就是 LangGraph 的存档系统 。每一次图执行超步,自动拍下 State 快照,依靠
thread_id实现断点续跑、会话记忆、人工介入调试、历史状态回溯。从开发阶段的MemorySaver,原型验证SqliteSaver,再到生产高并发PostgresSaver,很多同学踩坑直接把内存版丢上生产,线上大量丢状态。

一、为什么 Agent 必须用checkpointer?
很多人写 LangGraph Agent 只跑通内存版本:进程一重启,全部状态灰飞烟灭,任务中途崩溃只能从头跑,token 白白浪费。
Checkpointer 就是 LangGraph 的存档系统 。每一次图执行超步,自动拍下 State 快照,依靠 thread_id 实现断点续跑、会话记忆、人工介入调试、历史状态回溯。从开发阶段的 MemorySaver,原型验证 SqliteSaver,再到生产高并发 PostgresSaver,很多同学踩坑直接把内存版丢上生产,线上大量丢状态。
二、Checkpointer 核心原理
2.1 工作机制
Checkpointer 的本质是一个状态持久化层,它在 Agent 执行流程中扮演"自动存档"的角色:
每次调用结束后:
Checkpointer 自动保存本次对话的所有消息(Checkpoint)
|
下次调用开始时:
Checkpointer 自动加载之前保存的消息,拼接到新的输入前面
|
效果:
LLM看到的消息列表 = 历史消息 + 本次新消息
-> Agent就"记住"了之前的对话
在 LangGraph 中,Checkpointer 会在每个节点(Node)执行后 自动保存当前图的完整状态(State),包括 messages、tool_calls、intermediate_steps 等所有通道数据。
2.2 Thread ID:多会话隔离的钥匙
一个 Agent 通常同时服务多个用户,不同用户的对话历史绝不能互相干扰。LangGraph 通过 thread_id 实现会话隔离:
thread_id: "user_张三" -> [消息1, 消息2, 消息3, ...]
thread_id: "user_李四" -> [消息A, 消息B, ...]
thread_id: "user_王五" -> [消息X, 消息Y, ...]
每次调用 Agent 时,通过 config 参数指定 thread_id,LangGraph 就会自动加载该线程对应的历史状态。
三、四种存储后端详解
LangGraph 的 Checkpointer 采用存储后端抽象设计,同一套代码可以无缝切换不同的持久化方案。截至 2026 年,官方支持四种后端:
| 后端 | 导入路径 | 数据持久性 | 适用场景 | 并发支持 |
|---|---|---|---|---|
| InMemorySaver | langgraph.checkpoint.memory |
进程结束即丢失 | 本地开发/单元测试 | 单进程 |
| SqliteSaver | langgraph.checkpoint.sqlite |
本地文件持久化 | 单机原型/小工具 | 单进程(文件锁) |
| PostgresSaver | langgraph.checkpoint.postgres |
完整 ACID | 生产环境/分布式 | 高并发 |
| RedisSaver | 社区包 langgraph-checkpoint-redis |
依赖 RDB/AOF | 分布式缓存/短会话 | 高并发 |
3.1 内存存储:InMemorySaver(开发环境)
python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages
# 定义状态
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
# 构建图(简化示例)
builder = StateGraph(AgentState)
# ... 添加节点和边 ...
# 创建内存 checkpointer
memory = InMemorySaver()
# 编译图时传入 checkpointer
app = builder.compile(checkpointer=memory)
# 调用时指定 thread_id
config = {"configurable": {"thread_id": "user-123"}}
result = app.invoke(
{"messages": [("user", "今天北京天气怎么样?")]},
config=config
)
# 再次调用(同一个 thread_id -> 有记忆)
result2 = app.invoke(
{"messages": [("user", "我刚才问你什么了?")]},
config=config # 相同 thread_id,自动加载历史
)
注意 :InMemorySaver 是 MemorySaver 的别名,新代码建议统一使用 InMemorySaver。
3.2 SQLite 存储:SqliteSaver(单机原型)
SQLite 将状态写入本地文件,进程重启后数据不丢失,适合单用户桌面应用或小型演示。
安装依赖:
bash
pip install langgraph-checkpoint-sqlite>=3.0.1 # 安全补丁版本
同步版本:
python
from langgraph.checkpoint.sqlite import SqliteSaver
# 方式一:使用文件(推荐)
with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
app = builder.compile(checkpointer=checkpointer)
result = app.invoke(
{"messages": [("user", "你好")]},
config={"configurable": {"thread_id": "session-001"}}
)
# 方式二:内存中的 SQLite(测试用)
with SqliteSaver.from_conn_string(":memory:") as checkpointer:
app = builder.compile(checkpointer=checkpointer)
异步版本(生产推荐):
python
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
async with AsyncSqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
app = builder.compile(checkpointer=checkpointer)
result = await app.ainvoke(
{"messages": [("user", "你好")]},
config={"configurable": {"thread_id": "session-001"}}
)
数据库存储结构:
SQLite 后端会自动创建以下表(由 checkpointer.setup() 自动初始化):
sql
-- checkpoints 表:存储每个超级步骤的状态快照
CREATE TABLE checkpoints (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
parent_checkpoint_id TEXT,
type TEXT,
checkpoint BLOB, -- msgpack 序列化的状态数据
metadata BLOB,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)
);
-- checkpoint_writes 表:记录每个任务写入的通道数据
CREATE TABLE checkpoint_writes (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
task_id TEXT NOT NULL,
idx INTEGER NOT NULL,
channel TEXT NOT NULL,
type TEXT,
value BLOB,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
);
-- checkpoint_blobs 表:存储大体积二进制数据
CREATE TABLE checkpoint_blobs (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
channel TEXT NOT NULL,
version TEXT NOT NULL,
type TEXT,
value BLOB,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, channel, version)
);
3.3 PostgreSQL 存储:PostgresSaver(生产环境)
PostgreSQL 是生产环境的首选,支持并发写入、事务隔离、备份恢复,且能与现有运维体系集成。
安装依赖:
bash
pip install langgraph-checkpoint-postgres>=3.1.0
首次使用必须建表:
python
from langgraph.checkpoint.postgres import PostgresSaver
# 同步版本
with PostgresSaver.from_conn_string(
"postgresql://user:pass@localhost:5432/agentdb"
) as checkpointer:
checkpointer.setup() # 首次使用必须调用,创建表结构
app = builder.compile(checkpointer=checkpointer)
异步版本(生产标准写法):
python
import asyncio
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from psycopg import AsyncConnection
from psycopg.rows import dict_row
async def main():
# 生产环境:PostgreSQL + 异步 + 连接池
conn_string = "postgresql://user:pass@localhost:5432/agentdb"
async with await AsyncPostgresSaver.from_conn_string(
conn_string,
# 关键配置:自动提交 + 字典行工厂
conn_kwargs={
"autocommit": True,
"row_factory": dict_row,
"prepare_threshold": None, # 如果使用 PgBouncer 连接池,必须设置
}
) as checkpointer:
await checkpointer.setup()
app = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "user-alice-001"}}
result = await app.ainvoke(
{"messages": [("user", "帮我查北京天气")]},
config=config
)
# 第二次调用:自动加载历史
result2 = await app.ainvoke(
{"messages": [("user", "如果下雨就取消我的户外预约")]},
config=config
)
asyncio.run(main())
PostgreSQL 表结构:
与 SQLite 逻辑一致,但使用 Postgres 原生类型和索引优化:
sql
-- PostgreSQL 自动创建的表结构(简化版)
CREATE TABLE checkpoints (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
parent_checkpoint_id TEXT,
type TEXT,
checkpoint BYTEA, -- PostgreSQL 用 BYTEA 存储二进制
metadata BYTEA,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id)
);
CREATE TABLE checkpoint_writes (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
task_id TEXT NOT NULL,
idx INTEGER NOT NULL,
channel TEXT NOT NULL,
type TEXT,
value BYTEA,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx)
);
CREATE TABLE checkpoint_blobs (
thread_id TEXT NOT NULL,
checkpoint_ns TEXT NOT NULL DEFAULT '',
checkpoint_id TEXT NOT NULL,
channel TEXT NOT NULL,
version TEXT NOT NULL,
type TEXT,
value BYTEA,
PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, channel, version)
);
存储空间估算:
checkpoints行数约等于 (超级步骤数 + 1) 乘以 不同的命名空间数checkpoint_writes行数约等于 所有任务写入的通道数之和- 单线程存储字节数约等于 所有超级步骤中变更通道的序列化大小之和
- 注意 :对于追加型历史通道(如
messages),存储空间与轮次呈二次方增长------对话长度翻倍,存储空间约增长四倍
3.4 Redis 存储:RedisSaver(分布式缓存)
Redis 适合需要**自动过期(TTL)**的短会话场景,或作为 L1 缓存层使用。
安装依赖:
bash
pip install langgraph-checkpoint-redis>=1.0.2 # 安全补丁版本
python
from langgraph.checkpoint.redis import RedisSaver
with RedisSaver.from_conn_string("redis://localhost:6379/0") as checkpointer:
app = builder.compile(checkpointer=checkpointer)
result = app.invoke(
{"messages": [("user", "你好")]},
config={"configurable": {"thread_id": "session-redis-001"}}
)
四、完整实战:从开发到生产的记忆配置
4.1 开发阶段(InMemorySaver)
python
# dev_agent.py
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langchain_core.messages import HumanMessage, AIMessage
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]
def chat_node(state: State):
# 模拟 LLM 回复
return {"messages": [AIMessage(content=f"收到: {state['messages'][-1].content}")]}
builder = StateGraph(State)
builder.add_node("chat", chat_node)
builder.add_edge(START, "chat")
builder.add_edge("chat", END)
# 开发环境:内存存储,重启即丢
memory = InMemorySaver()
app = builder.compile(checkpointer=memory)
# 测试多轮对话
config = {"configurable": {"thread_id": "test-123"}}
# 第一轮
r1 = app.invoke({"messages": [HumanMessage(content="我叫张三")]}, config)
print(r1["messages"][-1].content) # "收到: 我叫张三"
# 第二轮(有记忆)
r2 = app.invoke({"messages": [HumanMessage(content="我叫什么?")]}, config)
print(r2["messages"][-1].content) # Agent 能引用之前的名字
4.2 原型阶段(SQLite)
python
# prototype_agent.py
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph
# ... 其他导入同上 ...
# 单机原型:SQLite 文件持久化
with SqliteSaver.from_conn_string("prototype.db") as checkpointer:
app = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "user-alice"}}
# 第一次调用
r1 = app.invoke({"messages": [("user", "查询北京天气")]}, config)
# 模拟进程重启后...
# 再次连接同一个数据库文件
with SqliteSaver.from_conn_string("prototype.db") as checkpointer2:
app2 = builder.compile(checkpointer=checkpointer2)
# 同一个 thread_id,历史还在!
r2 = app2.invoke({"messages": [("user", "刚才天气怎么样?")]}, config)
4.3 生产阶段(PostgreSQL + 异步)
python
# production_agent.py
import asyncio
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from psycopg.rows import dict_row
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
async def agent_node(state: AgentState):
llm = ChatOpenAI(model="gpt-4o-mini")
response = await llm.ainvoke(state["messages"])
return {"messages": [response]}
async def main():
builder = StateGraph(AgentState)
builder.add_node("agent", agent_node)
builder.add_edge(START, "agent")
builder.add_edge("agent", END)
# 生产环境:PostgreSQL + 异步 + 连接池
conn_string = (
"postgresql://agent_user:secret@postgres.internal:5432/agent_db"
)
async with await AsyncPostgresSaver.from_conn_string(
conn_string,
conn_kwargs={
"autocommit": True,
"row_factory": dict_row,
"prepare_threshold": None,
}
) as checkpointer:
await checkpointer.setup()
app = builder.compile(checkpointer=checkpointer)
# 用户 Alice 的对话
alice_config = {"configurable": {"thread_id": "user-alice-001"}}
r1 = await app.ainvoke(
{"messages": [HumanMessage(content="帮我订一张明天去上海的机票")]},
config=alice_config
)
# 几小时后,Alice 再次访问
r2 = await app.ainvoke(
{"messages": [HumanMessage(content="我的机票订好了吗?")]},
config=alice_config # 自动恢复上下文
)
# 用户 Bob 的对话(完全隔离)
bob_config = {"configurable": {"thread_id": "user-bob-999"}}
r3 = await app.ainvoke(
{"messages": [HumanMessage(content="你好")]},
config=bob_config
)
if __name__ == "__main__":
asyncio.run(main())
五、状态回退
Checkpointer 不仅是"记忆",还是时间机器。你可以查看任意历史状态,甚至从过去某个点分叉出新的执行路径。
python
# 查看某个 thread 的所有历史 checkpoint
config = {"configurable": {"thread_id": "user-alice-001"}}
history = list(app.get_state_history(config))
for checkpoint in history:
print(f"步骤: {checkpoint.metadata['step']}")
print(f"节点: {checkpoint.metadata['source']}")
print(f"消息数: {len(checkpoint.values.get('messages', []))}")
# 回退到上一个状态(撤销最后一步)
if len(history) >= 2:
previous = history[1] # 索引 0 是最新,1 是上一个
app.update_state(config, previous.values)
print("已回退到上一个 checkpoint")
# 从特定 checkpoint 恢复(崩溃恢复)
# 直接传入相同的 thread_id,LangGraph 会自动找到最后一个 checkpoint
result = app.invoke(None, config=config) # 传入 None 表示从 checkpoint 恢复
六、避坑指南与安全须知
6.1 版本安全(重要!)
2025-2026 年 LangGraph 的 Checkpointer 层曝出多个高危 CVE,请务必升级到安全版本:
| CVE | 影响组件 | 风险等级 | 最低安全版本 |
|---|---|---|---|
| CVE-2025-67644 | SQLite Checkpointer | SQL 注入 -> RCE | langgraph-checkpoint-sqlite >= 3.0.1 |
| CVE-2026-28277 | 核心 msgpack 反序列化 | 远程代码执行 | langgraph >= 1.0.10 |
| CVE-2026-27022 | Redis Checkpointer | 查询注入 | langgraph-checkpoint-redis >= 1.0.2 |
| CVE-2026-71433 | Postgres/SQLite 命名空间 | 跨租户数据泄露 | langgraph-checkpoint >= 4.0.1 |
所有后端(包括 PostgreSQL)都必须升级核心 langgraph 包,因为 msgpack 解码器位于共享核心中。
6.2 常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
CheckpointerConnectionError |
数据库连接失败 | 检查连接字符串,确保 Postgres/Redis 已启动 |
| 对话历史丢失 | 使用了随机 thread_id | 使用确定性 ID(如 user-{user_id}) |
| 消息无限增长 | 未配置消息压缩 | 使用 SummarizationMiddleware 或定期清理 |
| SQLite 并发错误 | 多进程同时写入 | 升级到 PostgreSQL |
| 跨线程记忆泄露 | 未正确隔离 thread_id | 确保每个用户有唯一的 thread_id |
6.3 存储空间优化
python
# 策略 1:限制 checkpoint 保留数量(需自定义清理脚本)
# PostgreSQL 示例:保留最近 30 天的 checkpoint
# DELETE FROM checkpoints WHERE metadata->>'timestamp' < NOW() - INTERVAL '30 days';
# 策略 2:Redis 利用原生 TTL(最省心)
# RedisSaver 支持设置过期时间,自动清理旧会话
# 策略 3:SummarizationMiddleware 压缩长对话
from langchain.agents.middleware import SummarizationMiddleware
from langchain_openai import ChatOpenAI
summary_middleware = SummarizationMiddleware(
model=ChatOpenAI(model="gpt-4o-mini"),
trigger=("messages", 100) # 消息数达到 100 条时触发压缩
)
七、总结速查表
| 场景 | 推荐后端 | 关键代码 | 注意事项 |
|---|---|---|---|
| 本地开发/测试 | InMemorySaver |
checkpointer = InMemorySaver() |
进程结束数据丢失 |
| 单机原型/小工具 | SqliteSaver |
SqliteSaver.from_conn_string("x.db") |
不支持多进程并发 |
| 生产环境/分布式 | AsyncPostgresSaver |
AsyncPostgresSaver.from_conn_string(url) |
首次调用 setup() 建表 |
| 短会话/自动清理 | RedisSaver |
RedisSaver.from_conn_string(url) |
利用 TTL 自动过期 |
| 多用户隔离 | 任意后端 + thread_id | config={"configurable":{"thread_id":"xxx"}} |
thread_id 必须唯一且稳定 |
| 崩溃恢复 | PostgreSQL/SQLite | app.invoke(None, config=config) |
传入 None 从 checkpoint 恢复 |
八、总结
Checkpointer 是 LangGraph Agent 的"记忆中枢" :
InMemorySaver让你快速启动,SqliteSaver让你单机持久化,PostgresSaver让你走向生产。无论选哪种后端,thread_id是隔离会话的唯一标识 ,而升级安全版本是生产部署的底线要求。