Skip to content

LangGraph 的核心是将智能体工作流建模为图。您可以使用三个关键组件来定义智能体的行为:

  1. State:一个共享数据结构,表示应用程序的当前快照。它可以是任何数据类型,但通常使用共享状态模式定义。

  2. Nodes:编码智能体逻辑的函数。它们接收当前状态作为输入,执行一些计算或副作用,并返回更新后的状态。

  3. Edges:根据当前状态决定接下来执行哪个 Node 的函数。它们可以是条件分支或固定转换。

通过组合 NodesEdges,您可以创建随时间演变状态的复杂、循环工作流。然而,真正的威力来自 LangGraph 如何管理该状态。

需要强调的是:NodesEdges 仅仅是函数——它们可以包含 LLM 或只是普通的代码。

简而言之:节点执行工作,边告诉接下来做什么

LangGraph 底层的图算法使用消息传递来定义一个通用程序。当一个节点完成其操作时,它会沿着一条或多条边向其他节点发送消息。这些接收节点然后执行其函数,将结果消息传递给下一组节点,过程持续进行。受 Google 的 Pregel 系统启发,程序以离散的“超级步”进行。

超级步可以被视为对图节点的一次迭代。并行运行的节点属于同一个超级步,而顺序运行的节点属于不同的超级步。在图执行开始时,所有节点都处于 inactive 状态。当一个节点在其任何传入边(或“通道”)上接收到新消息(状态)时,它变为 active。然后,活动节点运行其函数并响应更新。在每个超级步结束时,没有传入消息的节点通过将自己标记为 inactive 来投票 halt。当所有节点都处于 inactive 状态且没有消息在传输中时,图执行终止。

StateGraph

StateGraph 类是使用的主要图类。它由用户定义的 State 对象参数化。

编译您的图

要构建您的图,您首先定义状态,然后添加节点,最后编译它。编译图到底是什么,为什么需要它?

编译是一个相当简单的步骤。它对图的结构进行一些基本检查(没有孤立节点等)。这也是您可以指定运行时参数(如检查点和断点)的地方。您只需调用 .compile 方法来编译您的图:

python
graph = graph_builder.compile(...)

在使用图之前,您必须编译它。

状态

定义图时,您要做的第一件事是定义图的 StateState图的模式以及指定如何将更新应用到状态的reducer 函数组成。State 的模式将是图中所有 NodesEdges 的输入模式,可以是 TypedDictPydantic 模型。所有 Nodes 都会发出对 State 的更新,然后使用指定的 reducer 函数应用这些更新。

模式

指定图模式的主要文档化方法是使用 TypedDict。如果您想在状态中提供默认值,请使用 dataclass。如果您想要递归数据验证,我们也支持使用 Pydantic BaseModel 作为图状态(但请注意,Pydantic 的性能不如 TypedDictdataclass)。

默认情况下,图将具有相同的输入和输出模式。如果您想更改这一点,也可以直接指定显式的输入和输出模式。当您有很多键,并且其中一些明确用于输入,另一些用于输出时,这很有用。有关更多信息,请参阅指南

多个模式

通常,所有图节点都使用单一模式进行通信。这意味着它们将读取和写入相同的状态通道。但是,有些情况下我们希望对这一点有更多控制:

  • 内部节点可以传递图中输入/输出不需要的信息。
  • 我们可能还想为图使用不同的输入/输出模式。例如,输出可能只包含一个相关的输出键。

可以让节点在图中写入私有状态通道,用于内部节点通信。我们可以简单地定义一个私有模式 PrivateState

也可以为图定义显式的输入和输出模式。在这些情况下,我们定义一个“内部”模式,其中包含与图操作相关的_所有_键。但是,我们还定义了 inputoutput 模式,它们是“内部”模式的子集,以约束图的输入和输出。有关更多详细信息,请参阅本指南

让我们看一个例子:

python
class InputState(TypedDict):
    user_input: str

class OutputState(TypedDict):
    graph_output: str

class OverallState(TypedDict):
    foo: str
    user_input: str
    graph_output: str

class PrivateState(TypedDict):
    bar: str

def node_1(state: InputState) -> OverallState:
    # Write to OverallState
    return {"foo": state["user_input"] + " name"}

def node_2(state: OverallState) -> PrivateState:
    # Read from OverallState, write to PrivateState
    return {"bar": state["foo"] + " is"}

