图
LangGraph 的核心是将智能体工作流建模为图。您使用三个关键组件来定义智能体的行为:-
State:一个共享数据结构,表示应用程序的当前快照。它可以是任何数据类型,但通常使用共享状态模式来定义。 -
Nodes:编码智能体逻辑的函数。它们接收当前状态作为输入,执行某些计算或产生副作用,并返回更新后的状态。 -
Edges:根据当前状态确定接下来执行哪个Node的函数。它们可以是条件分支或固定转换。
Nodes 和 Edges,您可以创建复杂的、循环的工作流,这些工作流会随时间推移而演变状态。然而,真正的威力来自于 LangGraph 如何管理该状态。
需要强调的是:Nodes 和 Edges 不过是函数——它们可以包含一个 LLM 或者只是普通的代码。
简而言之:节点执行工作,边决定下一步做什么。
LangGraph 底层的图算法使用消息传递来定义一个通用程序。当一个节点完成其操作时,它会沿着一条或多条边向其他节点发送消息。这些接收节点随后执行它们的函数,将结果消息传递给下一组节点,过程继续进行。受 Google 的 Pregel 系统启发,该程序以离散的“超级步骤”进行。
一个超级步骤可以被视为对图节点的一次迭代。并行运行的节点属于同一个超级步骤,而顺序运行的节点属于不同的超级步骤。在图执行开始时,所有节点都处于 inactive 状态。当节点在其任何传入边(或“通道”)上接收到新消息(状态)时,它会变为 active 状态。活动节点随后运行其函数并以更新进行响应。在每个超级步骤结束时,没有传入消息的节点通过将自己标记为 inactive 来投票 halt。当所有节点都处于 inactive 状态且没有消息在传输中时,图执行终止。
StateGraph
StateGraph 类是要使用的主要图类。它由用户定义的 State 对象参数化。
编译您的图
要构建您的图,您首先定义状态,然后添加节点和边,然后编译它。编译图到底是什么,为什么需要它? 编译是一个相当简单的步骤。它对图的结构进行一些基本检查(例如没有孤立节点等)。它也是您可以指定运行时参数(如检查点器和断点)的地方。您只需调用.compile 方法即可编译图:
State
定义图时,首先要定义图的State。State 包括图的模式以及指定如何将更新应用于状态的 reducer 函数。State 的模式将是图中所有 Nodes 和 Edges 的输入模式。您使用 StateSchema 类来定义状态,该类接受任何标准模式(如 Zod)用于单个字段,以及特殊值类型如 ReducedValue 和 MessagesValue。所有 Nodes 都会向 State 发出更新,然后使用指定的 reducer 函数应用这些更新。
Schema
指定图模式的主要方式是使用StateSchema 类。模式中的每个字段可以是:
- 用于简单字段的标准模式(成为“最后值”通道,在更新时覆盖)
- 用于需要自定义 reducer 函数的字段的
ReducedValue(当节点并行运行时) - 用于聊天消息列表的
MessagesValue(预置了消息感知的 reducer) - 用于不应被检查点的临时状态的
UntrackedValue
多个模式
通常,所有图节点都与单个模式通信。这意味着它们将读取和写入相同的状态通道。但是,在某些情况下,我们希望对此有更多控制:- 内部节点可以传递图输入/输出中不需要的信息。
- 我们可能还希望为图使用不同的输入/输出模式。例如,输出可能只包含一个相关的输出键。
PrivateState。
也可以为图定义显式的输入和输出模式。在这些情况下,我们定义一个“内部”模式,其中包含与图操作相关的_所有_键。但是,我们还定义 input 和 output 模式,它们是“内部”模式的子集,以约束图的输入和输出。有关更多详细信息,请参阅定义输入和输出模式。
让我们看一个例子:
-
我们将
state作为输入模式传递给node1。但是,我们写入foo,这是OverallState中的一个通道。我们如何写入未包含在输入模式中的状态通道?这是因为节点_可以写入图状态中的任何状态通道。_ 图状态是初始化时定义的状态通道的并集,包括OverallState和过滤器InputState和OutputState。 -
我们使用
StateGraph({ state: OverallState, input: InputState, output: OutputState })初始化图。我们如何在node2中写入PrivateState?如果该模式未在StateGraph初始化中传递,图如何访问该模式?我们可以这样做是因为_节点也可以声明额外的状态通道_,只要状态模式定义存在即可。在这种情况下,PrivateState模式已定义,因此我们可以将bar作为新的状态通道添加到图中并写入它。
Reducers
Reducers 是理解如何将节点的更新应用于State 的关键。State 中的每个键都有自己的独立 reducer 函数。如果没有显式指定 reducer 函数,则假定对该键的所有更新都应覆盖它。有几种不同类型的 reducer,从默认类型的 reducer 开始:
默认 reducer
这两个示例展示了如何使用默认 reducer:示例 A
{ foo: 1, bar: ["hi"] }。然后假设第一个 Node 返回 { foo: 2 }。这被视为对状态的更新。请注意,Node 不需要返回整个 State 模式——只需返回更新。应用此更新后,State 将变为 { foo: 2, bar: ["hi"] }。如果第二个节点返回 { bar: ["bye"] },那么 State 将变为 { foo: 2, bar: ["bye"] }。
示例 B
ReducedValue 为第二个键(bar)指定 reducer 函数。请注意,第一个键保持不变。假设图的输入是 { foo: 1, bar: ["hi"] }。然后假设第一个 Node 返回 { foo: 2 }。这被视为对状态的更新。请注意,Node 不需要返回整个 State 模式——只需返回更新。应用此更新后,State 将变为 { foo: 2, bar: ["hi"] }。如果第二个节点返回 { bar: ["bye"] },那么 State 将变为 { foo: 2, bar: ["hi", "bye"] }。请注意,这里 bar 键通过连接两个数组进行更新。
Untracked values
UntrackedValue 用于应在图执行期间存在但永远不应被检查点的状态字段。当图从检查点恢复时,未跟踪的值将重置为其初始状态(或不可用)。
这对于以下情况很有用:
- 无法序列化的数据库连接
- 恢复时应重建的临时缓存
- 您不想持久化的大型对象
- 每次都应新鲜传递的仅运行时配置
- 执行期间:值像正常状态一样存储和访问
- 检查点时:未跟踪的值被排除在检查点数据之外
- 恢复时:未跟踪的值从头开始(空或使用其默认值)
- 使用
guard: true(默认):如果多个节点在同一步骤中写入则抛出错误 - 使用
guard: false:允许多次写入,最后一个值生效
Type utilities
LangGraph 提供了几个类型实用程序,以便在定义节点和条件边时获得更好的 TypeScript 类型安全性。GraphNode
使用 GraphNode 为在图构建器外部定义的节点函数提供类型:
State.Node 简写
每个 StateSchema 实例都有一个 Node 属性,为节点提供类型简写:
ConditionalEdgeRouter
使用 ConditionalEdgeRouter 为条件边中的路由函数提供类型(无状态更新,仅路由):
StateSchema.State 和 StateSchema.Update
从模式中提取状态和更新类型,用于自定义类型定义:
在图状态中处理消息
为什么使用消息?
大多数现代 LLM 提供商都有一个聊天模型接口,该接口接受消息列表作为输入。LangChain 的聊天模型接口特别接受消息对象列表作为输入。这些消息有多种形式,例如HumanMessage(用户输入)或 AIMessage(LLM 响应)。
要了解更多关于消息对象的信息,请参阅消息概念指南。
在图中使用消息
在许多情况下,将先前的对话历史记录作为消息列表存储在图状态中很有帮助。为此,您可以使用预置的MessagesValue,它提供了一个消息感知的 reducer,可以自动处理消息 ID、更新和删除。
MessagesValue reducer 对于告诉图如何在每次状态更新时更新状态中的 Message 对象列表至关重要。如果您不指定 reducer,每次状态更新都会用最近提供的值覆盖消息列表。MessagesValue 可以正确处理此问题:对于全新的消息,它会追加到现有列表中;对于现有消息(通过 ID 匹配),它会就地更新它们。
序列化
除了跟踪消息 ID 外,MessagesValue 还会在收到 messages 通道上的状态更新时,尝试将消息反序列化为 LangChain Message 对象。这允许以以下格式发送图输入/状态更新:
MessagesValue 时状态更新总是反序列化为 LangChain Messages,因此您应该使用点表示法访问消息属性,例如 state.messages.at(-1).content。下面是一个使用 MessagesValue 的图示例:
messages 字段被定义为 MessagesValue,这是一个带有内置 reducer 的 BaseMessage 对象列表。通常,需要跟踪的状态不止消息,因此我们看到人们扩展此状态并添加更多字段,例如:
Nodes
在 LangGraph 中,节点通常是函数(同步或异步),接受以下参数:state—图的状态config—一个RunnableConfig对象,包含配置信息(如thread_id)和跟踪信息(如tags)
addNode 方法将节点添加到图中。为了更好的类型安全性,请使用 GraphNode 类型实用程序或 State.Node 为节点函数提供类型:
RunnableLambda,它为您的函数添加了批处理和异步支持,以及原生跟踪和调试。
如果您在不指定名称的情况下将节点添加到图中,它将被赋予一个默认名称,等同于函数名称。
START 节点
START 节点是一个特殊节点,表示将用户输入发送到图的节点。引用此节点的主要目的是确定应首先调用哪些节点。
END 节点
END 节点是一个特殊节点,表示终端节点。当您想表示哪些边在完成后没有操作时,会引用此节点。
Node caching
LangGraph 支持基于节点输入的任务/节点缓存。要使用缓存:- 在编译图时(或指定入口点时)指定缓存
- 为节点指定缓存策略。每个缓存策略支持:
keyFunc,用于根据节点输入生成缓存键。ttl,缓存的生存时间(以秒为单位)。如果未指定,缓存将永不过期。
Edges
边定义了逻辑如何路由以及图如何决定停止。这是您的智能体如何工作以及不同节点如何相互通信的重要组成部分。边有几种关键类型:- 普通边:直接从一个节点到下一个节点。
- 条件边:调用一个函数来确定接下来要去哪个节点。
- 入口点:用户输入到达时首先调用哪个节点。
- 条件入口点:调用一个函数来确定用户输入到达时首先调用哪个节点。
Normal edges
如果您总是想从节点 A 到节点 B,您可以直接使用addEdge 方法。
Conditional edges
如果您想可选地路由到一个或多个边(或可选地终止),您可以使用addConditionalEdges 方法。此方法接受一个节点的名称和一个在该节点执行后调用的“路由函数”:
routingFunction 接受图的当前 state 并返回一个值。
默认情况下,routingFunction 的返回值用作下一个节点(或节点列表)的名称。所有这些节点都将作为下一个超级步骤的一部分并行运行。
您可以选择提供一个对象,将 routingFunction 的输出映射到下一个节点的名称。
Entry point
入口点是图开始时运行的第一个节点。您可以使用从虚拟START 节点到要执行的第一个节点的 addEdge 方法来指定从哪里进入图。
Conditional entry point
条件入口点允许您根据自定义逻辑从不同的节点开始。您可以使用从虚拟START 节点的 addConditionalEdges 来实现这一点。
routingFunction 的输出映射到下一个节点的名称。
Send
默认情况下,Nodes 和 Edges 是预先定义的,并在相同的共享状态上操作。但是,在某些情况下,确切的边可能不是预先知道的,和/或您可能希望不同版本的 State 同时存在。一个常见的例子是 map-reduce 设计模式。在这种设计模式中,第一个节点可能生成一个对象列表,您可能希望将其他节点应用于所有这些对象。对象的数量可能是未知的(意味着边的数量可能未知),并且下游 Node 的输入 State 应该不同(每个生成的对象一个)。
为了支持这种设计模式,LangGraph 支持从条件边返回 Send 对象。Send 接受两个参数:第一个是节点的名称,第二个是要传递给该节点的状态。
Command
Command 是一个用于控制图执行的多功能原语。它接受四个参数:
Command 在三种上下文中使用:
- 从节点返回:使用
update、goto和graph将状态更新与控制流组合。 - 输入到
invoke或stream:使用resume在中断后继续执行。 - 从工具返回:类似于从节点返回,在工具内部组合状态更新和控制流。
Return from nodes
update 和 goto
从节点函数返回 Command 以在单个步骤中更新状态并路由到下一个节点:
Command,您还可以实现动态控制流行为(与条件边相同):
Command。如果您只需要路由而不需要更新状态,请改用条件边。
在节点函数中使用 Command 时,必须在添加节点时添加 ends 参数以指定它可以路由到哪些节点:
Command 的端到端示例。
graph
如果您正在使用子图,您可以通过在 Command 中指定 graph: Command.PARENT,从子图内的节点导航到父图中的不同节点:
Input to invoke or stream
resume
使用 new Command({ resume: ... }) 提供一个值并在中断后恢复图执行。传递给 resume 的值成为暂停节点内 interrupt() 调用的返回值:
Return from tools
您可以从工具返回Command 以更新图状态和控制流。使用 update 修改状态(例如,保存对话期间查找的客户信息),使用 goto 在工具完成后路由到特定节点。
有关详细信息,请参阅在工具内使用。
Graph migrations
LangGraph 可以轻松处理图定义(节点、边和状态)的迁移,即使使用检查点器来跟踪状态。- 对于图末尾的线程(即未中断),您可以更改图的整个拓扑结构(即所有节点和边,删除、添加、重命名等)
- 对于当前中断的线程,我们支持除重命名/删除节点之外的所有拓扑更改(因为该线程现在可能即将进入一个不再存在的节点)——如果这是一个阻碍,请联系我们,我们可以优先考虑解决方案。
- 对于修改状态,我们在添加和删除键方面具有完全的向后和向前兼容性
- 重命名的状态键在现有线程中会丢失其保存的状态
- 以不兼容方式更改类型的状态键目前可能会在具有更改前状态的线程中导致问题——如果这是一个阻碍,请联系我们,我们可以优先考虑解决方案。
Runtime context
创建图时,您可以为传递给节点的运行时上下文指定一个contextSchema。这对于向节点传递不属于图状态的信息很有用。例如,您可能想传递依赖项,如模型名称或数据库连接。
context 属性将此配置传递给图。
Recursion limit
递归限制设置了图在单次执行期间可以执行的超级步骤的最大数量。一旦达到限制,LangGraph 将引发GraphRecursionError。默认情况下,此值设置为 25 步。递归限制可以在运行时在任何图上设置,并通过配置对象传递给 invoke/stream。重要的是,recursionLimit 是一个独立的 config 键,不应像所有其他用户定义的配置一样传递在 configurable 键内。请参见下面的示例:
Accessing and handling the recursion counter
当前步骤计数器可在任何节点内的config.metadata.langgraph_step 中访问,允许在达到递归限制之前进行主动递归处理。这使您能够在图逻辑中实现优雅的降级策略。
How it works
步骤计数器存储在config.metadata.langgraph_step 中。递归限制检查遵循以下逻辑:step > stop,其中 stop = step + recursionLimit + 1。当超过限制时,LangGraph 会引发 GraphRecursionError。
Accessing the current step counter
您可以在任何节点内访问当前步骤计数器以监控执行进度。GraphRecursionError 作为安全网捕获:
Proactive vs reactive approaches
处理递归限制有两种主要方法:主动(在图内监控)和被动(在外部捕获错误)。GraphRecursionError。设计您的图时要有明确的终止条件,以避免首先达到限制。
被动方法的优点:
- 实现简单
- 无需修改图逻辑
- 集中错误处理
Other available metadata
除了langgraph_step 之外,config.metadata 中还提供以下元数据:
Visualization
能够可视化图通常很好,尤其是当它们变得更复杂时。LangGraph 附带了几种内置的图可视化方式。有关更多信息,请参阅可视化您的图。Observability and Tracing
要跟踪、调试和评估您的智能体,请使用 LangSmith。Learn more
将这些文档连接到 Claude、VSCode 等,通过 MCP 获取实时答案。

