Skip to main content
本指南演示了 LangGraph 的 Graph API 的基础知识。它将介绍 状态,以及组合常见的图结构,如 序列分支循环。它还涵盖了 LangGraph 的控制功能,包括用于 map-reduce 工作流的 Send API 以及用于将状态更新与节点间的“跳转”相结合的 Command API

设置

安装 langgraph
设置 LangSmith 以获得更好的调试体验注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用——有关如何入门的更多信息,请阅读 文档

定义和更新状态

在此我们展示如何在 LangGraph 中定义和更新 状态。我们将演示:
  1. 如何使用状态来定义图的 模式
  2. 如何使用 归约器 来控制如何处理状态更新。

定义状态

LangGraph 中的 状态 可以是 TypedDictPydantic 模型或 dataclass。下面我们将使用 TypedDict。有关使用 Pydantic 的详细信息,请参阅 为图状态使用 Pydantic 模型 默认情况下,图将具有相同的输入和输出模式,状态决定了该模式。有关如何定义不同的输入和输出模式,请参阅 定义输入和输出模式 让我们考虑一个使用 消息 的简单示例。这代表了适用于许多 LLM 应用的通用状态表述。有关更多详细信息,请参阅我们的 概念页面
此状态跟踪 消息 对象列表,以及一个额外的整数字段。

更新状态

让我们构建一个包含单个节点的示例图。我们的 节点 只是一个读取图的 state 并对其进行更新的 Python 函数。此函数的第一个参数始终是 state:
此节点只是将一条消息附加到我们的消息列表中,并填充一个额外字段。
节点应直接返回对状态的更新,而不是修改状态。
接下来让我们定义一个包含此节点的简单图。我们使用 StateGraph 来定义一个在此状态下操作的图。然后我们使用 add_node 来填充我们的图。
LangGraph 提供了用于可视化您的图的内置实用程序。让我们检查我们的图。有关可视化的详细信息,请参阅 可视化您的图
包含单个节点的简单图 在这种情况下,我们的图只执行单个节点。让我们继续进行简单的调用:
请注意:
  • 我们通过更新状态的单个键来启动调用。
  • 我们在调用结果中接收整个状态。
为了方便起见,我们经常通过美化打印来检查 消息对象 的内容:

使用归约器处理状态更新

状态中的每个键都可以有自己的独立 归约器 函数,它控制如何应用来自节点的更新。如果没有显式指定归约器函数,则假设对该键的所有更新都应覆盖它。 对于 TypedDict 状态模式,我们可以通过使用归约器函数注释状态的相应字段来定义归约器。 在之前的示例中,我们的节点通过在消息列表中追加一条消息来更新状态中的 "messages" 键。下面,我们为此键添加一个归约器,以便自动追加更新:
现在我们的节点可以简化为:

MessagesState

在实践中,更新消息列表还有其他注意事项:
  • 我们可能希望更新状态中的现有消息。
  • 我们可能希望接受 消息格式 的简写,例如 OpenAI 格式
LangGraph 包含一个内置归约器 add_messages,可处理这些注意事项:
这是涉及 聊天模型 的应用程序的通用状态表示。LangGraph 包含预构建的 MessagesState 以供方便使用,因此我们可以拥有:

使用 Overwrite 绕过归约器

在某些情况下,您可能想要绕过归约器并直接覆盖状态值。LangGraph 为此目的提供了 Overwrite 类型。当节点返回用 Overwrite 包装的值时,归约器将被绕过,通道将直接设置为该值。 当您想要重置或替换累积的状态而不是将其与现有值合并时,这很有用。
您也可以使用带有特殊键 "__overwrite__" 的 JSON 格式:
当节点并行执行时,在同一个超级步骤中,只有一个节点可以在相同的状态键上使用 Overwrite。如果多个节点尝试在同一个超级步骤中覆盖相同的键,将引发 InvalidUpdateError

定义输入和输出模式

默认情况下,StateGraph 使用单一模式运行,所有节点都预期使用该模式进行通信。但是,也可以为图定义不同的输入和输出模式。 当指定不同的模式时,内部仍将使用模式用于节点之间的通信。输入模式确保提供的输入符合预期的结构,而输出模式过滤内部数据,仅根据定义的模式返回相关信息。 下面,我们将看到如何定义不同的输入和输出模式。
请注意,调用的输出仅包含输出模式。