def node_3(state: PrivateState) -> OutputState:
    # Read from PrivateState, write to OutputState
    return {"graph_output": state["bar"] + " Lance"}

builder = StateGraph(OverallState,input_schema=InputState,output_schema=OutputState)
builder.add_node("node_1", node_1)
builder.add_node("node_2", node_2)
builder.add_node("node_3", node_3)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
builder.add_edge("node_2", "node_3")
builder.add_edge("node_3", END)

graph = builder.compile()
graph.invoke({"user_input":"My"})
# {'graph_output': 'My name is Lance'}

这里有两个微妙而重要的点需要注意:

  1. 我们将 state: InputState 作为输入模式传递给 node_1。但是,我们写出到 foo,这是 OverallState 中的一个通道。我们如何写出到输入模式中未包含的状态通道?这是因为节点_可以写入图状态中的任何状态通道_。图状态是初始化时定义的状态通道的并集,其中包括 OverallState 以及过滤器 InputStateOutputState

  2. 我们使用以下方式初始化图:

python
StateGraph(
    OverallState,
    input_schema=InputState,
    output_schema=OutputState
)

那么,我们如何在 node_2 中写入 PrivateState?如果它没有在 StateGraph 初始化中传递,图如何获得对此模式的访问权限?

我们可以这样做,因为 _nodes 只要状态模式定义存在,也可以声明额外的状态 channels_。在这种情况下,PrivateState 模式已定义,因此我们可以将 bar 添加为图中的新状态通道并写入它。

Reducers

Reducers 是理解节点更新如何应用到 State 的关键。State 中的每个键都有其独立的 reducer 函数。如果没有显式指定 reducer 函数,则假定对该键的所有更新都应覆盖它。有几种不同类型的 reducers,从默认的 reducer 类型开始:

默认 reducer

这两个示例展示了如何使用默认 reducer:

Example A
python
from typing_extensions import TypedDict

class State(TypedDict):
    foo: int
    bar: list[str]

在这个例子中,没有为任何键指定 reducer 函数。假设图的输入是:

{"foo": 1, "bar": ["hi"]}。然后假设第一个 Node 返回 {"foo": 2}。这被视为对状态的更新。请注意,Node 不需要返回整个 State 模式——只需要一个更新。应用此更新后,State 将变为 {"foo": 2, "bar": ["hi"]}。如果第二个节点返回 {"bar": ["bye"]},那么 State 将变为 {"foo": 2, "bar": ["bye"]}

Example B
python
from typing import Annotated
from typing_extensions import TypedDict
from operator import add

class State(TypedDict):
    foo: int
    bar: Annotated[list[str], add]

在这个例子中,我们使用了 Annotated 类型来为第二个键(bar)指定一个 reducer 函数(operator.add)。请注意,第一个键保持不变。假设图的输入是 {"foo": 1, "bar": ["hi"]}。然后假设第一个 Node 返回 {"foo": 2}。这被视为对状态的更新。请注意,Node 不需要返回整个 State 模式——只需要一个更新。应用此更新后,State 将变为 {"foo": 2, "bar": ["hi"]}。如果第二个节点返回 {"bar": ["bye"]},那么 State 将变为 {"foo": 2, "bar": ["hi", "bye"]}。请注意,这里 bar 键是通过将两个列表相加来更新的。

覆盖

在某些情况下,您可能希望绕过 reducer 并直接覆盖状态值。LangGraph 为此提供了 Overwrite 类型。在此了解如何使用 Overwrite

在图状态中使用消息

为什么使用消息?

大多数现代 LLM 提供商都有一个聊天模型接口,接受消息列表作为输入。特别是 LangChain 的聊天模型接口接受消息对象列表作为输入。这些消息有多种形式,例如 HumanMessage(用户输入)或 AIMessage(LLM 响应)。

要了解更多关于消息对象的信息,请参阅消息概念指南

在您的图中使用消息

在许多情况下,将先前的对话历史记录作为消息列表存储在您的图状态中是有帮助的。为此,我们可以向图状态添加一个键(通道),用于存储 Message 对象列表,并使用 reducer 函数对其进行注释(参见下面的示例中的 messages 键)。reducer 函数对于告诉图如何用每次状态更新(例如,当节点发送更新时)来更新状态中的 Message 对象列表至关重要。如果您不指定 reducer,每次状态更新都会用最近提供的值覆盖消息列表。如果您想简单地将消息追加到现有列表中,可以使用 operator.add 作为 reducer。

