LangGraph 状态回显:查询、回放与分叉
LangGraph 的检查点(Checkpointer)会保存图在每一步执行后的状态。借助它,我们可以查询最新状态、查看历史快照,还可以从某个历史节点重新执行并创建分支。
本文中的
baidu、safari是模拟工具,只返回固定文本,不会真正访问搜索引擎。
1. 安装与配置
bash
pip install -U langgraph langchain-deepseek
export DEEPSEEK_API_KEY="你的 API Key"
python index.py
2. 构建带状态的工作流
先定义工具并绑定模型。ToolNode 会执行模型生成的工具调用,MessagesState 则负责累积消息。
python
from langchain_core.tools import tool
from langchain_deepseek import ChatDeepSeek
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode
@tool
def baidu(name: str):
"""使用百度搜索"""
return f"{name} 搜索百度"
@tool
def safari(name: str):
"""使用 Safari 浏览器"""
return f"{name} 使用Safari浏览器"
tools = [baidu, safari]
tool_node = ToolNode(tools)
model = ChatDeepSeek(model="deepseek-v4-flash", temperature=0).bind_tools(
tools, parallel_tool_calls=False
)
def call_model(state):
return {"messages": [model.invoke(state["messages"])]}
def should_continue(state):
if state["messages"][-1].tool_calls:
return "continue"
return "end"
builder = StateGraph(MessagesState)
builder.add_node("call_model", call_model)
builder.add_node("tool_node", tool_node)
builder.add_edge(START, "call_model")
builder.add_conditional_edges(
"call_model",
should_continue,
{"continue": "tool_node", "end": END},
)
builder.add_edge("tool_node", "call_model")
执行路径如下:
text
START -> call_model -> tool_node -> call_model -> END
3. 保存并回显状态
编译图时传入 MemorySaver,再通过 thread_id 标识一条独立会话。同一个 thread_id 会继续使用此前保存的状态。
python
from langchain_core.messages import HumanMessage
from langchain_core.runnables.config import RunnableConfig
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())
config = RunnableConfig(configurable={"thread_id": "1"})
for chunk in graph.stream(
{"messages": [HumanMessage(content="搜索中国的首都是哪里?")]},
config=config,
stream_mode="values",
):
chunk["messages"][-1].pretty_print()
# 最新状态
current_state = graph.get_state(config)
print(current_state.values["messages"])
# 历史快照,顺序为从新到旧
history = list(graph.get_state_history(config))
for snapshot in history:
print(snapshot.next, snapshot.values)
一次运行的关键输出如下(消息 ID 和快照元数据已省略):
text
Human Message
搜索中国的首都是哪里?
Ai Message
Tool Calls:
baidu
Args: {"name": "中国的首都是哪里?"}
Tool Message
中国的首都是哪里? 搜索百度
Ai Message
中国的首都是北京。
# 历史快照中的待执行节点
()
('call_model',)
('tool_node',)
其中,get_state() 返回线程的最新快照,get_state_history() 返回该线程的全部历史快照。快照中的 next 表示下一步将执行的节点,空元组表示流程已经结束。
4. 从历史状态创建分支
选择模型刚产生工具调用的历史快照,将工具从 baidu 改为 safari,再从该检查点继续执行:
python
from copy import deepcopy
replay = history[2]
last_message = deepcopy(replay.values["messages"][-1])
last_message.tool_calls[0]["name"] = "safari"
branch_config = graph.update_state(
replay.config,
{"messages": [last_message]},
)
# None 表示不添加新输入,直接从新检查点继续执行
for event in graph.stream(None, branch_config):
print(event)
分支的关键输出:
text
Tool Message
中国的首都是哪里? 使用Safari浏览器
Ai Message
中国的首都是北京。
这里的 update_state() 不会覆盖原执行路径,而是创建一个新的检查点。使用深拷贝修改消息,可以避免意外改变原历史快照。
小结
stream(..., stream_mode="values"):实时查看每一步的完整状态。get_state(config):获取当前线程的最新状态。get_state_history(config):按从新到旧的顺序读取历史快照。update_state(snapshot.config, values):基于历史快照创建分支。
MemorySaver 只适合本地演示,程序退出后数据会丢失;生产环境应换成持久化的检查点实现。