在节点之间传递私有状态

在某些情况下,您可能希望节点交换对中间逻辑至关重要但不需要成为图主模式一部分的信息。这种私有数据与图的整体输入/输出无关,应仅在特定节点之间共享。 下面,我们将创建一个由三个节点(node_1、node_2 和 node_3)组成的示例顺序图,其中私有数据在前两个步骤(node_1 和 node_2)之间传递,而第三个步骤(node_3)只能访问公共整体状态。

为图状态使用 Pydantic 模型

StateGraph 在初始化时接受 state_schema 参数,指定图中节点可以访问和更新的 state 的“形状”。 在我们的示例中,我们通常使用 Python 原生的 TypedDictdataclass 作为 state_schema,但 state_schema 可以是任何 类型 在这里,我们将看到如何使用 Pydantic BaseModel 作为 state_schema 来为 输入 添加运行时验证。
已知限制
  • 目前,图的输出 不会 是 pydantic 模型的实例。
  • 运行时验证仅发生在图中第一个节点的输入上,而不发生在后续节点或输出上。
  • pydantic 的验证错误跟踪不显示错误出现在哪个节点。
  • Pydantic 的递归验证可能很慢。对于性能敏感的应用程序,您可能需要考虑使用 dataclass
使用 无效 输入调用图
有关 Pydantic 模型状态的附加功能,请参见下方:
当使用 Pydantic 模型作为状态模式时,了解序列化如何工作非常重要,特别是在以下情况:
  • 将 Pydantic 对象作为输入传递
  • 接收来自图的输出
  • 处理嵌套的 Pydantic 模型
让我们看看这些行为的实际效果。
Pydantic 会对某些数据类型执行运行时类型强制转换。这可能很有帮助,但如果不知道这一点,也可能导致意外行为。
当在状态模式中使用 LangChain 消息类型时,序列化方面有重要的注意事项。在使用消息对象通过网络传输时,您应该使用 AnyMessage(而不是 BaseMessage)来进行正确的序列化/反序列化。

添加运行时配置

有时您希望在调用图时能够配置它。例如,您可能希望在运行时指定要使用什么 LLM 或系统提示,而 无需将这些参数污染到图状态中 要添加运行时配置:
  1. 指定配置的 schema
  2. 将配置添加到节点或条件边的函数签名中
  3. 将配置传递给图。
下面是一个简单示例:
下面我们演示一个实际示例,在其中配置在运行时使用什么 LLM。我们将同时使用 OpenAI 和 Anthropic 模型。
下面我们演示一个实际示例,在其中配置两个参数:在运行时使用的 LLM 和系统消息。

添加重试策略

有许多用例可能需要您的节点具有自定义重试策略,例如如果您正在调用 API、查询数据库或调用 LLM 等。LangGraph 允许您将重试策略添加到节点。 要配置重试策略,请将 retry_policy 参数传递给 add_noderetry_policy 参数接受 RetryPolicy 命名元组对象。下面我们使用默认参数实例化 RetryPolicy 对象并将其与节点关联:
默认情况下,retry_on 参数使用 default_retry_on 函数,它在除以下之外的任何异常上重试:
  • ValueError
  • TypeError
  • ArithmeticError
  • ImportError
  • LookupError
  • NameError
  • SyntaxError
  • RuntimeError
  • ReferenceError
  • StopIteration
  • StopAsyncIteration
  • OSError
此外,对于来自流行的 http 请求库(如 requestshttpx)的异常,它仅在 5xx 状态码上重试。
考虑一个我们从 SQL 数据库读取的示例。下面我们将两个不同的重试策略传递给节点:

在节点内访问执行信息

需要 langgraph>=1.1.3。当节点具有重试策略时,您可以通过 runtime.execution_info 检查当前尝试次数和第一次尝试的时间。这对于根据重试状态调整行为非常有用(例如,在第一次尝试失败后切换到备用提供商)。
即使没有重试策略,execution_info 也可用于 Runtime 对象——node_attempt 默认为 1node_first_attempt_time 设置为节点开始执行的时间。

添加节点缓存

