LangGraph Agent Checkpointer 持久化完全指南:从内存到生产的实战

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),包括 messagestool_callsintermediate_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,自动加载历史
)

注意InMemorySaverMemorySaver 的别名,新代码建议统一使用 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 是隔离会话的唯一标识 ,而升级安全版本是生产部署的底线要求。


相关推荐
2601_956319883 小时前
2026年用示例、拆解和练习提升量化理解效率
人工智能·python
王志来137944730083 小时前
从分散到集成:工控服务器机箱采购如何实现“一站式”破局
运维·服务器·人工智能·python
三十岁老牛再出发4 小时前
08.18每日总结
c++·python·numpy·pandas
️学习的小王4 小时前
Anaconda国内镜像配置与conda常用命令
ide·经验分享·python·conda
Zane19944 小时前
daemon 线程说没就没?一文讲透 threading 的适用场景与线程安全
后端·python
up up day4 小时前
Python enchant 模块使用教程
python
zoujiahui_20184 小时前
Python 包与环境管理工具 uv
开发语言·python·uv
程序猿阿森5 小时前
Python 闭包与装饰器:从入门到精通
开发语言·python·面试
老大白菜5 小时前
Qwen3.8-27B 本地推理 + DeepSeek Harness 配置
python·qwen·deepseek·harness
Code额5 小时前
Python 连接 DeepSeek API,OpenAI 对话方式总结
后端·python·ai·ai编程