但是,您可能还想手动更新图状态中的消息(例如,人在回路中)。如果您使用 operator.add,您发送到图的手动状态更新将被追加到现有的消息列表中,而不是更新现有消息。为了避免这种情况,您需要一个能够跟踪消息 ID 并在更新时覆盖现有消息的 reducer。为了实现这一点,您可以使用预构建的 add_messages 函数。对于全新的消息,它只会追加到现有列表,但它也会正确处理现有消息的更新。

序列化

除了跟踪消息ID外,每当在messages通道上接收到状态更新时,add_messages函数还会尝试将消息反序列化为LangChain的Message对象。

有关LangChain序列化/反序列化的更多信息,请参见此处。这允许以以下格式发送图输入/状态更新:

python
# this is supported
{"messages": [HumanMessage(content="message")]}

# and this is also supported
{"messages": [{"type": "human", "content": "message"}]}

由于在使用add_messages时,状态更新总是被反序列化为LangChain的Messages,因此你应该使用点表示法来访问消息属性,例如state["messages"][-1].content

下面是一个使用add_messages作为其归约器函数的图示例。

python
from langchain.messages import AnyMessage
from langgraph.graph.message import add_messages
from typing import Annotated
from typing_extensions import TypedDict

class GraphState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]

MessagesState

由于在状态中拥有一个消息列表非常常见,因此存在一个名为MessagesState的预构建状态,它使得使用消息变得容易。MessagesState定义了一个单一的messages键,它是一个AnyMessage对象列表,并使用add_messages归约器。通常,除了消息之外,还需要跟踪更多的状态,所以我们看到人们子类化这个状态并添加更多字段,例如:

python
from langgraph.graph import MessagesState

class State(MessagesState):
    documents: list[str]

节点

在LangGraph中,节点是接受以下参数的Python函数(同步或异步):

  1. state – 图的状态
  2. config – 一个包含配置信息(如thread_id)和追踪信息(如tags)的RunnableConfig对象
  3. runtime – 一个包含运行时context和其他信息(如storestream_writer)的Runtime对象

类似于NetworkX,你可以使用add_node方法将这些节点添加到图中:

python
from dataclasses import dataclass
from typing_extensions import TypedDict

from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph
from langgraph.runtime import Runtime

class State(TypedDict):
    input: str
    results: str

@dataclass
class Context:
    user_id: str

builder = StateGraph(State)

def plain_node(state: State):
    return state

def node_with_runtime(state: State, runtime: Runtime[Context]):
    print("In node: ", runtime.context.user_id)
    return {"results": f"Hello, {state['input']}!"}

def node_with_config(state: State, config: RunnableConfig):
    print("In node with thread_id: ", config["configurable"]["thread_id"])
    return {"results": f"Hello, {state['input']}!"}

builder.add_node("plain_node", plain_node)
builder.add_node("node_with_runtime", node_with_runtime)
builder.add_node("node_with_config", node_with_config)
...

在幕后,函数被转换为RunnableLambda,它为你的函数添加了批处理和异步支持,以及原生的追踪和调试功能。

如果你向图中添加一个节点而没有指定名称,它将被赋予一个默认名称,等同于函数名。

python
builder.add_node(my_node)
# You can then create edges to/from this node by referencing it as `"my_node"`

START 节点

START节点是一个特殊节点,代表将用户输入发送到图的节点。引用此节点的主要目的是确定应该首先调用哪些节点。

python
from langgraph.graph import START

graph.add_edge(START, "node_a")

END 节点

END节点是一个代表终端节点的特殊节点。当你想要表示哪些边在完成后没有后续操作时,会引用此节点。

python
from langgraph.graph import END

graph.add_edge("node_a", END)

节点缓存

LangGraph支持基于节点输入的任务/节点缓存。要使用缓存:

  • 在编译图(或指定入口点)时指定一个缓存
  • 为节点指定缓存策略。每个缓存策略支持:
    • key_func,用于基于节点输入生成缓存键,默认为使用pickle对输入进行hash
    • ttl,缓存的生存时间(秒)。如果未指定,缓存将永不过期。

例如:

python
import time
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy

class State(TypedDict):
    x: int
    result: int

builder = StateGraph(State)

def expensive_node(state: State) -> dict[str, int]:
    # expensive computation
    time.sleep(2)
    return {"result": state["x"] * 2}

builder.add_node("expensive_node", expensive_node, cache_policy=CachePolicy(ttl=3))
builder.set_entry_point("expensive_node")
builder.set_finish_point("expensive_node")

