本指南演示了 LangGraph 图 API 的基础知识。它将引导你了解状态,以及如何构建常见的图结构,如序列、分支和循环。它还涵盖了 LangGraph 的控制功能,包括用于 map-reduce 工作流的 Send API,以及用于将状态更新与跨节点的“跳转”相结合的 Command API。

设置

安装 langgraph:
设置 LangSmith 以便更好地调试注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 让你能够使用跟踪数据来调试、测试和监控你使用 LangGraph 构建的 LLM 应用程序——阅读文档了解更多入门信息。

定义和更新状态

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

定义状态

LangGraph 中的状态使用 StateSchema 类定义。它提供了一个统一的 API,可以接受字段的标准架构(如 Zod),以及特殊值类型如 ReducedValue、MessagesValue 和 UntrackedValue。 默认情况下,图将具有相同的输入和输出架构,而状态决定了该架构。有关如何定义不同的输入和输出架构,请参阅定义输入和输出架构。 让我们考虑一个使用消息的简单例子。这代表了许多 LLM 应用程序中一种通用的状态表示方式。更多详情请参阅我们的概念页面。
此状态跟踪一个消息对象列表,以及一个额外的整数字段。

更新状态

让我们构建一个包含单个节点的示例图。我们的节点只是一个 TypeScript 函数,它读取图的状态并对其进行更新。此函数的第一个参数始终是状态:
这个节点只是简单地向我们的消息列表追加一条消息(归约器负责处理拼接),并填充一个额外字段。
节点应直接返回对状态的更新,而不是直接修改状态本身。
接下来,让我们定义一个包含此节点的简单图。我们使用 StateGraph 来定义一个在此状态上操作的图。然后使用 addNode 来填充我们的图。
LangGraph 提供了内置工具来可视化你的图。让我们检查一下我们的图。有关可视化的详情,请参阅可视化你的图。
在这种情况下,我们的图只执行一个节点。让我们进行一次简单的调用:
请注意:
  • 我们通过更新状态的单个键来启动调用。
  • 我们在调用结果中接收整个状态。
为方便起见,我们通常通过日志记录来检查消息对象的内容:

使用归约器处理状态更新

状态中的每个键都可以拥有自己独立的归约器函数,该函数控制来自节点的更新如何被应用。如果没有显式指定归约器函数,则假定对该键的所有更新都应覆盖它。 在前面的例子中,我们使用了 MessagesValue,它已经内置了一个归约器。对于自定义字段,你可以使用 ReducedValue 来定义如何应用更新。 在前面的例子中,我们的节点通过向 "messages" 键追加一条消息来更新状态。MessagesValue 归约器会自动处理:
我们的节点可以简单地返回新消息(归约器处理拼接):

MessagesValue

在实践中,更新消息列表还有其他注意事项:
  • 我们可能希望更新状态中的现有消息。
  • 我们可能希望接受消息格式的简写形式,例如 OpenAI 格式。
LangGraph 包含内置的 MessagesValue 来处理这些考虑:
对于涉及聊天模型的应用程序来说,这是一种通用的状态表示。LangGraph 为了方便起见,包含了预构建的 MessagesValue,因此我们可以这样写:

定义输入和输出架构

默认情况下,StateGraph 使用单一架构操作,并且所有节点都预期使用该架构进行通信。然而,也可以为图定义不同的输入和输出架构。 当指定了不同的架构时,内部仍会使用一个架构在节点之间通信。输入架构确保提供的输入符合预期的结构,而输出架构则过滤内部数据,仅根据定义的输出架构返回相关信息。 下面,我们将看到如何定义不同的输入和输出架构。
请注意,invoke 的输出只包含输出架构。

在节点之间传递私有状态

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

替代状态定义

虽然 StateSchema 是定义状态的推荐方法,但 LangGraph 也支持其他几种方法。本节涵盖所有可用选项。

通道 API

通道 API 提供了对状态管理的底层控制。LangGraph 提供了几种内置通道类型: 使用对象简写: 当你传递一个包含 reducer 和 default 的对象时,它会创建一个 BinaryOperatorAggregate 通道。传递 null 会创建一个 LastValue 通道:
直接使用通道类: 为了获得更多控制,你可以直接实例化通道类:

Annotation.Root

Annotation.Root 提供了一种声明式的方式来定义带有归约器的状态。它类似于 StateSchema,但使用不同的语法:

使用 Zod v3 的 Zod 对象

当使用 Zod v3 时,你可以使用普通的 z.object() 架构来定义状态。LangGraph 通过一个 .langgraph 插件扩展了 Zod v3,该插件提供了 .reducer() 和 .metadata() 方法:

