写在前面
前面的文章里,Agent 执行完就"失忆"了------所有状态都丢了。但实际应用里,我们经常需要:
- 聊天机器人记住之前的对话
- 任务失败后从断点继续
- 敏感操作前暂停,等人审批
这些都需要持久化。
这篇文章通过一个聊天机器人示例,讲解持久化的核心用法。
一、启用持久化
1.1 基本用法
第 1 步:创建 Checkpointer
python
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
第 2 步:编译时传入
python
app = graph.compile(checkpointer=checkpointer)
第 3 步:执行时传入 config
python
config = {"configurable": {"thread_id": "conversation-1"}}
result = app.invoke(input_data, config=config)
1.2 完整示例
python
from typing import TypedDict, Annotated
from operator import add
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
# 定义 State
class State(TypedDict):
messages: Annotated[list, add]
# 创建模型
model = ChatOpenAI(model="gpt-4")
# 定义节点
def chat_node(state: State) -> dict:
response = model.invoke(state["messages"])
return {"messages": [response]}
# 构建图
graph = StateGraph(State)
graph.add_node("chat", chat_node)
graph.set_entry_point("chat")
graph.add_edge("chat", END)
# 启用持久化
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)
# 使用
config = {"configurable": {"thread_id": "conversation-1"}}
result = app.invoke(
{"messages": [HumanMessage(content="你好")]},
config=config
)
关键点:
- 创建 Checkpointer
- 编译时传入
checkpointer参数 - 执行时传入
config参数,包含thread_id
二、Thread:会话隔离
2.1 为什么需要 Thread
Thread 是会话的隔离单位。每个 Thread 有独立的 State 和 Checkpoint 历史。
没有 Thread 的问题:
python
# ❌ 错误:所有人的对话混在一起
app.invoke({"messages": ["你好"]}) # 用户 A
app.invoke({"messages": ["你好"]}) # 用户 B,会看到用户 A 的对话
使用 Thread 隔离:
python
# ✅ 正确:每个会话独立
config_a = {"configurable": {"thread_id": "user-a"}}
config_b = {"configurable": {"thread_id": "user-b"}}
app.invoke({"messages": ["你好"]}, config=config_a) # 用户 A
app.invoke({"messages": ["你好"]}, config=config_b) # 用户 B,看不到用户 A 的对话
2.2 Thread ID 设计
方案 1:一个用户一个会话
python
thread_id = f"user-{user_id}"
config = {"configurable": {"thread_id": thread_id}}
适用:每个用户只有一个活跃会话。
方案 2:一个用户多个会话
python
thread_id = f"user-{user_id}-session-{session_id}"
config = {"configurable": {"thread_id": thread_id}}
适用:用户可以同时有多个对话。
关键原则:thread_id 必须唯一标识一个会话。
三、对话记忆
3.1 多轮对话
启用持久化后,同一个 thread_id 的对话会自动记住历史。
python
config = {"configurable": {"thread_id": "conversation-1"}}
# 第一轮
app.invoke({"messages": [HumanMessage(content="你好")]}, config=config)
# 第二轮(AI 能记住第一轮)
app.invoke({"messages": [HumanMessage(content="我叫小明")]}, config=config)
# 第三轮(AI 还记得)
result = app.invoke(
{"messages": [HumanMessage(content="我叫什么?")]},
config=config
)
# AI: 你叫小明
工作原理:
- 每次执行后,Checkpointer 自动保存 State
- 下次执行时,自动恢复 State
- 同一个 thread_id 共享 State
3.2 查看当前状态
python
state = app.get_state(config)
state.values # 当前 State
state.next # 下一个要执行的节点
state.metadata # 元数据
四、断点续传
4.1 从断点恢复
执行失败后,可以从最后一个 Checkpoint 继续。
python
config = {"configurable": {"thread_id": "task-1"}}
# 第一次执行(可能失败)
try:
app.invoke(input_data, config=config)
except Exception:
pass # State 已保存
# 从断点恢复
app.invoke(None, config=config) # None 表示继续执行
关键点:
- 恢复时传
None,不要传新数据 - 传新数据会重新开始,而不是恢复
4.2 更新状态后恢复
某些场景需要先更新 State,再恢复执行(如审批)。
python
# 更新 State(如审批通过)
app.update_state(config, values={"approved": True})
# 恢复执行
app.invoke(None, config=config)
五、时间旅行
5.1 查看历史状态
python
states = app.get_state_history(config)
for state in states:
print(f"Step: {state.metadata['step']}")
print(f"State: {state.values}")
print(f"Next: {state.next}")
5.2 回到过去的状态
python
# 获取历史状态
states = app.get_state_history(config)
first_state = list(states)[-1] # 第一个状态
# 恢复到那个状态
app.update_state(config, values=first_state.values)
# 从那个状态重新开始
app.invoke({"messages": [HumanMessage(content="新问题")]}, config=config)
适用场景:
- 调试:回到之前的状态重新执行
- 用户想重新开始对话
- 分支对话:从某个历史状态开始新分支
六、Checkpointer 类型
LangGraph 提供多种 Checkpointer 实现。
6.1 MemorySaver(内存)
python
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
- ✅ 简单,无需配置
- ❌ 重启后数据丢失
- 适用:开发、测试
6.2 SqliteSaver(SQLite)
python
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")
- ✅ 持久化,重启不丢失
- ❌ 单机
- 适用:单机生产
6.3 RedisSaver(Redis)
python
from langgraph.checkpoint.redis import RedisSaver
checkpointer = RedisSaver.from_conn_string("redis://localhost:6379")
- ✅ 持久化,支持分布式
- ❌ 需要 Redis 服务
- 适用:分布式生产
6.4 PostgresSaver(PostgreSQL)
python
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string("postgresql://user:pass@localhost/db")
- ✅ 持久化,支持分布式,企业级
- ❌ 需要 PostgreSQL 服务
- 适用:企业级生产
6.5 选择建议
|---------------|-----|-----|-------|
| Checkpointer | 持久化 | 分布式 | 适用场景 |
| MemorySaver | ❌ | ❌ | 开发测试 |
| SqliteSaver | ✅ | ❌ | 单机生产 |
| RedisSaver | ✅ | ✅ | 分布式生产 |
| PostgresSaver | ✅ | ✅ | 企业级生产 |
七、常见错误
错误 1:忘记传 config
python
# ❌ 错误
app.invoke({"messages": [...]}) # 没有持久化
# ✅ 正确
config = {"configurable": {"thread_id": "user-1"}}
app.invoke({"messages": [...]}, config=config)
错误 2:thread_id 不唯一
python
# ❌ 错误
config = {"configurable": {"thread_id": "default"}} # 所有人混在一起
# ✅ 正确
config = {"configurable": {"thread_id": f"user-{user_id}"}}
错误 3:恢复时传了 input
python
# ❌ 错误
app.invoke({"new_input": "..."}, config=config) # 这会重新开始
# ✅ 正确
app.invoke(None, config=config) # None 表示继续执行
错误 4:不更新 State 就恢复
python
# ❌ 错误
app.invoke(None, config=config) # approved 还是 None
# ✅ 正确
app.update_state(config, values={"approved": True})
app.invoke(None, config=config)
总结
核心语法:
- 创建 Checkpointer
- 编译:
graph.compile(checkpointer=...) - 执行:
app.invoke(..., config={"configurable": {"thread_id": "..."}})
关键要点:
- thread_id 唯一标识会话
- 恢复执行传 None
- 恢复前可能需要更新 State
常用 API:
app.get_state(config):获取当前状态app.get_state_history(config):获取历史状态app.update_state(config, values=...):更新状态app.invoke(None, config=config):恢复执行