图
LangGraph 的核心是将智能体工作流建模为图。你通过三个关键组件来定义智能体的行为:-
状态:一个共享数据结构,表示应用程序的当前快照。它可以是任何数据类型,但通常使用共享的状态模式来定义。 -
节点:编码智能体逻辑的函数。它们接收当前状态作为输入,执行某些计算或副作用,并返回更新后的状态。 -
边:根据当前状态决定下一步执行哪个节点的函数。它们可以是条件分支或固定转换。
节点和边,你可以创建复杂的、可循环的工作流,使状态随时间演化。但真正强大之处在于 LangGraph 如何管理该状态。
需要强调的是:节点和边不过是函数——它们可以包含大语言模型(LLM)或普通代码。
简而言之:节点执行工作,边决定接下来做什么。
LangGraph 的底层图算法使用消息传递来定义通用程序。当一个节点完成操作时,它会沿着一条或多条边向其他节点发送消息。这些接收节点随后执行其函数,将生成的消息传递给下一组节点,整个过程如此循环。受到 Google Pregel 系统的启发,程序以离散的”超级步”方式运行。
超级步可以被理解为对图节点的一次迭代。并行运行的节点属于同一个超级步,而顺序运行的节点属于不同的超级步。在图执行开始时,所有节点都处于 inactive 状态。当节点在其任何传入边(或”通道”)上接收到新消息(状态)时,它变为 active 状态。活跃节点随后运行其函数并以更新作为响应。在每个超级步结束时,没有传入消息的节点通过将自己标记为 inactive 来投票 halt。当所有节点都处于 inactive 状态且没有消息在传输中时,图执行终止。
StateGraph
StateGraph 类是主要使用的图类。它由用户定义的 State 对象参数化。
编译你的图
要构建图,你首先定义状态,然后添加节点和边,最后编译它。编译图究竟是什么,为什么需要它? 编译是一个相当简单的步骤。它对图的结构进行一些基本检查(如没有孤立节点等)。编译也是你可以指定运行时参数(如检查点器和断点)的地方。你只需调用.compile 方法即可编译图:
状态
定义图时首先要做的是定义图的状态。状态由图的模式以及指定如何将更新应用于状态的reducer函数组成。模式将作为图中所有节点和边的输入模式,可以是 TypedDict 或 Pydantic 模型。所有节点将发出对状态的更新,这些更新随后使用指定的 reducer 函数应用。
模式
指定图模式的主要文档化方式是使用TypedDict。如果你想在状态中提供默认值,请使用 dataclass。如果你需要递归数据验证,我们也支持使用 Pydantic BaseModel 作为图状态(但请注意 Pydantic 的性能不如 TypedDict 或 dataclass)。
默认情况下,图的输入和输出模式相同。如果你想更改此设置,也可以直接指定显式的输入和输出模式。当你有很多键,其中一些明确用于输入,另一些用于输出时,这很有用。更多信息请参阅指南。
langchain 中的高级 create_agent 工厂不支持 Pydantic 状态模式。多重模式
通常,所有图节点使用单一模式通信。这意味着它们将读取和写入相同的状态通道。但在某些情况下,我们希望对此进行更多控制:- 内部节点可以传递图输入/输出中不需要的信息。
- 我们可能还希望为图使用不同的输入/输出模式。例如,输出可能只包含一个相关的输出键。
PrivateState。
也可以为图定义显式的输入和输出模式。在这种情况下,我们定义一个包含与图操作相关的_所有_键的”内部”模式。但我们也定义 input 和 output 模式作为”内部”模式的子集,以约束图的输入和输出。更多详情请参阅定义输入和输出模式。
让我们看一个示例:
-
我们将
state: InputState作为输入模式传递给node_1。但我们写入了foo,这是OverallState中的一个通道。我们如何能写入一个不包含在输入模式中的状态通道?这是因为节点_可以写入图状态中的任何状态通道_。图状态是在初始化时定义的状态通道的联合,包括OverallState以及过滤器InputState和OutputState。 -
我们用以下方式初始化图:
我们如何能在
node_2中写入PrivateState?如果它没有在StateGraph初始化中传入,图如何访问这个模式? 我们可以这样做,因为节点也可以声明额外的状态通道,只要状态模式定义存在。在这种情况下,PrivateState模式已定义,因此我们可以将bar添加为图中的新状态通道并写入它。
Reducer
Reducer 是理解节点更新如何应用于状态的关键。状态中的每个键都有其独立的 reducer 函数。如果没有显式指定 reducer 函数,则假定对该键的所有更新都应覆盖它。从默认类型的 reducer 开始,有几种不同类型的 reducer:
默认 reducer
以下两个示例展示了如何使用默认 reducer:Example A
{"foo": 1, "bar": ["hi"]}。然后假设第一个节点返回 {"foo": 2}。这被视为对状态的更新。请注意,节点不需要返回完整的状态模式——只需返回更新即可。应用此更新后,状态将变为 {"foo": 2, "bar": ["hi"]}。如果第二个节点返回 {"bar": ["bye"]},则状态将变为 {"foo": 2, "bar": ["bye"]}。
Example B
Annotated 类型为第二个键(bar)指定了 reducer 函数(operator.add)。请注意,第一个键保持不变。假设图的输入为 {"foo": 1, "bar": ["hi"]}。然后假设第一个节点返回 {"foo": 2}。这被视为对状态的更新。请注意,节点不需要返回完整的状态模式——只需返回更新即可。应用此更新后,状态将变为 {"foo": 2, "bar": ["hi"]}。如果第二个节点返回 {"bar": ["bye"]},则状态将变为 {"foo": 2, "bar": ["hi", "bye"]}。请注意,这里 bar 键是通过将两个列表合并来更新的。
Overwrite
在图状态中使用消息
为什么使用消息?
大多数现代 LLM 提供商都有一个接受消息列表作为输入的聊天模型接口。LangChain 的聊天模型接口特别接受消息对象列表作为输入。这些消息有多种形式,如HumanMessage(用户输入)或 AIMessage(LLM 响应)。
要了解更多关于消息对象的信息,请参阅消息概念指南。
在图中使用消息
在许多情况下,将先前的对话历史作为消息列表存储在图状态中很有帮助。为此,我们可以向图状态添加一个存储Message 对象列表的键(通道),并用 reducer 函数对其进行注解(参见下面示例中的 messages 键)。reducer 函数对于告诉图如何在每次状态更新时更新状态中的 Message 对象列表至关重要(例如,当节点发送更新时)。如果你不指定 reducer,每次状态更新都会用最新提供的值覆盖消息列表。如果你只想将消息追加到现有列表中,可以使用 operator.add 作为 reducer。
然而,你可能还想手动更新图状态中的消息(例如人机协作)。如果你使用 operator.add,你发送给图的手动状态更新将被追加到现有消息列表中,而不是更新现有消息。为避免这种情况,你需要一个能跟踪消息 ID 并正确覆盖现有消息的 reducer。为此,你可以使用预构建的 add_messages 函数。对于全新的消息,它会简单地追加到现有列表中,但也会正确处理对现有消息的更新。
序列化
除了跟踪消息 ID 之外,add_messages 函数还会在 messages 通道上收到状态更新时,尝试将消息反序列化为 LangChain Message 对象。
更多信息请参阅 LangChain 序列化/反序列化。这允许以以下格式发送图输入/状态更新:
add_messages 时状态更新总是被反序列化为 LangChain Messages,你应该使用点号语法访问消息属性,如 state["messages"][-1].content。
下面是一个使用 add_messages 作为 reducer 函数的图示例。
MessagesState
由于在状态中包含消息列表非常常见,因此存在一个预构建的状态MessagesState,使使用消息变得简单。MessagesState 定义了一个 messages 键,它是 AnyMessage 对象的列表,并使用 add_messages reducer。通常,除了消息之外还需要跟踪更多状态,因此我们经常看到人们继承此状态并添加更多字段,如:
节点
在 LangGraph 中,节点是 Python 函数(同步或异步),接受以下参数:state——图的状态config——一个RunnableConfig对象,包含配置信息(如thread_id)和追踪信息(如tags)runtime——一个Runtime对象,包含运行时context以及其他信息,如store、stream_writer、execution_info、server_info、heartbeat(用于空闲超时刷新)和control(用于优雅关闭)
add_node 方法将这些节点添加到图中:
RunnableLambda,它为你的函数添加了批处理和异步支持,以及原生追踪和调试。
如果你在添加节点到图时没有指定名称,它将被赋予一个等同于函数名的默认名称。
START 节点
START 节点是一个特殊节点,表示向图发送用户输入的节点。引用此节点的主要目的是确定应首先调用哪些节点。
END 节点
END 节点是一个特殊节点,表示终端节点。当你想表示哪些边在完成后没有后续操作时,引用此节点。
节点缓存
LangGraph 支持基于节点输入的任务/节点缓存。要使用缓存:- 在编译图(或指定入口点)时指定缓存
- 为节点指定缓存策略。每个缓存策略支持:
key_func:用于基于节点输入生成缓存键,默认使用 pickle 的输入hash。ttl:缓存的存活时间(秒)。如果未指定,缓存将永不过期。
- 第一次运行需要两秒(由于模拟的耗时计算)。
- 第二次运行利用缓存并快速返回。
边
边定义了逻辑如何路由以及图如何决定停止。这是智能体工作方式以及不同节点之间通信的重要部分。有几种关键类型的边:- 普通边:直接从一个节点转到下一个节点。
- 条件边:调用函数来确定接下来转到哪个节点。
- 入口点:当用户输入到达时首先调用哪个节点。
- 条件入口点:调用函数来确定当用户输入到达时首先调用哪个节点。
普通边
如果你总是想从节点 A 转到节点 B,可以直接使用add_edge 方法。
条件边
如果你想有条件地路由到一个或多个边(或有条件地终止),可以使用add_conditional_edges 方法。此方法接受节点名称和在该节点执行后调用的”路由函数”:
routing_function 接受图的当前状态并返回一个值。
默认情况下,routing_function 的返回值用作要将状态发送到的节点(或节点列表)的名称。所有这些节点将作为下一个超级步的一部分并行运行。
你可以选择性地提供一个字典,将 routing_function 的输出映射到下一个节点的名称。
入口点
入口点是图启动时运行的第一个节点。你可以使用add_edge 方法从虚拟 START 节点到第一个要执行的节点来指定图的入口。
条件入口点
条件入口点允许你根据自定义逻辑从不同的节点开始。你可以使用从虚拟START 节点出发的 add_conditional_edges 来实现。
routing_function 的输出映射到下一个节点的名称。
Send
默认情况下,节点和边是预先定义的,并在相同的共享状态上运行。然而,在某些情况下,确切的边可能事先未知,和/或你可能希望不同版本的状态同时存在。一个常见的例子是 map-reduce 设计模式。在此设计模式中,第一个节点可能生成一个对象列表,你可能希望将某个其他节点应用于所有这些对象。对象的数量可能事先未知(意味着边的数量可能未知),并且下游节点的输入状态应该不同(每个生成的对象一个)。
为了支持此设计模式,LangGraph 支持从条件边返回 Send 对象。Send 接受两个参数:第一个是节点名称,第二个是要传递给该节点的状态。
Command
Command 是一个用于控制图执行的多功能原语。它接受四个参数:
Command 在三种上下文中使用:
- 从节点返回:使用
update、goto和graph将状态更新与控制流结合。 - 作为
invoke或stream的输入:使用resume在中断后继续执行。 - 从工具返回:类似于从节点返回,从工具内部组合状态更新和控制流。
从节点返回
update 和 goto
从节点函数返回 Command 以在单步中更新状态并路由到下一个节点:
Command 你还可以实现动态控制流行为(与条件边相同):
Command。如果你只需要路由而不更新状态,请改用条件边。
在节点函数中返回
Command 时,你必须添加包含节点可路由到的节点名称列表的返回类型注解,例如 Command[Literal["my_other_node"]]。这对于图渲染是必要的,并告诉 LangGraph my_node 可以导航到 my_other_node。Command 的端到端示例。
graph
如果你使用子图,你可以通过在 Command 中指定 graph=Command.PARENT 来从子图中的节点导航到父图中的不同节点:
这在实现多智能体交接时特别有用。查看导航到父图中的节点了解详情。
作为 invoke 或 stream 的输入
resume
使用 Command(resume=...) 提供一个值并在中断后恢复图执行。传递给 resume 的值成为暂停节点内 interrupt() 调用的返回值:
从工具返回
你可以从工具返回Command 以更新图状态和控制流。使用 update 修改状态(例如,保存在对话中查找到的客户信息),使用 goto 在工具完成后路由到特定节点。
详情请参阅在工具内部使用。
图迁移
LangGraph 可以轻松处理图定义(节点、边和状态)的迁移,即使在使用检查点器跟踪状态时也是如此。- 对于处于图末端的线程(即未中断),你可以更改图的整个拓扑结构(即所有节点和边、移除、添加、重命名等)
- 对于当前被中断的线程,我们支持除重命名/移除节点之外的所有拓扑更改(因为该线程可能即将进入一个不再存在的节点)——如果这是一个阻碍因素,请联系我们,我们可以优先解决。
- 对于修改状态,我们对添加和删除键具有完全的向后和向前兼容性
- 被重命名的状态键会丢失现有线程中的保存状态
- 以不兼容方式更改类型的状态键目前可能在包含更改前状态的线程中导致问题——如果这是一个阻碍因素,请联系我们,我们可以优先解决。
运行时上下文
创建图时,你可以指定一个context_schema 用于传递给节点的运行时上下文。这对于向节点传递不属于图状态的信息很有用。例如,你可能想传递依赖项,如模型名称或数据库连接。
invoke 方法的 context 参数将此上下文传递给图。
递归限制
递归限制设置了图在单次执行期间可以执行的最大超级步数。一旦达到限制,LangGraph 将引发GraphRecursionError。从 1.0.6 版本开始,默认递归限制设置为 1000 步。递归限制可以在运行时对任何图设置,并通过 config 字典传递给 invoke/stream。重要的是,recursion_limit 是一个独立的 config 键,不应在 configurable 键内传递,因为所有其他用户定义的配置都在那里。参见下面的示例:
访问和处理递归计数器
当前步骤计数器可在任何节点中通过config["metadata"]["langgraph_step"] 访问,允许在达到递归限制之前进行主动的递归处理。这使你能够在图逻辑中实现优雅降级策略。
工作原理
步骤计数器存储在config["metadata"]["langgraph_step"] 中。递归限制检查遵循以下逻辑:step > stop,其中 stop = step + recursion_limit + 1。当超过限制时,LangGraph 引发 GraphRecursionError。
访问当前步骤计数器
你可以在任何节点中访问当前步骤计数器以监控执行进度。主动递归处理
LangGraph 提供了一个RemainingSteps 管理值,用于跟踪在达到递归限制之前还剩多少步。这允许在图中进行优雅降级。
主动与被动方法
处理递归限制有两种主要方法:主动(在图内监控)和被动(在外部捕获错误)。
主动方法的优势:
- 在图内优雅降级
- 可以在检查点中保存中间状态
- 通过部分结果提供更好的用户体验
- 图正常完成(无异常)
- 实现更简单
- 无需修改图逻辑
- 集中式错误处理
其他可用元数据
除了langgraph_step 之外,以下元数据也可在 config["metadata"] 中获取:
可视化
能够可视化图通常很有帮助,特别是当图变得更复杂时。LangGraph 提供了几种内置的图可视化方式。更多信息请参阅可视化你的图。可观测性与追踪
要追踪、调试和评估你的智能体,请使用 LangSmith。了解更多
通过 MCP 连接这些文档到 Claude、VSCode 等工具,获取实时答案。