graph = builder.compile(cache=InMemoryCache())

print(graph.invoke({"x": 5}, stream_mode='updates'))    
# [{'expensive_node': {'result': 10}}]
print(graph.invoke({"x": 5}, stream_mode='updates'))    
# [{'expensive_node': {'result': 10}, '__metadata__': {'cached': True}}]
  1. 第一次运行需要两秒钟(由于模拟了昂贵的计算)。
  2. 第二次运行利用缓存并快速返回。

边定义了逻辑如何路由以及图如何决定停止。这是你的代理如何工作以及不同节点如何相互通信的重要组成部分。有几种关键类型的边:

  • 普通边:直接从一个节点到下一个节点。
  • 条件边:调用一个函数来确定接下来要转到哪个节点(或多个节点)。
  • 入口点:当用户输入到达时首先调用哪个节点。
  • 条件入口点:调用一个函数来确定当用户输入到达时首先调用哪个节点(或多个节点)。

一个节点可以有多个出边。如果一个节点有多个出边,所有这些目标节点将作为下一个超步的一部分并行执行。

普通边

如果你总是想从节点A转到节点B,可以直接使用add_edge方法。

python
graph.add_edge("node_a", "node_b")

条件边

如果你想有条件地路由到一个或多个边(或选择性地终止),可以使用add_conditional_edges方法。该方法接受一个节点名称和一个在该节点执行后调用的“路由函数”:

python
graph.add_conditional_edges("node_a", routing_function)

与节点类似,routing_function接受图的当前state并返回一个值。

默认情况下,routing_function的返回值被用作下一个要发送状态的节点(或节点列表)的名称。所有这些节点将作为下一个超步的一部分并行运行。

你可以选择提供一个字典,将routing_function的输出映射到下一个节点的名称。

python
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})

如果你想在单个函数中结合状态更新和路由,请使用Command而不是条件边。

入口点

入口点是图启动时首先运行的节点。你可以使用从虚拟START节点到要执行的第一个节点的add_edge方法来指定图的入口位置。

python
from langgraph.graph import START

graph.add_edge(START, "node_a")

条件入口点

条件入口点允许你根据自定义逻辑从不同的节点开始。你可以使用从虚拟START节点出发的add_conditional_edges来实现这一点。

python
from langgraph.graph import START

graph.add_conditional_edges(START, routing_function)

你可以选择提供一个字典,将routing_function的输出映射到下一个节点的名称。

python
graph.add_conditional_edges(START, routing_function, {True: "node_b", False: "node_c"})

Send

默认情况下,NodesEdges是预先定义的,并在相同的共享状态上操作。然而,有些情况下,确切的边是事先未知的,和/或你可能希望同时存在不同版本的State。一个常见的例子是map-reduce设计模式。在这种设计模式中,第一个节点可能生成一个对象列表,你可能希望将其他节点应用于所有这些对象。对象的数量可能事先未知(意味着边的数量可能未知),并且下游Node的输入State应该不同(每个生成的对象一个)。

为了支持这种设计模式,LangGraph支持从条件边返回Send对象。Send接受两个参数:第一个是节点名称,第二个是传递给该节点的状态。

python
def continue_to_jokes(state: OverallState):
    return [Send("generate_joke", {"subject": s}) for s in state['subjects']]

graph.add_conditional_edges("node_a", continue_to_jokes)

Command

将控制流(边)和状态更新(节点)结合起来可能很有用。例如,你可能希望在同一个节点中同时执行状态更新决定接下来转到哪个节点。LangGraph提供了一种方法,通过从节点函数返回一个Command对象来实现这一点:

python
def my_node(state: State) -> Command[Literal["my_other_node"]]:
    return Command(
        # state update
        update={"foo": "bar"},
        # control flow
        goto="my_other_node"
    )

使用Command,你还可以实现动态控制流行为(与条件边完全相同):

python
def my_node(state: State) -> Command[Literal["my_other_node"]]:
    if state["foo"] == "bar":
        return Command(update={"foo": "baz"}, goto="my_other_node")

当在你的节点函数中返回Command时,你必须添加返回类型注解,并列出节点可以路由到的节点名称列表,例如Command[Literal["my_other_node"]]。这对于图渲染是必要的,并告诉LangGraphmy_node可以导航到my_other_node

查看这个操作指南,了解如何使用Command的端到端示例。

