培歌行学LangGraph(14):启用可恢复执行(Checkpoint)

0 阅读6分钟
  1. 培歌行学LangGraph(1):从 LangChain 到 LangGraph 的思维跃迁]
  2. 培歌行学LangGraph(2):Reducer(归约器)彻底搞懂
  3. 培歌行学LangGraph(3):编译、执行与可视化
  4. 培歌行学LangGraph(4):一文搞懂图的状态(State)管理
  5. 培歌行学LangGraph(5):状态管理与graph.invoke入参深度解析
  6. 培歌行学LangGraph(6):Multi Schema多状态管理详解
  7. 培歌行学LangGraph(7):预定义状态MessagesState与AgentState
  8. 培歌行学LangGraph(8):控制流详解
  9. 培歌行学LangGraph(9):控制流详解:defer延迟节点——让收尾工作自动排到最后
  10. 培歌行学LangGraph(10):多分支汇聚Fan-in——多个分支如何汇聚到一起?
  11. 培歌行学LangGraph(11):用循环结构实现 ReAct Agent
  12. 培歌行学LangGraph(12):递归限制——别让你的图无限跑下去
  13. 培歌行学LangGraph(13):节点执行与容错机制

一、什么是可恢复执行?

可恢复执行指的是:图在执行过程中,每一步的状态都被保存下来。即使程序中断、服务器重启,也能从上次中断的地方继续运行。

这在以下场景中非常重要:

  • 多轮对话:用户说完话离开,回来还能接着聊
  • 长时间运行的任务:数据处理到一半宕机,恢复后继续
  • 人工审批流程:在某个节点等待审批,审批通过后继续执行

LangGraph 通过 检查点(Checkpoint) ​ 机制实现可恢复执行。


二、检查点(Checkpoint)是什么?

检查点是 图在某个时刻的完整状态快照,包括:

  • 当前 State 的所有字段值
  • 当前执行到哪个节点
  • 已执行过的节点历史

可以把检查点想象成游戏的存档

  • 你可以在任何时候存档
  • 读档后,游戏从存档点继续,而不是从头开始

三、启用可恢复执行的两步

3.1 第一步:编译时传入 Checkpointer

from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer)

3.2 第二步:调用时传入 thread_id

config = {"configurable": {"thread_id": "session_001"}}
graph.invoke(input_data, config=config)

**thread_id**​ 是会话的唯一标识。同一个 thread_id的多轮调用,会共享同一份状态历史。


四、检查点后端的选择

LangGraph 提供了多种检查点后端:

后端包名特点
InMemorySaverlanggraph-checkpoint内存存储,进程结束即丢失
SqliteSaverlanggraph-checkpoint-sqlite文件存储,轻量级持久化
PostgresSaverlanggraph-checkpoint-postgres数据库存储,生产环境首选
MongoDBSaverlanggraph-checkpoint-mongodbNoSQL 存储
RedisSaverlanggraph-checkpoint-redis高速缓存存储

五、基于内存的检查点(InMemorySaver)

5.1 特点

  • 数据保存在当前 Python 进程的内存中
  • 进程结束 → 数据丢失
  • Jupyter 内核不重启 → 数据保留

5.2 完整示例

from langgraph.checkpoint.memory import InMemorySaver

# 1. 创建检查点存储器
checkpointer = InMemorySaver()

# 2. 编译时传入
graph = builder.compile(checkpointer=checkpointer)

# 3. 定义 thread_id
config = {"configurable": {"thread_id": "session_001"}}

# 4. 多轮对话,共享状态
graph.invoke({"messages": [HumanMessage("你好,我是老王")]}, config=config)
graph.invoke({"messages": [HumanMessage("从现在开始,你是小王")]}, config=config)
res = graph.invoke({"messages": [HumanMessage("我是谁?你是谁?")]}, config=config)

print(res["output"])
# 输出:老王,我是小王啊!

5.3 注意事项

如果重新执行完整代码,InMemorySaver()实例被重新创建,历史检查点会被清空。

# 重新执行这行,之前的记忆就没了
checkpointer = InMemorySaver()

六、基于 PostgreSQL 的检查点(PostgresSaver)

6.1 特点

  • 数据保存在 PostgreSQL 数据库中
  • 程序重启 → 数据不丢
  • 适合生产环境