使用 Zod v4 的 Zod 对象

Zod v4 使用基于注册表的方法。使用 LangGraph 注册表来向架构字段附加元数据:

比较表

添加运行时配置

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

添加重试策略

在许多用例中,你可能希望你的节点具有自定义的重试策略,例如,如果你正在调用 API、查询数据库或调用 LLM 等。LangGraph 允许你为节点添加重试策略。 要配置重试策略,请将 retryPolicy 参数传递给 addNode。retryPolicy 参数接受一个 RetryPolicy 对象。下面我们将使用默认参数实例化一个 RetryPolicy 对象,并将其与一个节点关联:
默认情况下,重试策略会对除以下异常之外的任何异常进行重试:
  • TypeError
  • SyntaxError
  • ReferenceError
考虑一个从 SQL 数据库读取的示例。下面我们向节点传递两个不同的重试策略:

在节点内部访问执行信息

你可以通过 runtime.executionInfo 访问执行标识和重试信息。这会显示线程、运行和检查点标识符以及重试状态,而无需直接从 config 读取。

访问线程和运行 ID

使用 executionInfo 在节点内部访问线程 ID、运行 ID 和其他标识字段:

根据重试状态调整行为

当节点具有重试策略时,使用 executionInfo 检查当前尝试次数,并在第一次尝试失败后切换到回退方案:
即使没有重试策略,executionInfo 也可在 Runtime 对象上使用——nodeAttempt 默认为 1,nodeFirstAttemptTime 设置为节点开始执行的时间。

在节点内部访问服务器信息

当你的图在 LangGraph Server 上运行时,你可以通过 runtime.serverInfo 访问服务器特定的元数据。
当图未在 LangGraph Server 上运行时,serverInfo 为 null。
需要 deepagents>=1.9.0(或 @langchain/langgraph>=1.2.8)才能使用 runtime.executionInfo 和 runtime.serverInfo。

创建步骤序列

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

创建分支

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

并行运行图节点

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

条件分支

如果你的扇出应该在运行时根据状态变化,你可以使用 addConditionalEdges 来使用图状态选择一个或多个路径。参见下面的示例,其中节点 a 生成一个状态更新,决定后续节点。
你的条件边可以路由到多个目标节点。例如:

Map-Reduce 和 Send API

LangGraph 使用 Send API 支持 Map-Reduce 和其他高级分支模式。以下是如何使用它的一个示例:

创建和控制循环

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

施加递归限制

在某些应用中,我们可能无法保证会达到给定的终止条件。在这些情况下,我们可以设置图的 递归限制。这会在给定数量的 超级步骤 后引发 GraphRecursionError。然后我们可以捕获并处理此异常:

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

将控制流(边)和状态更新(节点)组合起来可能很有用。例如,您可能希望在同一个节点中同时执行状态更新并决定下一步要跳转到哪个节点。LangGraph 提供了一种方法,通过从节点函数返回一个 Command 对象来实现:
下面我们展示一个端到端的示例。让我们创建一个包含 3 个节点的简单图:A、B 和 C。我们将首先执行节点 A,然后根据节点 A 的输出决定接下来是跳转到节点 B 还是节点 C。
现在我们可以使用上述节点创建 StateGraph。请注意,该图没有用于路由的条件边!这是因为控制流是在 nodeA 内部使用 Command 定义的。
您可能注意到我们使用 ends 来指定 nodeA 可以导航到哪些节点。这对于图渲染是必要的,并告诉 LangGraph nodeA 可以导航到 nodeB 和 nodeC。
如果我们多次运行该图,我们会看到它根据节点 A 中的随机选择走不同的路径(A -> B 或 A -> C)。

导航到父图中的节点

如果您正在使用子图,您可能希望从子图中的一个节点导航到另一个子图(即父图中的不同节点)。为此,您可以在 Command 中指定 graph=Command.PARENT:
让我们用上面的例子来演示。我们将通过把上面示例中的 nodeA 改成一个单节点图,并将其作为子图添加到我们的父图中来实现。
使用 Command.PARENT 的状态更新 当您从子图节点向父图节点发送更新,并且该键由父图和子图的状态模式共享时,您必须为父图状态中正在更新的键定义一个 reducer。请参见下面的示例。

在工具内部使用

一个常见的用例是从工具内部更新图状态。例如,在一个客户支持应用中,您可能希望在对话开始时根据客户的账号或 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

如果愿意,我们可以将 Graph 渲染成 .png。这里使用 Mermaid.ink API 来生成图表。