我应该何时使用Command而不是条件边?

  • 当你需要同时更新图状态路由到不同节点时,使用Command。例如,在实现多代理交接时,路由到不同的代理并向该代理传递一些信息非常重要。
  • 使用条件边来有条件地在节点之间路由,而不更新状态。

导航到父图中的节点

如果你正在使用子图,你可能希望从子图内的节点导航到不同的子图(即父图中的不同节点)。为此,你可以在Command中指定graph=Command.PARENT

python
def my_node(state: State) -> Command[Literal["other_subgraph"]]:
    return Command(
        update={"foo": "bar"},
        goto="other_subgraph",  # where `other_subgraph` is a node in the parent graph
        graph=Command.PARENT
    )

graph设置为Command.PARENT将导航到最近的父图。

当你从子图节点向父图节点发送更新,且更新的键由父图和子图状态模式共享时,你必须在父图状态中为你正在更新的键定义一个归约器。请参阅此示例

这在实现多代理交接时特别有用。

查看本指南了解详情。

在工具内部使用

一个常见的用例是从工具内部更新图状态。例如,在客户支持应用程序中,你可能希望在对话开始时根据客户的账号或ID查找客户信息。

请参阅本指南了解详情。

人在回路中

Command 是人机协同工作流的重要组成部分:当使用 interrupt() 收集用户输入时,Command 随后用于提供输入并通过 Command(resume="用户输入") 恢复执行。查看此概念指南了解更多信息。

图迁移

LangGraph 可以轻松处理图定义(节点、边和状态)的迁移,即使在使用检查点跟踪状态时也是如此。

  • 对于图末尾的线程(即未中断的线程),您可以更改图的整个拓扑结构(即所有节点和边,删除、添加、重命名等)
  • 对于当前中断的线程,我们支持除重命名/删除节点之外的所有拓扑更改(因为该线程可能即将进入一个不再存在的节点)——如果这成为阻碍,请联系我们,我们可以优先解决。
  • 对于修改状态,我们在添加和删除键方面具有完全的向前和向后兼容性
  • 重命名的状态键在现有线程中会丢失其保存的状态
  • 类型以不兼容方式更改的状态键目前可能会在包含更改前状态的线程中引发问题——如果这成为阻碍,请联系我们,我们可以优先解决。

运行时上下文

创建图时,可以为传递给节点的运行时上下文指定 context_schema。这对于向节点传递不属于图状态的信息非常有用。例如,您可能希望传递模型名称或数据库连接等依赖项。

python
@dataclass
class ContextSchema:
    llm_provider: str = "openai"

graph = StateGraph(State, context_schema=ContextSchema)

然后,您可以使用 invoke 方法的 context 参数将此上下文传递到图中。

python
graph.invoke(inputs, context={"llm_provider": "anthropic"})

然后,您可以在节点或条件边内部访问和使用此上下文:

python
from langgraph.runtime import Runtime

def node_a(state: State, runtime: Runtime[ContextSchema]):
    llm = get_llm(runtime.context.llm_provider)
    # ...

查看此指南以获取关于配置的完整解析。

递归限制

递归限制设置了图在单次执行期间可以执行的超级步骤的最大数量。一旦达到限制,LangGraph 将引发 GraphRecursionError。默认情况下,此值设置为 25 步。递归限制可以在运行时在任何图上设置,并通过配置字典传递给 invoke/stream。重要的是,recursion_limit 是一个独立的 config 键,不应像所有其他用户定义的配置一样传递到 configurable 键内部。请参见以下示例:

python
graph.invoke(inputs, config={"recursion_limit": 5}, context={"llm": "anthropic"})

阅读此操作指南以了解更多关于递归限制的工作原理。

访问和处理递归计数器

当前步骤计数器可在任何节点内的 config["metadata"]["langgraph_step"] 中访问,允许在达到递归限制之前进行主动的递归处理。这使您能够在图逻辑中实现优雅降级策略。

工作原理

步骤计数器存储在 config["metadata"]["langgraph_step"] 中。递归限制检查遵循以下逻辑:step > stop,其中 stop = step + recursion_limit + 1。当超过限制时,LangGraph 会引发 GraphRecursionError

访问当前步骤计数器

您可以在任何节点内访问当前步骤计数器以监控执行进度。

python
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph

def my_node(state: dict, config: RunnableConfig) -> dict:
    current_step = config["metadata"]["langgraph_step"]
    print(f"Currently on step: {current_step}")
    return state

