文章目录
- 前言
- 1.状态的定义
-
- [1.1 定义状态类](#1.1 定义状态类)
-
- TypedDict(推荐方式)
- [Pydantic BaseModel](#Pydantic BaseModel)
- [1.2 输入输出数据隔离](#1.2 输入输出数据隔离)
- [1.3 节点间数据隔离](#1.3 节点间数据隔离)
- [1.4 Reducer函数](#1.4 Reducer函数)
- 2.状态的存储
-
- [2.1 实际场景当中的问题](#2.1 实际场景当中的问题)
- [2.2 状态的存储](#2.2 状态的存储)
- [2.3 从状态中恢复执行](#2.3 从状态中恢复执行)
- 3.获取历史状态
-
- [3.1 LangGraph底层运行算法](#3.1 LangGraph底层运行算法)
- [3.2 获取图执行的历史状态](#3.2 获取图执行的历史状态)
前言
需要通过LangGraph构建一个应用程序时,第一步就是定义State,因为State代表了整个图的结果目标,和图当中节点需要修改的数据目标。
State作为LangGraph当中最重要的概念,有其专门的定义方式,和运行时的行为特点。
1.状态的定义
1.1 定义状态类
State Schema可以通过三种方式定义:
TypedDict(推荐方式)
TypedDict是Python提供的一种类型提示工具,用于为字典(Dict)的键和值指定精确的类型信息。状态类继承TypedDict,定义键的类型和reducer函数即可;
代码示例如下:
py
from typing import TypedDict
class MyStateFull(TypedDict):
rag_result:str
web_search_result:str
final_answer:str
query:str
a_new_key:str
Pydantic BaseModel
Pydantic 提供运行时数据校验,并支持静态类型检查工具进行类型推导。状态类通过继承Pydantic的BaseModel,定义键的类型和reducer函数;
代码示例如下:
py
from pydantic import BaseModel
class MyStateFull(BaseModel):
rag_result:str
web_search_result:str
final_answer:str
query:str
a_new_key:str
Dataclass
dataclass是Python标准库中的一个装饰器,用于自动生成常见特殊方法(如__init__、repr 、__eq__等),从而简化主要用作数据容器的类的定义。状态类通过dataclass装饰器装饰后,定义键的类型和reducer函数即可。
代码示例如下:
py
from dataclasses import dataclass
@dataclass
class MyStateFullTwo():
rag_result:str
web_search_result:str
final_answer:str
query:str
a_new_key:str
1.2 输入输出数据隔离
在LangGraph 当中,可以精细管理输入到图中的状态键有哪些,以及输出的状态键有哪些。这是通过初始化StateGraph时,分别指定三个参数:state_schema、input_schema 和 output_schema 来实现的。
其具体作用,可以参考如下图示:

1)state_schema
这是图的完整内部状态,包含了所有节点可能读写的字段,必须指定,不能为空。
特点:
- 是图的"全局状态空间";
- 所有节点都可以访问和写入这个 schema 中的任何字段。
2)input_schema
定义图接受什么输入,是 state_schema 的子集
特点:
- 可选参数,如果不指定,默认等于 state_schema;
- 限制图的输入接口,只能传入这些字段;
- 是 state_schema 的子集或相等。
3)output_schema
定义图返回什么输出,是 state_schema 的子集
特点:
- 可选参数,如果不指定,默认等于 state_schema;
- 限制图的输出接口,只返回这些字段;
- 是 state_schema 的子集或相等。
代码如下所示:
py
# 测试输入输出隔离
from typing import TypedDict
from langgraph.constants import START, END
from langgraph.graph import StateGraph
# 定义状态
class State(TypedDict):
# 用户的查询信息
query: str
# 用户输入的其他信息
other: str
# RAG结果
rag_result: str
# 搜索结果
search_result: str
# 最终结果
final_result: str
# 定义输入状态,只接受用户输入的查询信息
class InputState(TypedDict):
# 用户的查询信息
query: str
# 定义输出状态,只输出最终结果
class OutputState(TypedDict):
# 最终结果
final_result: str
# 定义节点
def rag_node(state: State):
print(f"执行rag_node节点时全局状态的值:{state}")
# 在第一个节点对other赋值
other = "在rag_node节点赋值"
return {"rag_result":"通过RAG节点之后的值","other":other}
# 定义节点
def search_node(state: State):
print(f"执行search_node节点时全局状态的值:{state}")
return {"search_result":"这是查询互联网之后的值"}
# 定义汇总节点
def final_node(state: State):
print(f"执行final_node节点时全局状态的值:{state}")
return {"final_result":"这是最终结果"}
# 创建状态图实例
# 测试输入输出隔离
# state_graph = StateGraph(state_schema= State)
state_graph = StateGraph(state_schema=State,input_schema=InputState,output_schema=OutputState)
# 添加节点
# 添加节点
state_graph.add_node(rag_node).add_node(search_node).add_node(final_node)
# 添加边
state_graph.add_edge(START,"rag_node")
state_graph.add_edge(START,"search_node")
state_graph.add_edge("rag_node","final_node")
state_graph.add_edge("search_node","final_node")
state_graph.add_edge("final_node",END)
# 编译
compile_state_graph = state_graph.compile()
# 执行
res = compile_state_graph.invoke({"query":"测试输入输出隔离","other":"这是其他信息"})
print(f"最终输出的结果:{res}")
# 打印图
graph = compile_state_graph.get_graph()
print(graph.draw_ascii())
设置输入输出数据时
StateGraph(state_schema=State,input_schema=InputState,output_schema=OutputState)
bash
执行rag_node节点时全局状态的值:{'query': '测试输入输出隔离'}
执行search_node节点时全局状态的值:{'query': '测试输入输出隔离'}
执行final_node节点时全局状态的值:{'query': '测试输入输出隔离', 'other': '在rag_node节点赋值', 'rag_result': '通过RAG节点之后的值', 'search_result': '这是查询互联网之后的值'}
最终输出的结果:{'final_result': '这是最终结果'}
+-----------+
| __start__ |
+-----------+
*** ***
* *
** **
+----------+ +-------------+
| rag_node | | search_node |
+----------+ +-------------+
*** ***
* *
** **
+------------+
| final_node |
+------------+
*
*
*
+---------+
| __end__ |
+---------+
只设置state_schema时
py
执行rag_node节点时全局状态的值:{'query': '测试输入输出隔离', 'other': '这是其他信息'}
执行search_node节点时全局状态的值:{'query': '测试输入输出隔离', 'other': '这是其他信息'}
执行final_node节点时全局状态的值:{'query': '测试输入输出隔离', 'other': '在rag_node节点赋值', 'rag_result': '通过RAG节点之后的值', 'search_result': '这是查询互联网之后的值'}
最终输出的结果:{'query': '测试输入输出隔离', 'other': '在rag_node节点赋值', 'rag_result': '通过RAG节点之后的值', 'search_result': '这是查询互联网之后的值', 'final_result': '这是最终结果'}
+-----------+
| __start__ |
+-----------+
*** ***
* *
** **
+----------+ +-------------+
| rag_node | | search_node |
+----------+ +-------------+
*** ***
* *
** **
+------------+
| final_node |
+------------+
*
*
*
+---------+
| __end__ |
+---------+
1.3 节点间数据隔离
LangGraph当中节点所接收的状态,还可以为一个非全局状态的私有状态。
示例代码如下:
py
# 测试私有状态
from typing import TypedDict
from langgraph.constants import START, END
from langgraph.graph import StateGraph
# 定义状态
class State(TypedDict):
# 用户的查询信息
query: str
# 最终结果
final_result: str
# 定义一个私有状态
class PrivateState(TypedDict):
# RAG结果
rag_result: str
# 搜索结果
search_result: str
# 定义节点
def rag_node(state: State):
# 获取用户的查询信息
user_query = state["query"]
# 对私有状态中rag_result赋值
rag_result = f"用户的查询信息是:{user_query}+ 在rag节点对rag_result赋值"
return {"rag_result":rag_result}
# 定义节点
def search_node(state: State):
return {"search_result":"在search节点对search_result赋值"}
# 定义汇总节点
def final_node(state: PrivateState):
# 获取私有状态中的值
rag_result = state["rag_result"]
search_result = state["search_result"]
return {"final_result": f"私有状态中的两个值分别是:{rag_result} ----- {search_result}"}
# 创建状态图实例
state_graph = StateGraph(state_schema=State)
# 添加节点
# 添加节点
state_graph.add_node(rag_node).add_node(search_node).add_node(final_node)
# 添加边
state_graph.add_edge(START,"rag_node")
state_graph.add_edge(START,"search_node")
state_graph.add_edge("rag_node","final_node")
state_graph.add_edge("search_node","final_node")
state_graph.add_edge("final_node",END)
# 编译
compile_state_graph = state_graph.compile()
# 执行
res = compile_state_graph.invoke({"query":"测试私有状态"})
print(f"最终输出的结果:{res}")
# 打印图
graph = compile_state_graph.get_graph()
print(graph.draw_ascii())
结果
bash
最终输出的结果:{'query': '测试私有状态', 'final_result': '私有状态中的两个值分别是:用户的查询信息是:测试私有状态+ 在rag节点对rag_result赋值 ----- 在search节点对search_result赋值'}
+-----------+
| __start__ |
+-----------+
*** ***
* *
** **
+----------+ +-------------+
| rag_node | | search_node |
+----------+ +-------------+
*** ***
* *
** **
+------------+
| final_node |
+------------+
*
*
*
+---------+
| __end__ |
+---------+
为什么rag_node函数中可以直接给PrivateState的rag_result赋值
在 LangGraph 中,状态本质上是一个共享的字典 ,所有节点都能读取和写入该字典中的任意键值对。虽然代码中用 State(包含 query 和 final_result)作为状态图的 state_schema,但这仅用于定义初始状态的结构 (例如 invoke 时传入的字段),并不限制节点在运行时向状态中添加新键。
具体到 rag_node 函数:
- 它接受 state: State 作为输入,但实际传入的是一个完整的全局状态字典(包含所有已存在的键)。
- 函数返回 {"rag_result": rag_result},这个字典会在图执行过程中与全局状态合并(使用 dict.update 或类似机制),因此 rag_result 这个新键被直接添加到全局状态中。
- 同样,search_node 也添加了 search_result 键。
- 后续的 final_node(声明输入为 PrivateState)在执行时,全局状态中已经包含了这两个键,因此可以正常读取。
关键点:
- LangGraph 不会因为返回的键不在 state_schema 中而抛出异常(除非使用 Pydantic 模型并设置了 extra='forbid')。
- 代码中的 PrivateState 只是一个类型标注,用于提示开发者这些字段在某个节点会被使用,但它并没有真正被用作隔离作用域------所有状态对全部节点都是公开的。
- 所谓的"私有状态"在目前的实现中仅是概念上的划分,实际上所有字段都存在于同一全局字典中。
1.4 Reducer函数
Reducer用于进行当前增量状态(节点输出的状态)和全局状态的合并。State中的每个键都有其独立的reducer函数。每个node的返回值中的每个key与全局state_schema中对应的key进行合并更新,具体更新逻辑取决于每个key指定的reducer函数。
Reducer常用函数有以下几种:
- 默认行为:未指定Reducer时使用覆盖更新
- 内置reducer函数:例如langgraph.graph.messages当中的add_messages函数
- 自定义Reducer:支持用户自定义合并逻辑
如果未明确指定reducer函数,则默认对该键的更新是覆盖行为。以下为默认覆盖行为:
py
from typing import TypedDict
from langgraph.constants import START, END
from langgraph.graph import StateGraph
# 测试修改状态中的值(默认)
# 演示值被覆盖的情况
# 定义状态
class UserState(TypedDict):
hobbies: list[str]
# 定义节点
def add_hobby1(state: UserState):
print(f"节点1处理之前的状态是:{state}")
# 修改用户的爱好
hobbies = ["打篮球"]
return {"hobbies": hobbies}
def add_hobby2(state: UserState):
print(f"节点2处理之前的状态是:{state}")
# 修改用户的爱好
hobbies = ["踢足球"]
return {"hobbies": hobbies}
# 创建状态图实例
state_graph = StateGraph(state_schema=UserState)
# 添加节点
state_graph.add_node(add_hobby1).add_node(add_hobby2)
# 添加边
state_graph.add_edge(START,"add_hobby1").add_edge("add_hobby1","add_hobby2").add_edge("add_hobby2",END)
# 编译
compile_state_graph = state_graph.compile()
# 执行
res = compile_state_graph.invoke({"hobbies":["唱歌"]})
print(res)
结果
bash
节点1处理之前的状态是:{'hobbies': ['唱歌']}
节点2处理之前的状态是:{'hobbies': ['打篮球']}
{'hobbies': ['踢足球']}
可以自定义Reducer函数,用于合并操作。自定义Reducer函数接收两个参数,用以将其合并,最终返回一个结果。
例如此处定义一个operate_state_value函数,用于将全局状态当中的message和节点的message进行合并操作:
py
import operator
from typing import TypedDict, Annotated
from langgraph.constants import START, END
from langgraph.graph import StateGraph
# 测试修改状态中的值(reducer)
# 使用自定义函数处理状态值的合并
# 定义一个处理状态值的函数
def operate_state_value(old_value,new_value):
return old_value+new_value
# 定义状态
class UserState(TypedDict):
hobbies: Annotated[list[str],operate_state_value]
# 定义节点
def add_hobby1(state: UserState):
print(f"节点1处理之前的状态是:{state}")
# 修改用户的爱好
hobbies = ["打篮球"]
return {"hobbies": hobbies}
def add_hobby2(state: UserState):
print(f"节点2处理之前的状态是:{state}")
# 修改用户的爱好
hobbies = ["踢足球"]
return {"hobbies": hobbies}
# 创建状态图实例
state_graph = StateGraph(state_schema=UserState)
# 添加节点
state_graph.add_node(add_hobby1).add_node(add_hobby2)
# 添加边
state_graph.add_edge(START,"add_hobby1").add_edge("add_hobby1","add_hobby2").add_edge("add_hobby2",END)
# 编译
compile_state_graph = state_graph.compile()
# 执行
res = compile_state_graph.invoke({"hobbies":["唱歌"]})
print(res)
2.状态的存储
2.1 实际场景当中的问题
在前面的例子当中,我们看到,每次用户invoke时,LangGraph都会初始化一个空状态,然后将用户传入的初始状态合并进来,再继续往下执行。
这在一些一次性、简单任务过程中,没有什么问题。但是对于一些复杂任务,就会出现一些问题,考虑以下两个场景:
1)场景一:在agent的一个会话里,需要在多次调用当中保持上下文

在这个场景下,我们定义的状态为messages列表,第一次调用过程中,图会在messages列表当中写入值。而第二次调用,又会初始化一个新的messages列表,导致缺失了第一次调用的上下文。
2)场景二:图执行过程当中报错,不想重复已执行完节点,想要实现断点续传

在这个场景下,第一次调用过程中,图会在Node2节点因某些原因报错,但是我们想要拿到Node1节点所对应的值。并在修复完第二个节点之后,继续往下执行。
2.2 状态的存储
前面所涉及到的问题,解决方案都是将第一次调用的结果,存储起来,然后在第二次调用时,从存储的结果当中取出状态,而不是让LangGraph去初始化空状态。
由于同一个agent会存在多用户或者单用户多会话等场景,要实现状态存储,并且后续能够找到正确的状态,就需要在状态当中做一个id映射,写入时指定一个id,读取时读取指定id的状态。
首先需要将状态存储起来,LangGraph为我们提供了一个存储状态的方式:Checkpointer。
Checkpointer也即检查点, 它可以存储图执行过程当中所产生的状态,具体存储的位置,有内存、数据库等。Checkpointer所有相关的实例化类都来自于langgraph.checkpointer包。
LangGraph同样做了前述的id隔离,这个id在langgraph当中就称之为thread_id。需要注意:Thread_id和实际编码当中的线程没有联系,只是一种隔离状态空间的方式。
实际编码进行状态隔离的步骤,如下所示:
- 创建checkpointer实例
- 图编译时,传入checkpointer实例
- 图调用时,传递thread_id:保证上下文空间,就传入相同的thread_id,需要开辟新的上下文空间,就传入新的thread_id。
对于前面所讲到的场景一,要想在一个会话中保持上下文,只需要在多次调用时,传入同一个thread_id即可;要想启动一个新会话,就传入一个新的thread_id。这也就是所谓"Agent短期记忆"。
示例代码如下所示,重点关注内容参看带序号注释:
py
def scenario_1_demo():
# 1、create_agent底层就是通过LangGraph构建的图结构,此处直接通过create_agent来举一个例子
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI
# 1、引入checkpointer
from langgraph.checkpoint.memory import InMemorySaver
llm = ChatOpenAI(model="gpt-4o-mini")
# 2、定义checkpointer实例
checkpointer = InMemorySaver()
@tool
def weather_tool(city:str,date:str)->str:
"""查天气的工具"""
return f'{city}在{date}的天气是晴朗的'
# 3、构建Agent时引入checkpointer
agent = create_agent(
model=llm,
tools=[weather_tool],
checkpointer=checkpointer,
)
# 4、用户第一次调用
user1_res = agent.invoke({"messages":"北京2026-01-14天气怎么样"},config={"configurable":{"thread_id":"user_1_session_1"}})
print('第一次调用',user1_res['messages'][-1],end="\n\n==============\n\n")
# 5、用户在同一个会话当中,第二次调用
user1_res_2 = agent.invoke({"messages":"适合出去玩吗"},config={"configurable":{"thread_id":"user_1_session_1"}})
print('第二次调用',user1_res_2['messages'][-1],end="\n\n==============\n\n")
2.3 从状态中恢复执行
上一小节当中所讲到的例子,checkpointer是通过InMemorySaver进行实例化的,这个实例会将状态全部存储到内存当中,这仍然不能解决场景二的问题,场景二涉及的问题不再是多次调用之间,保持上下文不中断,而是如何进行故障恢复。
在因为故障导致进程退出后,内存状态空间当中的内容,将全部丢失。在实际生产环境下,需要使用数据库做持久化状态存储。
下面代码当中,Node2节点表示在实际执行过程中,因为某些原因导致的错误。如果将相关的状态存储到数据库中,在第一次执行时,node2前面节点的状态,会写入到数据库中。
在node2节点问题处理完成后,要想从故障中恢复,需要以下两点:
- invoke时传递None作为初始参数;
- 传入相同的thread_id。满足以上两点,即可让图从断点开始,以第一次调用的历史状态,继续往下执行,
断点续传示例代码如下所示:
py
import sqlite3
from typing import TypedDict
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.constants import START, END
from langgraph.graph import StateGraph
# 定义状态
class UserState(TypedDict):
first_girl_friend: str
second_girl_friend: str
third_girl_friend: str
# 定义节点
def first_node(state: UserState):
print("第一个节点执行")
return {"first_girl_friend": "刘亦菲"}
def second_node(state: UserState):
print("第二个节点执行")
print(f"执行第二个节点时状态中的值:{state}")
# 第一次执行先抛出异常
raise Exception("出现异常!")
return {"second_girl_friend": "刘诗诗"}
def third_node(state: UserState):
print("第三个节点执行")
return {"third_girl_friend": "迪丽热巴"}
# 创建状态图实例
state_graph = StateGraph(state_schema=UserState)
# 添加节点
state_graph.add_node(first_node).add_node(second_node).add_node(third_node)
# 添加边
state_graph.add_edge(START,"first_node").add_edge("first_node","second_node").add_edge("second_node","third_node").add_edge("third_node",END)
# 获取数据库连接
conn = sqlite3.connect(database="db/state.db",check_same_thread=False)
# 创建checkpointer
checkpointer = SqliteSaver(conn)
# 编译
compile_state_graph = state_graph.compile(checkpointer)
# 执行(以下是保存状态时执行的代码)
res = compile_state_graph.invoke({},config={"configurable":{"thread_id":"qfccc"}})
# 执行(以下是恢复状态时执行的代码)
# res = compile_state_graph.invoke(None,config={"configurable":{"thread_id":"qfccc"}})
print(res)
第一次执行先抛出异常
bash
第一个节点执行
第二个节点执行
执行第二个节点时状态中的值:{'first_girl_friend': '刘亦菲', 'second_girl_friend': '刘诗诗', 'third_girl_friend': '迪丽热巴'}
Traceback (most recent call last):
File xxx
raise Exception("出现异常!")
Exception: 出现异常!
During task with name 'second_node' and id '4619f129-3a77-f314-d96d-514a4cb04063'
第二次断点续传后结果(注意graph.invoke参数input=None):
bash
第二个节点执行
执行第二个节点时状态中的值:{'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿', 'third_girl_friend': '王菲'}
第三个节点执行
{'first_girl_friend': '刘亦菲', 'second_girl_friend': '刘诗诗', 'third_girl_friend': '迪丽热巴'}
3.获取历史状态
前面我们了解到,构造checkpointer之后,langgraph可以保证在多次调用之间,保证状态的连续,或者是通过状态进行恢复。不过,要想再深入了解状态,就需要知道状态的保存时机和如何获取到所有的历史状态。
3.1 LangGraph底层运行算法
在了解如何获取历史状态之前,首先我们需要了解一下LangGraph底层算法---Pregel。
Pregel 是LangGraph底层的一个类,管理 LangGraph 应用程序的运行时(runtime)行为。也就是说,整个图结构,从开始到结束的迭代执行过程,是由Pregel控制管理的。
在这个类当中,有两大主要组件:
1)Actors (节点)
Actor即为在前面介绍的Node,对应在LangGraph当中的类为PregelNode,其订阅某些通道(channels),从通道当中读取数据,或者写入数据。PregelNode也实现了LangChain的Runnable接口。
2)Channels(通道)
Channels用于actors之间通信,每个通道都有一个值类型、一个更新类型和一个更新函数 ------ 该函数接收一系列更新并修改存储的值。
正是因为这种设计,在LangGraph中通过边和节点构建的图,不是一个传统的有向无环图(DAG)。在传统的有向无环图下,节点的执行顺序,完全边的连接,某个节点只有在和它连接的所有的上游节点都执行完成了后,才会执行。
而在LangGraph图当中,节点的执行来自于一个Pregel的超步(SuperStep)。而一个超步的执行逻辑,分为如下三个过程:
- Plan阶段:确定在此步骤中要执行哪些Actors。例如,在第一步中,选择订阅特殊输入通道的Actors;在后续步骤中,选择订阅上一步骤中更新的通道的参与者
- Execute阶段:并行执行所有选定的Actors,直到所有参与者完成、其中一个失败或达到超时时间
- Update阶段:用本步骤中Actors所写的值更新channels。
由以上三个步骤组合而成的超步会重复执行,直到没有Actors被选中执行,或者达到最大步骤数为止。以如下的图为例:

图中展示了builder.compile()内部逻辑:将逻辑图,转换成Pregel实例,而节点和边,分别转换成了Actors和Channels实例。
针对于Actors实例,每个Actor实例都有其订阅和写入的Channels,如下图所示:

整个图的执行流程,可以分为如下几个Step,Step索引从0开始:

整个执行流程如下:
首先在step为-1时,__START__节点执行(图中未标注),将相关状态写入到branch:to:a当中;在Step0当中,节点a执行,将状态写入到branch:to:b和branch:to:c;在Step1当中,节点b和c执行,将状态写入到branch:to:b_2和branch:to:d当中;在Step2,节点b_2和节点d执行,b_2节点再次将状态写入到branch:to:d当中。在Step3当中,节点d再次执行,由于没有新的状态更新,图执行过程结束。
3.2 获取图执行的历史状态
理解了Pregel和Step的概念之后,现在可以来学习如何获取图执行的历史状态,以及如何看懂历史状态当中所存储的内容。
构建好的graph实例,可以通过get_state() / get_state_history()方法,传入想要获取的历史状态的thread_id,即可拿到图执行过程中的历史状态,get_state()方法获取到的是最近一个时间步的状态,get_state_history()获取到的是图执行当中所有时间步的历史状态,其输出值为一个迭代器,按照时间步倒序排列。
历史状态由StateSnapShot实例表示,也即状态快照,其中包含的信息如下:
| 名称 | 类型 | 描述 |
|---|---|---|
| values | dictstr, Any | Any |
| next | tuplestr, ... | 本超步中每个任务要执行的节点名称 |
| config | RunnableConfig | 用于获取此快照的配置。 |
| metadata | CheckpointMetadata | 与此快照相关联的元数据 |
| parent_config | RunnableConfig | 用于获取父快照(如果有的话)的配置。 |
| interrupts | tupleInterrupt, ... | 此超步中发生且有待解决的中断。 |
我们重点需要关注的是,在状态快照中,除了有当前状态值以外,还包含接下来需要执行的节点名称,这也为从故障中恢复提供了有用的信息,我们可以通过状态快照直接知道,从哪一个节点开始继续往下执行。
示例代码如下所示:
py
import sqlite3
from typing import TypedDict
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.constants import START, END
from langgraph.graph import StateGraph
# 定义状态
class UserState(TypedDict):
first_girl_friend: str
second_girl_friend: str
third_girl_friend: str
# 定义节点
def first_node(state: UserState):
print("第一个节点执行")
return {"first_girl_friend": "刘亦菲"}
def second_node(state: UserState):
print("第二个节点执行")
print(f"执行第二个节点时状态中的值:{state}")
# 抛出异常
# raise Exception("官宣超长休整,身体亮红灯")
return {"second_girl_friend": "白鹿"}
def third_node(state: UserState):
print("第三个节点执行")
return {"third_girl_friend": "王菲"}
# 创建状态图实例
state_graph = StateGraph(state_schema=UserState)
# 添加节点
state_graph.add_node(first_node).add_node(second_node).add_node(third_node)
# 添加边
state_graph.add_edge(START,"first_node").add_edge("first_node","second_node").add_edge("second_node","third_node").add_edge("third_node",END)
# 获取数据库连接
conn = sqlite3.connect(database="db/state.db",check_same_thread=False)
# 创建checkpointer
checkpointer = SqliteSaver(conn)
# 编译
compile_state_graph = state_graph.compile(checkpointer)
# 执行(以下是保存状态时执行的代码)
# res = compile_state_graph.invoke({},config={"configurable":{"thread_id":"qfccc"}})
# 执行(以下是恢复状态时执行的代码)
# res = compile_state_graph.invoke(None,config={"configurable":{"thread_id":"qfccc"}})
# 获取最近的一次状态
last_state = compile_state_graph.get_state(config={"configurable":{"thread_id":"qfccc"}})
print(f"最近一次的状态是:{last_state}")
print("="*66)
# 获取所有的历史状态
history_states = compile_state_graph.get_state_history(config={"configurable":{"thread_id":"qfccc"}})
# 遍历
for history_state in history_states:
print(f"历史状态是:{history_state}")
结果
bash
最近一次的状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '刘诗诗', 'third_girl_friend': '迪丽热巴'}, next=('second_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1cd-9bf4-675f-800e-8fa9703b2138'}}, metadata={'source': 'loop', 'step': 14, 'parents': {}}, created_at='2026-07-07T15:59:35.606051+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1cd-9bf2-604c-800d-cab4d164f157'}}, tasks=(PregelTask(id='4619f129-3a77-f314-d96d-514a4cb04063', name='second_node', path=('__pregel_pull', 'second_node'), error="Exception('出现异常!')", interrupts=(), state=None, result=None),), interrupts=())
==================================================================
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '刘诗诗', 'third_girl_friend': '迪丽热巴'}, next=('second_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1cd-9bf4-675f-800e-8fa9703b2138'}}, metadata={'source': 'loop', 'step': 14, 'parents': {}}, created_at='2026-07-07T15:59:35.606051+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1cd-9bf2-604c-800d-cab4d164f157'}}, tasks=(PregelTask(id='4619f129-3a77-f314-d96d-514a4cb04063', name='second_node', path=('__pregel_pull', 'second_node'), error="Exception('出现异常!')", interrupts=(), state=None, result=None),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '刘诗诗', 'third_girl_friend': '迪丽热巴'}, next=('first_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1cd-9bf2-604c-800d-cab4d164f157'}}, metadata={'source': 'loop', 'step': 13, 'parents': {}}, created_at='2026-07-07T15:59:35.605050+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1cd-9bed-6228-800c-b897853f50bf'}}, tasks=(PregelTask(id='74c935a6-4c18-5b74-26d3-642e3430398b', name='first_node', path=('__pregel_pull', 'first_node'), error=None, interrupts=(), state=None, result={'first_girl_friend': '刘亦菲'}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '刘诗诗', 'third_girl_friend': '迪丽热巴'}, next=('__start__',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1cd-9bed-6228-800c-b897853f50bf'}}, metadata={'source': 'input', 'step': 12, 'parents': {}}, created_at='2026-07-07T15:59:35.603050+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-da92-621c-800b-46044f0e7762'}}, tasks=(PregelTask(id='df65646d-f5fe-e8c5-489f-95b99f9b6770', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '刘诗诗', 'third_girl_friend': '迪丽热巴'}, next=(), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-da92-621c-800b-46044f0e7762'}}, metadata={'source': 'loop', 'step': 11, 'parents': {}}, created_at='2026-07-07T15:57:27.954076+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-da8d-66d6-800a-3d4f675416c5'}}, tasks=(), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '刘诗诗', 'third_girl_friend': '王菲'}, next=('third_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-da8d-66d6-800a-3d4f675416c5'}}, metadata={'source': 'loop', 'step': 10, 'parents': {}}, created_at='2026-07-07T15:57:27.952149+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-3ecd-670b-8009-cbd15d0e3ce6'}}, tasks=(PregelTask(id='5da95c67-7094-fe73-4ccf-d57ea1adf361', name='third_node', path=('__pregel_pull', 'third_node'), error=None, interrupts=(), state=None, result={'third_girl_friend': '迪丽热巴'}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿', 'third_girl_friend': '王菲'}, next=('second_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-3ecd-670b-8009-cbd15d0e3ce6'}}, metadata={'source': 'loop', 'step': 9, 'parents': {}}, created_at='2026-07-07T15:57:11.620583+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-3eca-6fea-8008-8e190fab0591'}}, tasks=(PregelTask(id='45724424-47d0-5112-921e-e1ec75feda79', name='second_node', path=('__pregel_pull', 'second_node'), error="Exception('出现异常!')", interrupts=(), state=None, result={'second_girl_friend': '刘诗诗'}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿', 'third_girl_friend': '王菲'}, next=('first_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-3eca-6fea-8008-8e190fab0591'}}, metadata={'source': 'loop', 'step': 8, 'parents': {}}, created_at='2026-07-07T15:57:11.619581+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-3ec4-6a92-8007-ce51eb4e4315'}}, tasks=(PregelTask(id='e8981f5b-3bc1-60d7-a658-cd50f7162e47', name='first_node', path=('__pregel_pull', 'first_node'), error=None, interrupts=(), state=None, result={'first_girl_friend': '刘亦菲'}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿', 'third_girl_friend': '王菲'}, next=('__start__',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c8-3ec4-6a92-8007-ce51eb4e4315'}}, metadata={'source': 'input', 'step': 7, 'parents': {}}, created_at='2026-07-07T15:57:11.616987+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c4-d229-63fd-8006-0ad57586636e'}}, tasks=(PregelTask(id='0aeb770e-395a-efce-c3d5-6e195334a7cc', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿', 'third_girl_friend': '王菲'}, next=('second_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c4-d229-63fd-8006-0ad57586636e'}}, metadata={'source': 'loop', 'step': 6, 'parents': {}}, created_at='2026-07-07T15:55:39.698073+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c4-d226-6bab-8005-49b8fc5e1b0c'}}, tasks=(PregelTask(id='37b2d345-6e91-40c6-3067-cdf95fd12c84', name='second_node', path=('__pregel_pull', 'second_node'), error="Exception('官宣超长休整,身体亮红灯')", interrupts=(), state=None, result=None),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿', 'third_girl_friend': '王菲'}, next=('first_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c4-d226-6bab-8005-49b8fc5e1b0c'}}, metadata={'source': 'loop', 'step': 5, 'parents': {}}, created_at='2026-07-07T15:55:39.697041+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c4-d21d-6bec-8004-017fc4d2ffe2'}}, tasks=(PregelTask(id='96b16e24-5b88-234b-e419-c172effd0e8c', name='first_node', path=('__pregel_pull', 'first_node'), error=None, interrupts=(), state=None, result={'first_girl_friend': '刘亦菲'}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿', 'third_girl_friend': '王菲'}, next=('__start__',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f17a1c4-d21d-6bec-8004-017fc4d2ffe2'}}, metadata={'source': 'input', 'step': 4, 'parents': {}}, created_at='2026-07-07T15:55:39.693361+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-77a6-693e-8003-1dccbea63642'}}, tasks=(PregelTask(id='fa57e81f-7afe-1985-83fc-b5a5b39d1446', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿', 'third_girl_friend': '王菲'}, next=(), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-77a6-693e-8003-1dccbea63642'}}, metadata={'source': 'loop', 'step': 3, 'parents': {}}, created_at='2026-07-07T07:59:28.674847+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-779f-634a-8002-f00dbb47ef13'}}, tasks=(), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲', 'second_girl_friend': '白鹿'}, next=('third_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-779f-634a-8002-f00dbb47ef13'}}, metadata={'source': 'loop', 'step': 2, 'parents': {}}, created_at='2026-07-07T07:59:28.671828+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-06dc-6214-8001-0b6fa2ecfbfd'}}, tasks=(PregelTask(id='312a0a69-ec07-746f-5070-9397a291415a', name='third_node', path=('__pregel_pull', 'third_node'), error=None, interrupts=(), state=None, result={'third_girl_friend': '王菲'}),), interrupts=())
历史状态是:StateSnapshot(values={'first_girl_friend': '刘亦菲'}, next=('second_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-06dc-6214-8001-0b6fa2ecfbfd'}}, metadata={'source': 'loop', 'step': 1, 'parents': {}}, created_at='2026-07-07T07:59:16.847874+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-06d7-6356-8000-2990d7e1db6b'}}, tasks=(PregelTask(id='52b056dc-d8d7-254b-a21b-07fa0032d80f', name='second_node', path=('__pregel_pull', 'second_node'), error="Exception('官宣超长休整,身体亮红灯')", interrupts=(), state=None, result={'second_girl_friend': '白鹿'}),), interrupts=())
历史状态是:StateSnapshot(values={}, next=('first_node',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-06d7-6356-8000-2990d7e1db6b'}}, metadata={'source': 'loop', 'step': 0, 'parents': {}}, created_at='2026-07-07T07:59:16.845858+00:00', parent_config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-06d2-642f-bfff-1a731ac9a815'}}, tasks=(PregelTask(id='c0149071-decf-9bf1-928c-bc3abe32eb43', name='first_node', path=('__pregel_pull', 'first_node'), error=None, interrupts=(), state=None, result={'first_girl_friend': '刘亦菲'}),), interrupts=())
历史状态是:StateSnapshot(values={}, next=('__start__',), config={'configurable': {'thread_id': 'qfccc', 'checkpoint_ns': '', 'checkpoint_id': '1f179d9c-06d2-642f-bfff-1a731ac9a815'}}, metadata={'source': 'input', 'step': -1, 'parents': {}}, created_at='2026-07-07T07:59:16.843831+00:00', parent_config=None, tasks=(PregelTask(id='90df2771-9be8-3ede-69c8-4137d7e91e89', name='__start__', path=('__pregel_pull', '__start__'), error=None, interrupts=(), state=None, result={}),), interrupts=())