6.2 完整示例

from langgraph.checkpoint.postgres import PostgresSaver

# 1. 数据库连接 URL
DB_URL = "postgresql://langgraph_user:123456@localhost:5432/langgraph_db?sslmode=disable"

# 2. 创建检查点存储器
with PostgresSaver.from_conn_string(DB_URL) as checkpointer:
    # 初始化数据库表(实际项目中应作为独立步骤)
    checkpointer.setup()
    
    # 3. 编译时传入
    graph = builder.compile(checkpointer=checkpointer)
    
    # 4. 定义 thread_id
    config = {"configurable": {"thread_id": "session_002"}}
    
    # 5. 多轮对话
    graph.invoke({"messages": [HumanMessage("你好,我是老王")]}, config=config)
    graph.invoke({"messages": [HumanMessage("从现在开始,你是小王")]}, config=config)
    res = graph.invoke({"messages": [HumanMessage("我是谁?你是谁?")]}, config=config)
    
    print(res["output"])

6.3 程序重启后仍然有效

# 即使重新运行整个脚本,只要 thread_id 相同,历史还在
with PostgresSaver.from_conn_string(DB_URL) as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)
    config = {"configurable": {"thread_id": "session_002"}}
    
    # 模型还记得之前的对话
    res = graph.invoke({"messages": [HumanMessage("我刚才说了什么?")]}, config=config)
    # 输出:你说你是老王,让我叫你小王

七、InMemorySaver vs PostgresSaver

对比项InMemorySaverPostgresSaver
存储位置内存PostgreSQL 数据库
程序重启数据丢失数据保留
跨进程共享不支持支持
性能极快较快
适用场景开发调试生产环境
配置复杂度简单需要数据库

八、检查点的工作原理

调用 1(thread_id=001)
  → 执行节点 A → 保存检查点 1
  → 执行节点 B → 保存检查点 2
  → 执行节点 C → 保存检查点 3

调用 2(thread_id=001)
  → 从检查点 3 恢复状态
  → 执行节点 D → 保存检查点 4

调用 3(thread_id=002)
  → 新的会话,从头开始

每个检查点包含:

  • 当前 State
  • 父检查点 ID(形成链条)
  • 执行时间戳

九、面试题

面试题1:什么是检查点(Checkpoint)?它的作用是什么?

:检查点是图在某个时刻的完整状态快照,包含 State 的所有字段、当前执行位置和历史记录。它的作用是:

  1. 可恢复执行:程序中断后可以从上次中断处继续
  2. 多轮对话:同一个 thread_id 的多轮调用共享状态
  3. 状态回溯:可以回退到任意历史检查点

面试题2:InMemorySaverPostgresSaver的根本区别是什么?

:根本区别在于存储介质

  • InMemorySaver存储在内存中,进程结束即丢失
  • PostgresSaver存储在数据库中,程序重启后数据仍在

这也决定了它们的适用场景:前者适合开发调试,后者适合生产环境。

面试题3:thread_id的作用是什么?不传会怎样?

thread_id是会话的唯一标识,用于区分不同会话的状态。如果不传 thread_id,每次调用都会创建一个新的匿名会话,无法共享状态,也无法从历史检查点恢复。

面试题4:检查点机制如何实现多轮对话的记忆功能?

:流程如下:

  1. 第一轮调用:传入 thread_id=001,图执行完毕后保存检查点
  2. 第二轮调用:传入相同的 thread_id=001,LangGraph 从检查点恢复状态
  3. 新的消息追加到已有的消息列表中
  4. 模型看到完整的对话历史,从而实现"记忆"

面试题5:如果我想在生产环境中使用检查点,应该选择哪个后端?为什么?

:推荐使用 PostgresSaverRedisSaver。原因:

  1. 持久化:数据不会因为程序重启而丢失
  2. 可扩展:支持多进程、多服务共享状态
  3. 可靠性:数据库有完善的备份和恢复机制

InMemorySaver只适合开发和调试,不适合生产环境。


检查点就像是给你的图装上了"时光机"。 ​ 有了它,你的 Agent 不再是"一次性"的,而是可以记住过去、延续对话、甚至在故障后原地复活。