主动递归处理

LangGraph 提供了一个 RemainingSteps 托管值,用于跟踪在达到递归限制之前剩余的步骤数。这允许在图内进行优雅降级。

python
from typing import Annotated, Literal
from langgraph.graph import StateGraph, START, END
from langgraph.managed import RemainingSteps

class State(TypedDict):
    messages: Annotated[list, lambda x, y: x + y]
    remaining_steps: RemainingSteps  # Managed value - tracks steps until limit

def reasoning_node(state: State) -> dict:
    # RemainingSteps is automatically populated by LangGraph
    remaining = state["remaining_steps"]

    # Check if we're running low on steps
    if remaining <= 2:
        return {"messages": ["Approaching limit, wrapping up..."]}

    # Normal processing
    return {"messages": ["thinking..."]}

def route_decision(state: State) -> Literal["reasoning_node", "fallback_node"]:
    """Route based on remaining steps"""
    if state["remaining_steps"] <= 2:
        return "fallback_node"
    return "reasoning_node"

def fallback_node(state: State) -> dict:
    """Handle cases where recursion limit is approaching"""
    return {"messages": ["Reached complexity limit, providing best effort answer"]}

# Build graph
builder = StateGraph(State)
builder.add_node("reasoning_node", reasoning_node)
builder.add_node("fallback_node", fallback_node)
builder.add_edge(START, "reasoning_node")
builder.add_conditional_edges("reasoning_node", route_decision)
builder.add_edge("fallback_node", END)

graph = builder.compile()

# RemainingSteps works with any recursion_limit
result = graph.invoke({"messages": []}, {"recursion_limit": 10})

主动与被动方法

处理递归限制主要有两种方法:主动(在图内监控)和被动(在外部捕获错误)。

python
from typing import Annotated, Literal, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.managed import RemainingSteps
from langgraph.errors import GraphRecursionError

class State(TypedDict):
    messages: Annotated[list, lambda x, y: x + y]
    remaining_steps: RemainingSteps

# Proactive Approach (recommended) - using RemainingSteps
def agent_with_monitoring(state: State) -> dict:
    """Proactively monitor and handle recursion within the graph"""
    remaining = state["remaining_steps"]

    # Early detection - route to internal handling
    if remaining <= 2:
        return {
            "messages": ["Approaching limit, returning partial result"]
        }

    # Normal processing
    return {"messages": [f"Processing... ({remaining} steps remaining)"]}

def route_decision(state: State) -> Literal["agent", END]:
    if state["remaining_steps"] <= 2:
        return END
    return "agent"

# Build graph
builder = StateGraph(State)
builder.add_node("agent", agent_with_monitoring)
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", route_decision)
graph = builder.compile()

# Proactive: Graph completes gracefully
result = graph.invoke({"messages": []}, {"recursion_limit": 10})

# Reactive Approach (fallback) - catching error externally
try:
    result = graph.invoke({"messages": []}, {"recursion_limit": 10})
except GraphRecursionError as e:
    # Handle externally after graph execution fails
    result = {"messages": ["Fallback: recursion limit exceeded"]}

这些方法之间的主要区别是:

方法检测时机处理方式控制流
主动(使用 RemainingSteps在达到限制之前通过条件路由在图内部处理图继续执行到完成节点
被动(捕获 GraphRecursionError在超过限制之后在 try/catch 块中在图外部处理图执行终止

主动方法的优势:

  • 在图内实现优雅降级
  • 可以在检查点中保存中间状态
  • 提供更好的用户体验(部分结果)
  • 图正常完成(无异常)

被动方法的优势:

  • 实现更简单
  • 无需修改图逻辑
  • 集中式错误处理

其他可用的元数据

除了 langgraph_step 之外,以下元数据也可在 config["metadata"] 中获取:

python
def inspect_metadata(state: dict, config: RunnableConfig) -> dict:
    metadata = config["metadata"]

    print(f"Step: {metadata['langgraph_step']}")
    print(f"Node: {metadata['langgraph_node']}")
    print(f"Triggers: {metadata['langgraph_triggers']}")
    print(f"Path: {metadata['langgraph_path']}")
    print(f"Checkpoint NS: {metadata['langgraph_checkpoint_ns']}")

    return state

可视化

能够可视化图通常很有帮助,尤其是当它们变得更加复杂时。LangGraph 提供了几种内置的可视化图的方法。查看此操作指南以获取更多信息。

LangChain 中文文档