节点缓存在您希望避免重复操作的情况下非常有用,例如在执行昂贵操作时(无论是时间还是成本方面)。LangGraph 允许您在图中的节点中添加个性化的缓存策略。 要配置缓存策略,请将 cache_policy 参数传递给 add_node 函数。在下面的示例中,使用 120 秒的生存时间和默认 key_func 生成器实例化 CachePolicy 对象。然后将其与节点关联:
然后,要为图启用节点级缓存,请在编译图时设置 cache 参数。下面的示例使用 InMemoryCache 来设置具有内存缓存的图,但也提供 SqliteCache

创建步骤序列

先决条件 本指南假设您熟悉上面关于 状态 的部分。
在此我们演示如何构建简单的步骤序列。我们将展示:
  1. 如何构建顺序图
  2. 构建类似图的内置简写。
要添加节点序列,我们使用 add_nodeadd_edge 方法:
我们也可以使用内置简写 .add_sequence
LangGraph 使为您的应用程序添加底层持久层变得容易。 这允许在节点执行之间对状态进行检查点,因此您的 LangGraph 节点管理:它们还决定执行步骤如何 流式传输,以及如何使用 Studio 可视化和调试您的应用程序。让我们演示一个端到端示例。我们将创建三个步骤的序列:
  1. 在状态的键中填充值
  2. 更新相同的值
  3. 填充不同的值
首先让我们定义我们的 状态。这管理图的 模式,还可以指定如何应用更新。有关更多详细信息,请参阅 使用归约器处理状态更新在我们的例子中,我们将只跟踪两个值:
我们的 节点 只是读取图的 state 并对其进行更新的 Python 函数。此函数的第一个参数始终是 state:
请注意,在向状态发出更新时,每个节点只需指定其希望更新的键的值。默认情况下,这将 覆盖 相应键的值。您还可以使用 归约器 来控制如何处理更新——例如,您可以将连续更新附加到键。有关更多详细信息,请参阅 使用归约器处理状态更新
最后,我们定义图。我们使用 StateGraph 来定义一个在此状态下操作的图。然后我们将使用 add_nodeadd_edge 来填充我们的图并定义其控制流。
指定自定义名称 您可以使用 add_node 为节点指定自定义名称:
请注意:
  • add_edge 接受节点的名称,对于函数默认为 node.__name__
  • 我们必须指定图的入口点。为此,我们添加一条带有 START 节点 的边。
  • 当没有更多节点可执行时,图停止。
我们接下来 编译 我们的图。这提供了对图结构的几个基本检查(例如,识别孤立节点)。如果我们通过 检查点器 为我们的应用程序添加持久性,它也将在此处传递。
LangGraph 提供了用于可视化您的图的内置实用程序。让我们检查我们的序列。有关可视化的详细信息,请参阅 可视化您的图
步骤序列图让我们继续进行简单的调用:
请注意:
  • 我们通过为单个状态键提供值来启动调用。我们必须始终至少提供一个键的值。
  • 我们传入的值被第一个节点覆盖。
  • 第二个节点更新了该值。
  • 第三个节点填充了不同的值。
内置简写 langgraph>=0.2.46 包含用于添加节点序列的内置简写 add_sequence。您可以如下编译相同的图:

创建分支

节点的并行执行对于加快整体图操作至关重要。LangGraph 原生支持节点的并行执行,可以显著提高基于图的工作流的性能。这种并行化是通过扇出和扇入机制实现的,利用标准边和 条件边。以下是一些示例,展示如何添加适合您的分支数据流。

并行运行图节点

