文章目录
-
- [第 5 章:Checkpoint 持久化与状态管理](#第 5 章:Checkpoint 持久化与状态管理)
-
- [5.1 本章目标](#5.1 本章目标)
- [5.2 核心概念](#5.2 核心概念)
-
- 持久化三要素
- [Checkpoint 生命周期](#Checkpoint 生命周期)
- [Thread 隔离模型](#Thread 隔离模型)
- [三种 Checkpointer 对比](#三种 Checkpointer 对比)
- [5.3 实战](#5.3 实战)
-
- [实战 1:多会话隔离](#实战 1:多会话隔离)
- [实战 2:断点续传](#实战 2:断点续传)
- [实战 3:状态历史回溯](#实战 3:状态历史回溯)
- [实战 4:update_state 手动修改状态](#实战 4:update_state 手动修改状态)
- [5.4 API 速查](#5.4 API 速查)
- [5.5 错误与避坑指南](#5.5 错误与避坑指南)
-
- [坑 1:忘记传入 config](#坑 1:忘记传入 config)
- [坑 2:混淆 config 和 input](#坑 2:混淆 config 和 input)
- [坑 3:以为 update_state 是覆盖](#坑 3:以为 update_state 是覆盖)
- [坑 4:生产环境用 MemorySaver](#坑 4:生产环境用 MemorySaver)
- [5.6 最佳实践总结](#5.6 最佳实践总结)
第 5 章:Checkpoint 持久化与状态管理

5.1 本章目标
学完本章你将能够:
- 理解 LangGraph 持久化模型(Checkpoint、Thread、Super-step)
- 掌握 MemorySaver / SqliteSaver / PostgresSaver 的选择和使用
- 学会 get_state / update_state / get_state_history 的用法
- 实现多会话隔离和状态回溯
5.2 核心概念
持久化三要素
| 概念 | 说明 | 比喻 |
|---|---|---|
| Checkpoint | 每个 super-step 后自动保存的状态快照 | 游戏存档------每一步操作后自动保存 |
| Thread | 通过 thread_id 标识的独立对话线程 |
游戏存档槽------不同存档互不干扰 |
| Super-step | 图的一次完整"滴答"(该步内所有并行节点执行完毕) | 游戏中的一回合 |
Checkpoint 生命周期
#mermaid-svg-QSoW6OTMiMyAXmMe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QSoW6OTMiMyAXmMe .error-icon{fill:#552222;}#mermaid-svg-QSoW6OTMiMyAXmMe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QSoW6OTMiMyAXmMe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QSoW6OTMiMyAXmMe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QSoW6OTMiMyAXmMe .marker.cross{stroke:#333333;}#mermaid-svg-QSoW6OTMiMyAXmMe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QSoW6OTMiMyAXmMe p{margin:0;}#mermaid-svg-QSoW6OTMiMyAXmMe .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster-label text{fill:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster-label span{color:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster-label span p{background-color:transparent;}#mermaid-svg-QSoW6OTMiMyAXmMe .label text,#mermaid-svg-QSoW6OTMiMyAXmMe span{fill:#333;color:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .node rect,#mermaid-svg-QSoW6OTMiMyAXmMe .node circle,#mermaid-svg-QSoW6OTMiMyAXmMe .node ellipse,#mermaid-svg-QSoW6OTMiMyAXmMe .node polygon,#mermaid-svg-QSoW6OTMiMyAXmMe .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QSoW6OTMiMyAXmMe .rough-node .label text,#mermaid-svg-QSoW6OTMiMyAXmMe .node .label text,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape .label,#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape .label{text-anchor:middle;}#mermaid-svg-QSoW6OTMiMyAXmMe .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QSoW6OTMiMyAXmMe .rough-node .label,#mermaid-svg-QSoW6OTMiMyAXmMe .node .label,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape .label,#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape .label{text-align:center;}#mermaid-svg-QSoW6OTMiMyAXmMe .node.clickable{cursor:pointer;}#mermaid-svg-QSoW6OTMiMyAXmMe .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QSoW6OTMiMyAXmMe .arrowheadPath{fill:#333333;}#mermaid-svg-QSoW6OTMiMyAXmMe .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QSoW6OTMiMyAXmMe .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QSoW6OTMiMyAXmMe .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QSoW6OTMiMyAXmMe .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QSoW6OTMiMyAXmMe .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QSoW6OTMiMyAXmMe .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster text{fill:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe .cluster span{color:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QSoW6OTMiMyAXmMe .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QSoW6OTMiMyAXmMe rect.text{fill:none;stroke-width:0;}#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape p,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QSoW6OTMiMyAXmMe .icon-shape .label rect,#mermaid-svg-QSoW6OTMiMyAXmMe .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QSoW6OTMiMyAXmMe .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QSoW6OTMiMyAXmMe .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QSoW6OTMiMyAXmMe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 可回溯
可回溯
可回溯
START
Step 1: agent 节点执行
Checkpoint 1
保存 State 快照
Step 2: tools 节点执行
Checkpoint 2
保存 State 快照
Step 3: agent 节点执行
Checkpoint 3
保存 State 快照
END
Thread 隔离模型
#mermaid-svg-fo7FLyfNxZjijxGj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fo7FLyfNxZjijxGj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fo7FLyfNxZjijxGj .error-icon{fill:#552222;}#mermaid-svg-fo7FLyfNxZjijxGj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fo7FLyfNxZjijxGj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fo7FLyfNxZjijxGj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fo7FLyfNxZjijxGj .marker.cross{stroke:#333333;}#mermaid-svg-fo7FLyfNxZjijxGj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fo7FLyfNxZjijxGj p{margin:0;}#mermaid-svg-fo7FLyfNxZjijxGj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster-label text{fill:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster-label span{color:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster-label span p{background-color:transparent;}#mermaid-svg-fo7FLyfNxZjijxGj .label text,#mermaid-svg-fo7FLyfNxZjijxGj span{fill:#333;color:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .node rect,#mermaid-svg-fo7FLyfNxZjijxGj .node circle,#mermaid-svg-fo7FLyfNxZjijxGj .node ellipse,#mermaid-svg-fo7FLyfNxZjijxGj .node polygon,#mermaid-svg-fo7FLyfNxZjijxGj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fo7FLyfNxZjijxGj .rough-node .label text,#mermaid-svg-fo7FLyfNxZjijxGj .node .label text,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape .label,#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape .label{text-anchor:middle;}#mermaid-svg-fo7FLyfNxZjijxGj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fo7FLyfNxZjijxGj .rough-node .label,#mermaid-svg-fo7FLyfNxZjijxGj .node .label,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape .label,#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape .label{text-align:center;}#mermaid-svg-fo7FLyfNxZjijxGj .node.clickable{cursor:pointer;}#mermaid-svg-fo7FLyfNxZjijxGj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fo7FLyfNxZjijxGj .arrowheadPath{fill:#333333;}#mermaid-svg-fo7FLyfNxZjijxGj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fo7FLyfNxZjijxGj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fo7FLyfNxZjijxGj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fo7FLyfNxZjijxGj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fo7FLyfNxZjijxGj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fo7FLyfNxZjijxGj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fo7FLyfNxZjijxGj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster text{fill:#333;}#mermaid-svg-fo7FLyfNxZjijxGj .cluster span{color:#333;}#mermaid-svg-fo7FLyfNxZjijxGj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fo7FLyfNxZjijxGj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fo7FLyfNxZjijxGj rect.text{fill:none;stroke-width:0;}#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape p,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fo7FLyfNxZjijxGj .icon-shape .label rect,#mermaid-svg-fo7FLyfNxZjijxGj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fo7FLyfNxZjijxGj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fo7FLyfNxZjijxGj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fo7FLyfNxZjijxGj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Checkpointer
Thread: user-002
Thread: user-001
thread_id=user-001
thread_id=user-002
消息1
消息2
消息3
消息1
消息2
MemorySaver
或 SqliteSaver
或 PostgresSaver
三种 Checkpointer 对比
| 实现 | 数据存储 | 适用场景 | 优缺点 |
|---|---|---|---|
MemorySaver |
内存 | 开发/测试 | 快、简单,但重启丢失 |
SqliteSaver |
SQLite 文件 | 本地开发/小规模部署 | 持久化、无需外部服务 |
PostgresSaver |
PostgreSQL | 生产环境 | 高可用、支持并发、持久化 |
5.3 实战
实战 1:多会话隔离
python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END, add_messages
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
user_name: str
def remember_name(state: State) -> dict:
"""从消息中提取用户名"""
last_msg = state["messages"][-1]
if "叫" in last_msg.content:
name = last_msg.content.split("叫")[-1].strip("。! ")
return {"user_name": name, "messages": [AIMessage(content=f"好的,我记住了,你叫{name}!")]}
return {"messages": [AIMessage(content="你好!")]}
# 创建 checkpointer
checkpointer = MemorySaver()
builder = StateGraph(State)
builder.add_node("remember", remember_name)
builder.add_edge(START, "remember")
builder.add_edge("remember", END)
graph = builder.compile(checkpointer=checkpointer)
# ============================================
# 两个独立的会话
# ============================================
# 会话 1:小明
config1 = {"configurable": {"thread_id": "user-001"}}
result1 = graph.invoke(
{"messages": [HumanMessage(content="我叫小明")], "user_name": ""},
config=config1,
)
print(f"会话1: {result1['user_name']}") # 小明
# 会话 2:小红
config2 = {"configurable": {"thread_id": "user-002"}}
result2 = graph.invoke(
{"messages": [HumanMessage(content="我叫小红")], "user_name": ""},
config=config2,
)
print(f"会话2: {result2['user_name']}") # 小红
# 验证隔离:回到会话1,状态仍然是小明
snapshot = graph.get_state(config1)
print(f"会话1 状态: {snapshot.values['user_name']}") # 小明(不受会话2影响)
实战 2:断点续传
说明:下面的示例展示了正常执行流程。真正的断点续传场景是:执行到中途程序崩溃/服务重启,下一次调用时从上次 checkpoint 自动恢复。只要配置了 checkpointer,这个恢复过程是自动的,不需要额外代码。
python
from typing import TypedDict, Annotated
import operator
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
class State(TypedDict):
step: int
data: Annotated[list[str], operator.add]
def step_one(state: State) -> dict:
print(f" 执行步骤 1...")
return {"step": 1, "data": ["步骤1完成"]}
def step_two(state: State) -> dict:
print(f" 执行步骤 2...")
return {"step": 2, "data": ["步骤2完成"]}
def step_three(state: State) -> dict:
print(f" 执行步骤 3...")
return {"step": 3, "data": ["步骤3完成"]}
checkpointer = MemorySaver()
builder = StateGraph(State)
builder.add_node("step1", step_one)
builder.add_node("step2", step_two)
builder.add_node("step3", step_three)
builder.add_edge(START, "step1")
builder.add_edge("step1", "step2")
builder.add_edge("step2", "step3")
builder.add_edge("step3", END)
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "thread-resume"}}
# 执行(如果在某步中断,下次调用会从上次中断处继续)
result = graph.invoke({"step": 0, "data": []}, config=config)
print(f"最终 step: {result['step']}, data: {result['data']}")
# 查看状态快照
snapshot = graph.get_state(config)
print(f"当前状态: step={snapshot.values['step']}, next={snapshot.next}") # next=() 表示已完成
实战 3:状态历史回溯
python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END, add_messages
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
def chat(state: State) -> dict:
last_msg = state["messages"][-1]
return {"messages": [AIMessage(content=f"回复: {last_msg.content}")]}
checkpointer = MemorySaver()
builder = StateGraph(State)
builder.add_node("chat", chat)
builder.add_edge(START, "chat")
builder.add_edge("chat", END)
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "history-demo"}}
# 多轮对话
for i, msg in enumerate(["你好", "今天天气不错", "再见"]):
graph.invoke({"messages": [HumanMessage(content=msg)]}, config=config)
# 查看所有历史状态
print("=== 对话历史 ===")
history = list(graph.get_state_history(config))
for i, snapshot in enumerate(reversed(history)):
msgs = snapshot.values.get("messages", [])
print(f" Checkpoint {i}: {len(msgs)} 条消息, step={snapshot.metadata.get('step')}")
# 回溯到第 2 个 checkpoint
if len(history) >= 2:
checkpoint_2 = history[-2] # 倒数第 2 个
cp_id = checkpoint_2.config["configurable"]["checkpoint_id"]
replay_config = {"configurable": {"thread_id": "history-demo", "checkpoint_id": cp_id}}
replay_state = graph.get_state(replay_config)
print(f"\n回溯到 checkpoint 2:")
for msg in replay_state.values.get("messages", []):
print(f" [{msg.type}] {msg.content}")
实战 4:update_state 手动修改状态
python
# ... 接上面代码 ...
# 手动更新状态:在 thread 中插入一条修正消息
graph.update_state(
config=config,
values={"messages": [AIMessage(content="【已修正】之前的回复有误,正确回复是...")]},
as_node="chat", # 标记为来自 chat 节点
)
# 验证
snapshot = graph.get_state(config)
print(f"修正后消息数: {len(snapshot.values['messages'])}")
5.4 API 速查
| API | 完整签名 | 入参说明 | 返回值 | 说明 |
|---|---|---|---|---|
MemorySaver() |
MemorySaver() |
无 | Checkpointer |
内存存储(开发用) |
SqliteSaver.from_conn_string(path) |
from_conn_string(path: str) |
path: SQLite 文件路径 |
Checkpointer |
SQLite 持久化 |
PostgresSaver(conn) |
PostgresSaver(conn) |
conn: psycopg2 连接 |
Checkpointer |
Postgres 持久化 |
compile(checkpointer=...) |
compile(checkpointer: Checkpointer) |
checkpointer: 检查点保存器 |
CompiledGraph |
编译时注入持久化 |
.get_state(config) |
get_state(config: dict) |
config: {"configurable": {"thread_id": "..."}} |
StateSnapshot |
获取最新状态 |
.get_state_history(config) |
get_state_history(config: dict) |
config: 同上 |
Iterator[StateSnapshot] |
获取历史快照 |
.update_state(config, values) |
update_state(config, values, as_node) |
config: 配置; values: 更新值; as_node: 来源节点名(可选) |
dict |
手动更新状态 |
config 参数 |
{"configurable": {"thread_id": str}} |
thread_id: 线程唯一标识 |
配置字典 | 所有方法都需要传入 |
5.5 错误与避坑指南
坑 1:忘记传入 config
python
# ❌ 错误写法
graph.invoke({"messages": [...]}) # 没有 config!
# → 使用默认 thread_id,所有调用共享同一个线程
# ✅ 正确写法
config = {"configurable": {"thread_id": "user-123"}}
graph.invoke({"messages": [...]}, config=config)
坑 2:混淆 config 和 input
python
# ❌ 错误写法
# 把 thread_id 放在 input 里
graph.invoke({"thread_id": "user-123", "messages": [...]})
# ✅ 正确写法
# config 和 input 是分开的!
config = {"configurable": {"thread_id": "user-123"}}
input_data = {"messages": [...]}
graph.invoke(input_data, config=config)
坑 3:以为 update_state 是覆盖
python
# ❌ 误解
graph.update_state(config, {"messages": [new_msg]})
# 以为会用 new_msg 替换所有 messages
# 实际:add_messages Reducer 将 new_msg 追加到现有消息
# ✅ 正确理解
# update_state 的值会通过 Reducer 合并,并非直接覆盖
# 如果 State 字段有 Reducer(如 add_messages),会按 Reducer 逻辑合并
坑 4:生产环境用 MemorySaver
python
# ❌ 错误:生产环境
checkpointer = MemorySaver() # 服务重启后所有对话状态丢失!
# ✅ 正确:生产环境
from langgraph.checkpoint.postgres import PostgresSaver
import psycopg2
conn = psycopg2.connect(os.environ["DATABASE_URL"])
checkpointer = PostgresSaver(conn)
checkpointer.setup() # 初始化表结构
5.6 最佳实践总结
- 开发用 MemorySaver,生产用 PostgresSaver:根据环境选择合适的持久化方案
- 每个用户/会话使用唯一
thread_id:如f"user-{user_id}"或f"session-{uuid.uuid4()}" - 定期清理旧 checkpoint:避免存储无限膨胀(生产环境可设置 TTL)
- 使用
get_state_history实现"撤销"功能:用户可以回到之前的对话状态 update_state用于人工修正:当 AI 输出有误时,可以手动插入修正消息