培歌行学LangGraph(17):检查点的四大使用场景

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):节点执行与容错机制
  14. 培歌行学LangGraph(14):启用可恢复执行(Checkpoint)
  15. 培歌行学LangGraph(15):持久化模式——何时保存检查点?
  16. 培歌行学LangGraph(16):持久化模式——查看历史检查点——你的图走过的每一步都有记录

一、多轮对话(基础用法)

这个最简单,前面已经讲过了。就是同一个 thread_id下多次调用,模型能记住之前说过的话。

# 第一轮
graph.invoke({"messages": [HumanMessage("你好,我是老王")]}, config=config)
# 第二轮:模型还记得你是老王
graph.invoke({"messages": [HumanMessage("我是谁?")]}, config=config)

二、失败后恢复运行

2.1 这是什么场景?

你的图跑到一半,某个节点挂了(比如网络波动、API 超时)。修复问题后,你希望从断点继续跑,而不是从头开始。

2.2 怎么做?

三步走:

  1. 图挂了:某个节点抛出异常,图停止
  2. 修BUG:修复出问题的节点代码
  3. 恢复运行:用 None作为输入,传入同一个 thread_id
# 恢复运行:输入传 None,config 只传 thread_id
res = graph.invoke(None, config=config)

2.3 举个栗子

假设你的图是这样的:

START → change_topic → 写诗 → 汇总 → END
                    ↘ 讲笑话 ↗

第一次运行时,讲笑话节点抛异常了。修复后,恢复运行:

# 恢复运行
res = graph.invoke(None, config=config)

你会发现:

  • 写诗节点没有重新执行(结果被复用了)
  • 讲笑话节点重新执行了(因为之前失败了)
  • 汇总节点重新执行了(因为需要新的笑话结果)

2.4 关键点

条件说明
输入传 None告诉 LangGraph "我要恢复,不是新任务"
只传 thread_id不传 checkpoint_id,自动从最新检查点恢复
检查点后端要持久化如果用 InMemorySaver,进程重启就丢了

三、Time Travel(检查点回溯)

3.1 这是什么?

Time Travel 就是回到过去的某个检查点,重新执行后面的流程

有两种玩法:

玩法英文说明
重放Replay回到过去,原封不动重新跑一遍
分叉Fork回到过去,改点东西,跑一条新路线

3.2 Replay:重放

3.2.1 怎么做?

# 1. 拿到所有历史检查点
history = list(graph.get_state_history(config=config))

# 2. 选一个你想重放的检查点(比如写诗之前的那个)
target = None
for h in history:
    if h.next == ('node_poem', 'node_joke'):
        target = h
        break

# 3. 重放:输入传 None,config 传这个检查点的 config
res = graph.invoke(None, config=target.config)

3.2.2 关键点

  • 输入传 None:和失败恢复一样
  • config 里带 checkpoint_id:告诉 LangGraph "从哪个存档点开始"
  • 后面的节点会重新执行:LLM 调用、API 请求都会重新触发
  • 前面的节点不会重新执行:比如 change_topic不会再跑一遍

3.3 Fork:分叉

3.3.1 这是什么?

回到某个历史检查点,修改状态,然后跑一条新的执行分支

比如:

  • 用户说"写首诗",模型写了诗
  • 你想试试:"如果当时用户说的是'讲个笑话',结果会怎样?"
  • 回到那个检查点,改一下输入,重新跑

3.3.2 怎么做?

# 1. 找到你想分叉的检查点(比如 router_node 执行之前的那个)
before_router = next(h for h in history if h.next == ('router_node',))

# 2. 修改状态,创建分叉
fork_config = graph.update_state(
    config=before_router.config,
    values={"user_input": "帮我写一个关于布偶猫的笑话"},
    as_node=START  # 把这个修改当作 START 的输出
)

# 3. 从分叉点继续运行
res = graph.invoke(None, fork_config)

3.3.3 as_node是什么意思?

as_node告诉 LangGraph:"我这个修改,假装是某个节点的输出"。

as_node的值效果
START重新执行所有节点
"router_node"跳过 router_node,直接从它的后继节点开始
"node_poem"跳过前面的所有节点,直接从 poem 开始

3.3.4 两种分叉方式对比

方式代码效果
只改状态as_node=START重新执行 router_node
改状态+跳过节点as_node="router_node"跳过 router_node,伪造它的输出

方式一:只改状态

fork_config = graph.update_state(
    config=before_router.config,
    values={"user_input": "帮我写一个关于布偶猫的笑话"},
    as_node=START
)

效果:user_input变了,router_node重新执行,根据新的输入重新路由。

方式二:改状态+跳过节点

fork_config = graph.update_state(
    config=before_router.config,
    values={"topic": "狸花猫", "mode": "joke"},
    as_node="router_node"
)

效果:router_node不执行了,直接伪造它的输出。路由函数会根据新的 mode决定走向。


四、四种场景总结

场景输入config效果
多轮对话新消息同 thread_id模型记得历史
失败恢复None同 thread_id从断点继续,成功节点不重跑
ReplayNonethread_id + checkpoint_id从历史点重跑,后面的节点全重跑
ForkNoneupdate_state 返回的 config修改状态后跑一条新分支

五、面试题

面试题1:失败恢复时,输入为什么要传 None

:传 None告诉 LangGraph "我不是要开启一个新任务,而是要恢复之前的任务"。LangGraph 会从检查点读取历史状态,而不是用新的输入覆盖它。

面试题2:失败恢复时,已经成功的节点会重新执行吗?

:不会。LangGraph 会把已成功节点的结果保存为 pending_writes,恢复时直接复用。只有失败的节点和它后面的节点会重新执行。

面试题3:Replay 和失败恢复有什么区别?

  • 失败恢复:从最新检查点恢复,config 里只有 thread_id
  • Replay:从指定历史检查点恢复,config 里有 thread_id+ checkpoint_id

失败恢复是"接着跑",Replay 是"回去重新跑"。

面试题4:Fork 时 as_node参数的作用是什么?

as_node告诉 LangGraph "我这个修改,假装是某个节点的输出"。这决定了:

  1. 哪些节点会被跳过(as_node之前的节点不执行)
  2. 路由函数会不会被触发(如果 as_node有条件边,路由函数会执行)

面试题5:Fork 和 Replay 的根本区别是什么?

  • Replay:不改状态,原封不动重跑后面的节点
  • Fork:改了状态,跑一条新的执行分支

Replay 是"再看一遍电影",Fork 是"改个结局重拍"。


检查点就像你图的"存档系统"。 ​ 失败了可以读档重来,想试试不同选择可以分叉存档。用好这些功能,你的图就不再是一次性的脚本,而是一个可以反复调试、不断优化的可靠系统。