在此示例中,我们从 Node A 扇出到 B 和 C,然后扇入到 D。使用我们的状态,我们指定归约器 add 操作。这将组合或累积特定键的值,而不是简单地覆盖现有值。对于列表,这意味着将新列表与现有列表连接。有关使用归约器更新状态的更多详细信息,请参阅上面的 状态归约器 部分。
并行执行图 使用归约器,您可以看到每个节点中添加的值都被累积了。
在上述示例中,节点 "b""c" 在同一个 超级步骤 中并发执行。因为它们在同一步骤中,所以节点 "d""b""c" 完成后执行。重要的是,来自并行超级步骤的更新可能不会按一致的顺序排列。如果您需要来自并行超级步骤的一致、预定顺序的更新,您应该将输出写入状态中的单独字段,并附带用于排序的值。
LangGraph 在 超级步骤 内执行节点,这意味着虽然并行分支并行执行,但整个超级步骤是 事务性 的。如果这些分支中的任何一个抛出异常,没有任何 更新应用于状态(整个超级步骤出错)。重要的是,当使用 检查点器 时,超级步骤内成功节点的結果会被保存,并且在恢复时不会重复。如果您有容易出错的(也许想处理不稳定的 API 调用)节点,LangGraph 提供两种解决方法:
  1. 您可以在节点内编写常规 Python 代码来捕获和处理异常。
  2. 您可以设置 retry_policy 以指导图重试抛出某些类型异常的节点。只有失败的分支会被重试,因此您无需担心执行冗余工作。
结合这两者,您可以执行并行执行并完全控制异常处理。
设置最大并发数 您可以通过在调用图时在 配置 中设置 max_concurrency 来控制最大并发任务数。

延迟节点执行

延迟节点执行在您希望延迟节点执行直到所有其他待处理任务完成时非常有用。这在分支长度不同的情况下特别相关,这在 map-reduce 流等工作流中很常见。 上面的示例展示了当每条路径只有一步时如何扇出和扇入。但是如果一个分支有多步怎么办?让我们在 "b" 分支中添加一个节点 "b_2"
延迟执行图
在上述示例中,节点 "b""c" 在同一个超级步骤中并发执行。我们在节点 d 上设置了 defer=True,因此它不会执行直到所有待处理任务完成。在这种情况下,这意味着 "d" 等待执行直到整个 "b" 分支完成。

条件分支

如果您的扇出应根据状态在运行时变化,您可以使用 add_conditional_edges 使用图状态选择一个或多个路径。见下方示例,其中节点 a 生成确定下一个节点的状态更新。
条件分支图
您的条件边可以路由到多个目标节点。例如:

Map-Reduce 和 Send API

LangGraph 支持使用 Send API 进行 map-reduce 和其他高级分支模式。以下是如何使用它的示例:
带有扇出的 map-reduce 图

创建和控制循环

在创建带有循环的图时,我们需要一种终止执行的机制。这通常是通过添加 条件边 来实现的,一旦达到某些终止条件,该边就会路由到 END 节点。 您也可以在调用或流式传输图时设置图递归限制。递归限制设置图在抛出错误之前允许执行的 超级步骤 数量。有关 递归限制概念 的更多信息。 让我们考虑一个简单的带有循环的图,以更好地了解这些机制如何工作。
若要返回状态的最后一个值而不是收到递归限制错误,请参阅 下一节
创建循环时,您可以包含指定终止条件的条件边:
要控制递归限制,请在配置中指定 "recursionLimit"。这将抛出 GraphRecursionError,您可以捕获并处理:
让我们定义一个带有简单循环的图。请注意,我们使用条件边来实现终止条件。
简单循环图 此架构类似于 ReAct agent,其中节点 "a" 是调用工具的模型,节点 "b" 代表工具。 在我们的 route 条件边中,我们指定在状态中的 "aggregate" 列表超过阈值长度后我们应该结束。 调用图时,我们看到我们在达到终止条件之前在节点 "a""b" 之间交替。

施加递归限制

在某些应用中,我们无法保证将达到给定的终止条件。在这些情况下,我们可以设置图的 递归限制。这将在给定数量的 超级步骤 后抛出 GraphRecursionError。然后我们可以捕获并处理此异常:
与其抛出 GraphRecursionError,我们可以向状态引入一个新键来跟踪到达递归限制所需的剩余步骤数。然后我们可以使用此键来确定是否应该结束运行。LangGraph 实现了特殊的 RemainingSteps 注解。在底层,它创建一个 ManagedValue 通道——一个在我们图运行期间存在且不再存在的状态通道。
为了更好地理解递归限制如何工作,让我们考虑一个更复杂的示例。下面我们实现一个循环,但一步扇出到两个节点:
带有分支的复杂循环图此图看起来很复杂,但可以概念化为 超级步骤 的循环:
  1. 节点 A
  2. 节点 B
  3. 节点 C 和 D
  4. 节点 A
