LangGraph Checkpointer:持久化、断点续跑与多轮记忆

个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

  • [LangGraph Checkpointer:持久化、断点续跑与多轮记忆](#LangGraph Checkpointer:持久化、断点续跑与多轮记忆)
    • [一、Checkpointer 做了什么](#一、Checkpointer 做了什么)
    • 二、最小可用:内存版
    • [三、生产:SQLite / Postgres](#三、生产:SQLite / Postgres)
      • [3.1 SQLite(单机、轻量)](#3.1 SQLite(单机、轻量))
      • [3.2 Postgres(生产推荐)](#3.2 Postgres(生产推荐))
    • [四、断点续跑:interrupt + resume](#四、断点续跑:interrupt + resume)
    • 五、查看和管理历史
      • [5.1 拿到当前状态](#5.1 拿到当前状态)
      • [5.2 遍历历史](#5.2 遍历历史)
      • [5.3 从历史快照分叉(时间旅行)](#5.3 从历史快照分叉(时间旅行))
      • [5.4 手动改状态](#5.4 手动改状态)
    • [六、Checkpointer 与消息裁剪](#六、Checkpointer 与消息裁剪)
    • 七、常见坑
    • 八、小结

LangGraph Checkpointer:持久化、断点续跑与多轮记忆

图跑完状态就没了------这在 demo 里没问题,在生产里是硬伤:

用户刷新页面对话断了、人工审批要等一天、服务重启任务丢了。

Checkpointer 解决的就是"让图的状态活下来 "。

这篇讲清楚它怎么用,以及 thread_id 这个关键概念。

一、Checkpointer 做了什么

简单说:每执行完一个节点,就把当前 State 快照存一份。

复制代码
节点 A 执行完 → 存快照 1
节点 B 执行完 → 存快照 2
节点 C 执行完 → 存快照 3

有了这些快照,你就能:

  • 继续:同一个会话的下一次输入,从最新快照继续;
  • 回溯:拿到历史上任意一个快照;
  • 恢复 :interrupt 暂停后,隔多久都能接着跑;
  • 分叉:从某个历史快照复制出一条新分支(做"如果当时......"的实验)。

二、最小可用:内存版

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent

checkpointer = InMemorySaver()
agent = create_react_agent(llm, tools, checkpointer=checkpointer)

config = {"configurable": {"thread_id": "user-123"}}

agent.invoke({"messages": [("user", "我叫小明")]}, config=config)
agent.invoke({"messages": [("user", "我叫什么?")]}, config=config)
# → "你叫小明"

关键就在 thread_id。

  • 同一个 thread_id → 共享同一份状态,多轮对话有记忆;
  • 不同 thread_id → 完全独立的会话。

InMemorySaver 只存在进程内存里,重启即丢。

只适合开发调试。生产必须换成持久化的实现。

三、生产:SQLite / Postgres

3.1 SQLite(单机、轻量)

bash 复制代码
pip install langgraph-checkpoint-sqlite
python 复制代码
from langgraph.checkpoint.sqlite import SqliteSaver

# 方式一:文件路径,需手动管理连接
saver = SqliteSaver.from_conn_string("checkpoints.db")

# 方式二:配合 sqlite3 连接(推荐,可设 check_same_thread=False)
import sqlite3
conn = sqlite3.connect("checkpoints.db", check_same_thread=False)
saver = SqliteSaver(conn)

agent = create_react_agent(llm, tools, checkpointer=saver)

SQLite 适合单机部署、中小规模 。优点是不需要额外服务,

缺点是写操作串行,并发高了会争锁。

3.2 Postgres(生产推荐)

bash 复制代码
pip install langgraph-checkpoint-postgres
python 复制代码
from langgraph.checkpoint.postgres import PostgresSaver

DB_URI = "postgresql://user:pwd@localhost:5432/agentdb"

with PostgresSaver.from_conn_string(DB_URI) as saver:
    saver.setup()          # 第一次要建表
    agent = create_react_agent(llm, tools, checkpointer=saver)
    ...

生产选 Postgres 的理由和向量库那篇一样:

备份、监控、扩容都复用现有体系,不用再维护一个新组件。

四、断点续跑:interrupt + resume

这是 Checkpointer 最有价值的用法。结合上一篇讲的人工介入:

python 复制代码
from langgraph.types import interrupt, Command

def sensitive_node(state):
    # 暂停,等待外部决策
    decision = interrupt({"action": "refund", "amount": 299.0})
    if decision == "approve":
        return {"result": do_refund()}
    return {"result": "用户已拒绝"}

调用流程:

python 复制代码
config = {"configurable": {"thread_id": "order-888"}}

# 第一次调用:跑到 interrupt 就停
result = agent.invoke({"messages": [("user", "我要退款")]}, config=config)
print(result.get("__interrupt__"))
# [Interrupt(value={'action': 'refund', 'amount': 299.0})]

# ------ 此时可以关掉服务、过一天再回来 ------

# 恢复执行
result = agent.invoke(Command(resume="approve"), config=config)

注意第二次 invoke 传的不是新的用户输入,而是 Command(resume=...)。

只要 thread_id 一致,LangGraph 就能从快照里恢复出暂停时的状态继续跑。

这个能力支撑了非常实际的场景:

退款审批、合同确认、大额下单 ------图可以挂起几天,

人工在后台点完"同意",流程自动往下走。

五、查看和管理历史

5.1 拿到当前状态

python 复制代码
snapshot = agent.get_state(config)
print(snapshot.values)      # 当前 State 内容
print(snapshot.next)        # 下一步会执行哪些节点
print(snapshot.tasks)       # 待办(含 interrupt 信息)

5.2 遍历历史

python 复制代码
for snap in agent.get_state_history(config):
    print(snap.config["configurable"]["checkpoint_id"], snap.next)

5.3 从历史快照分叉(时间旅行)

python 复制代码
history = list(agent.get_state_history(config))
old = history[2]            # 回到第 3 个快照

# 用旧的 checkpoint_id 构造新 config
forked_config = {
    "configurable": {
        "thread_id": "user-123",
        "checkpoint_id": old.config["configurable"]["checkpoint_id"],
    }
}

# 在新分支上继续跑,不影响原分支
agent.invoke({"messages": [("user", "换个思路重新分析")]}, config=forked_config)

"分叉"在调试时特别有用 :跑到一半发现走错了,

退回去改个输入重新跑,原分支还留着可以对比。

5.4 手动改状态

python 复制代码
agent.update_state(config, {"answer": "手动修正后的答案"})

直接覆写某个字段,然后继续跑。适合人工修正 Agent 的错误输出。

六、Checkpointer 与消息裁剪

有个实际问题:状态存久了,消息列表会越来越长 ,

每次加载都要把所有历史消息塞进 prompt,成本和延迟都会涨。

解法是定期裁剪,只保留最近 N 条:

python 复制代码
from langchain_core.messages import AnyMessage
from langgraph.graph.message import add_messages, RemoveMessage

MAX_MESSAGES = 20

def trim_messages(state):
    msgs = state["messages"]
    if len(msgs) <= MAX_MESSAGES:
        return {}
    # 删掉最老的,保留最近 MAX_MESSAGES 条
    to_remove = [RemoveMessage(id=m.id) for m in msgs[:-MAX_MESSAGES]]
    return {"messages": to_remove}

在图里加一个节点做这件事:

python 复制代码
graph.add_node("trim", trim_messages)
graph.add_edge(START, "trim")
graph.add_edge("trim", "agent")

注意别把系统消息和用户的最新输入裁掉,否则会出奇怪的行为。

另一个思路是摘要 :历史太长时用 LLM 压成一段摘要,

替换掉原始消息。LangChain 有现成的 summarize_messages。

七、常见坑

现象 原因 解法
多轮对话没记忆 每次 thread_id 不一样 用同一个稳定 id(如用户 ID)
服务重启后记忆丢失 用了 InMemorySaver 换 Sqlite / Postgres
resume 报找不到线程 thread_id 变了 两次调用必须一致
状态越来越大、越来越慢 消息无限累积 定期裁剪或摘要
Postgres 报缺表 没调 setup() 首次运行执行 saver.setup()
SQLite 报线程错误 多线程共享连接 check_same_thread=False

八、小结

  • Checkpointer 每节点存一份 State 快照,支撑继续 / 回溯 / 恢复 / 分叉;
  • thread_id 是唯一关键:相同 id 共享会话,不同 id 完全隔离;
  • InMemorySaver 只用于调试,生产用 SqliteSaver 或 PostgresSaver;
  • 断点续跑 = interrupt() 暂停 + Command(resume=...) 恢复,
    配合持久化可以挂起几天;
  • get_state / get_state_history / update_state 三个 API 用于查看和管理;
  • 消息会无限累积,务必做裁剪或摘要,否则成本和延迟会失控。

下一篇讲部署上线------把链或 Agent 变成一个 HTTP 服务。

相关推荐
Data analyse4562 小时前
用户路径SQL按会话起止时间聚合的方法
大数据·服务器·前端·数据库·人工智能·sql·数据分析
Lhan.zzZ2 小时前
Linux + Qt/QML:解决 QtQuick.VirtualKeyboard.Plugins 模块加载失败与环境变量配置
linux·数据库·qt
大圣编蚕2 小时前
Redis 为什么快?现在还是单线程吗?
数据库·redis·缓存
学电子她就能回来吗2 小时前
LeetCode Hot 100|滑动窗口专题零基础学习笔记
笔记·学习·leetcode
鸥梨菌Honevid2 小时前
UVM 面试高频:uvm_test_top 与 uvm_config_db 路径机制全解析
数据库·面试·职场和发展
我先去打把游戏先2 小时前
Git-Rebase使用指南-从原理到实战
服务器·git·嵌入式硬件·物联网·ubuntu
极小狐2 小时前
MR 里测试摘要空白、覆盖率不显示?JUnit 与覆盖率配置 9 步排查
数据库·ci/cd·junit·单元测试·mr·devops·代码覆盖率
silvamux_2 小时前
MiniMax API 调用教程:兼容 OpenAI 写法
java·数据库·elasticsearch
zh73142 小时前
sqlforge一个SQL 格式化与方言转换工具
数据库·sql