LangGraph快速入门-02状态

文章目录


前言

需要通过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和实际编码当中的线程没有联系,只是一种隔离状态空间的方式。

实际编码进行状态隔离的步骤,如下所示:

  1. 创建checkpointer实例
  2. 图编译时,传入checkpointer实例
  3. 图调用时,传递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节点问题处理完成后,要想从故障中恢复,需要以下两点:

  1. invoke时传递None作为初始参数;
  2. 传入相同的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)。而一个超步的执行逻辑,分为如下三个过程:

  1. Plan阶段:确定在此步骤中要执行哪些Actors。例如,在第一步中,选择订阅特殊输入通道的Actors;在后续步骤中,选择订阅上一步骤中更新的通道的参与者
  2. Execute阶段:并行执行所有选定的Actors,直到所有参与者完成、其中一个失败或达到超时时间
  3. 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=())

相关推荐
人工智能培训2 小时前
破解数据发展瓶颈 激活数字经济新动能
大数据·数据库·人工智能·深度学习·神经网络·机器学习·生成对抗网络
阿里云大数据AI技术2 小时前
MaxCompute 多模态检索:原生向量能力开启多模态 AI 大数据处理新范式
人工智能
CCYe、2 小时前
生产级 AI 网关落地:鉴权、限流与故障转移的 7 个坑
人工智能
库拉大叔2 小时前
一站式AI模型聚合平台实测:GPT/Claude/Gemini全系调用,国内直连无需翻墙
人工智能·gpt
happyprince2 小时前
03_NVIDIA_ModelOpt-量化算法深入
人工智能·深度学习·算法
孤月葬花魂2 小时前
Token 正在吞掉程序员的钱包:一个月 AI 成本上万元,新时代的“生产资料”已经变了
人工智能
exarunner2 小时前
KV Cache 驱逐要不要上 Radix Tree?以 Mooncake模拟一下
人工智能
阿牛哥_GX2 小时前
Angular 页面自动化进阶:Select2 组件与表单提交
人工智能
橘子星2 小时前
🤖 从零造一个AI程序员:30行核心代码拆解Cursor背后的Agent原理
人工智能
大鱼>2 小时前
AI+货物追踪:智能快递柜追踪系统
人工智能·深度学习·算法·机器学习