我们有一个四个超级步骤的循环,其中节点 C 和 D 并发执行。像以前一样调用图时,我们看到我们在达到终止条件之前完成了两个完整的“圈”:
但是,如果我们将递归限制设置为四,我们只完成一圈,因为每圈是四个超级步骤:

异步

使用异步编程范式可以在并发运行 IO 绑定 代码时产生显著的性能提升(例如,并发 API 请求到聊天模型提供商)。 要将图的 sync 实现转换为 async 实现,您需要:
  1. 更新 nodes 使用 async def 而不是 def
  2. 更新代码内部以适当使用 await
  3. 根据需要调用图使用 .ainvoke.astream
由于许多 LangChain 对象实现了 Runnable Protocol,它具有所有 sync 方法的 async 变体,因此将 sync 图升级为 async 图通常很快。 见下方示例。为了演示底层 LLM 的异步调用,我们将包含一个聊天模型:
👉 阅读 OpenAI 聊天模型集成文档
异步流式传输 有关异步流式传输的示例,请参阅 流式传输指南

使用 Command 结合控制流和状态更新

结合控制流(边)和状态更新(节点)可能很有用。例如,您可能希望 同时 执行状态更新 决定下一个节点去哪里。LangGraph 提供了一种方法,即从节点函数返回 Command 对象:
我们在下面展示了一个端到端示例。让我们创建一个包含 3 个节点:A、B 和 C 的简单图。我们将首先执行节点 A,然后根据节点 A 的输出决定下一步是去节点 B 还是节点 C。
我们现在可以使用上述节点创建 StateGraph。请注意,图没有 条件边 用于路由!这是因为控制流是在 node_a 中使用 Command 定义的。
您可能注意到我们使用了 Command 作为返回类型注解,例如 Command[Literal["node_b", "node_c"]]。这对于图渲染是必要的,并告诉 LangGraph node_a 可以导航到 node_bnode_c
基于 Command 的图导航 如果我们多次运行图,我们会看到它根据节点 A 中的随机选择采取不同的路径(A -> B 或 A -> C)。

导航到父图中的节点

如果您正在使用 子图,您可能希望从子图中的节点导航到不同的子图(即父图中的不同节点)。为此,您可以在 Command 中指定 graph=Command.PARENT
让我们使用上面的示例演示此操作。我们将这样做,通过将上面的示例中的 nodeA 更改为我们将其作为子图添加到父图中的单节点图。
使用 Command.PARENT 的状态更新 当您从子图节点向父图节点发送更新时,对于父图和子图 状态模式 共享的键,您 必须 为父图状态中您要更新的键定义 归约器。见下方示例。

在工具中使用

一个常见的用例是从工具内部更新图状态。例如,在客户支持应用中,您可能希望在对话开始时根据账户号或 ID 查找客户信息。要从工具更新图状态,您可以从工具返回 Command(update={"my_custom_key": "foo", "messages": [...]})
当您从工具返回 Command 时,您 必须Command.update 中包含 messages(或用于消息历史的任何状态键),并且 messages 中的消息列表 必须 包含 ToolMessage。这对于生成的消息历史有效是必要的(LLM 提供商要求带有工具调用的 AI 消息后跟工具结果消息)。
如果您正在使用通过 Command 更新状态的工具,我们建议使用预构建的 ToolNode,它自动处理工具返回 Command 对象并将它们传播到图状态。如果您正在编写调用工具的自定义节点,则需要手动传播工具返回的 Command 对象作为节点的更新。

可视化您的图

在此我们演示如何可视化您创建的图。 您可以可视化任何任意 Graph,包括 StateGraph 让我们画分形图吧 :)。

Mermaid

我们还可以将图类转换为 Mermaid 语法。

PNG

如果更喜欢,我们可以将图渲染为 .png。这里有三种选择:
  • 使用 Mermaid.ink API(不需要额外的包)
  • 使用 Mermaid + Pyppeteer(需要 pip install pyppeteer
  • 使用 graphviz(需要 pip install graphviz
使用 Mermaid.Ink 默认情况下,draw_mermaid_png() 使用 Mermaid.Ink 的 API 生成图表。
分形图可视化 使用 Mermaid + Pyppeteer
使用 Graphviz