图
其核心在于,LangGraph 将智能体工作流建模为图。您使用三个关键组件来定义智能体的行为:-
State:表示应用程序当前快照的共享数据结构。它可以是任何数据类型,但通常使用共享状态模式定义。 -
Nodes:编码智能体逻辑的函数。它们接收当前状态作为输入,执行某些计算或副作用,并返回更新后的状态。 -
Edges:根据当前状态确定下一个要执行的Node的函数。它们可以是条件分支或固定转换。
Nodes 和 Edges,您可以创建复杂的、循环的工作流,使状态随时间演变。然而,真正的力量来自于 LangGraph 如何管理该状态。
强调一下:Nodes 和 Edges 仅仅是函数——它们可以包含 LLM 或只是普通的代码。
简而言之:节点做工作,边告诉下一步做什么。
LangGraph 的底层图算法使用 消息传递 来定义通用程序。当一个 Node 完成其操作时,它沿着一条或多条边向其他节点发送消息。这些接收节点随后执行它们的函数,将结果消息传递给下一组节点,过程继续。受 Google 的 Pregel 系统启发,程序以离散的“超级步骤”进行。
一个超级步骤可以被视为对图节点的一次单轮迭代。并行运行的节点属于同一个超级步骤,而顺序运行的节点属于不同的超级步骤。在图执行开始时,所有节点都处于 inactive 状态。当节点在其任何传入边(或“通道”)上收到新消息(状态)时,该节点变为 active。活动节点随后运行其函数并响应更新。在每个超级步骤结束时,没有传入消息的节点通过将自己标记为 inactive 来投票 halt。当所有节点都 inactive 且没有消息在传输中时,图执行终止。
StateGraph
StateGraph 类是要使用的主要图类。这是由用户定义的 State 对象参数化的。
编译您的图
要构建您的图,首先定义 state,然后添加 nodes 和 edges,最后编译它。究竟什么是编译您的图以及为什么需要它? 编译是一个相当简单的步骤。它对图的结构提供一些基本检查(例如没有孤立的节点等)。这也是您可以指定运行时参数的地方,如 checkpointers 和 breakpoints。您只需调用.compile 方法即可编译您的图:
状态
定义图时做的第一件事就是定义图的State。State 包括 图的模式 以及 reducer 函数,后者指定如何将更新应用到状态。State 的模式将是图中所有 Nodes 和 Edges 的输入模式,可以是 TypedDict 或 Pydantic 模型。所有 Nodes 都会向 State 发出更新,然后使用指定的 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初始化中传递,图如何获得对该模式的访问权限? 我们可以这样做是因为_nodes也可以声明额外的状态channels_,只要存在状态模式定义。在这种情况下,定义了PrivateState模式,因此我们可以将bar作为图中的新状态通道并写入它。
归约器
归约器对于理解如何将从节点发出的更新应用到State 至关重要。State 中的每个键都有自己独立的归约器函数。如果没有明确指定归约器函数,则假定对该键的所有更新都应覆盖它。有几种不同类型的归约器,从默认类型的归约器开始:
默认归约器
这两个示例展示了如何使用默认归约器:Example A
{"foo": 1, "bar": ["hi"]}。然后假设第一个 Node 返回 {"foo": 2}。这被视为对状态的更新。注意 Node 不需要返回整个 State 模式 - 只需要一个更新。应用此更新后,State 将变为 {"foo": 2, "bar": ["hi"]}。如果第二个节点返回 {"bar": ["bye"]},则 State 将变为 {"foo": 2, "bar": ["bye"]}
Example B
Annotated 类型指定了第二个键 (bar) 的归约器函数 (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 键是通过将两个列表相加来更新的。
覆盖
在图状态中处理消息
为什么要使用消息?
大多数现代 LLM 提供商都具有聊天模型接口,接受消息列表作为输入。特别是 LangChain 的 聊天模型接口 接受消息对象列表作为输入。这些消息有多种形式,如HumanMessage(用户输入)或 AIMessage(LLM 响应)。
有关消息对象的更多信息,请参阅 消息概念指南。
在图中使用消息
在许多情况下,将先前的对话历史作为消息列表存储在图状态中很有帮助。为此,我们可以向图状态添加一个键(通道)来存储Message 对象列表,并使用归约器函数对其进行注释(见下面示例中的 messages 键)。归约器函数对于告诉图如何在每次状态更新时(例如当节点发送更新时)更新状态中的 Message 对象列表至关重要。如果您不指定归约器,每次状态更新都将用最近提供的值覆盖消息列表。如果您只想将消息附加到现有列表,可以使用 operator.add 作为归约器。
但是,您可能还希望在图状态中手动更新消息(例如人工干预)。如果您使用 operator.add,您发送给图的手动状态更新将被附加到现有的消息列表中,而不是更新现有消息。为了避免这种情况,您需要一个能够跟踪消息 ID 并在更新时覆盖现有消息的归约器。为了实现这一点,您可以使用预建的 add_messages 函数。对于全新的消息,它将简单地附加到现有列表,但它也会正确处理现有消息的更新。
序列化
除了跟踪消息 ID 外,add_messages 函数还会尝试在接收到 messages 通道上的状态更新时将消息反序列化为 LangChain Message 对象。
有关更多信息,请参阅 LangChain 序列化/反序列化。这允许以下格式发送图输入/状态更新:
add_messages 时状态更新始终反序列化为 LangChain Messages,您应该使用点符号访问消息属性,如 state["messages"][-1].content。
下面是使用 add_messages 作为其归约器函数的图的示例。
MessagesState
由于在状态中拥有消息列表非常常见,因此存在一个预建的状态MessagesState,使其易于使用消息。MessagesState 定义了一个单一的 messages 键,它是 AnyMessage 对象列表,并使用 add_messages 归约器。通常,需要跟踪的状态不仅仅是消息,所以我们看到人们继承此状态并添加更多字段,例如:
节点
在 LangGraph 中,节点是 Python 函数(同步或异步),接受以下参数:state—图的 状态config—包含配置信息(如thread_id)和追踪信息(如tags)的RunnableConfig对象runtime—包含 运行时context和其他信息(如store、stream_writer和execution_info)的Runtime对象
NetworkX,您使用 add_node 方法将这些节点添加到图中:
RunnableLambda,为您的函数添加批量和异步支持,以及 原生追踪和调试。
如果您在不指定名称的情况下将节点添加到图中,它将获得等同于函数名的默认名称。
START 节点
START 节点是一个特殊节点,代表向图发送用户输入的节点。引用此节点的主要目的是确定哪些节点应该首先被调用。
END 节点
END 节点是一个特殊节点,代表终止节点。当您想要表示哪些边在完成之后没有操作时,会引用此节点。
节点缓存
LangGraph 支持基于节点输入的任务/节点缓存。要使用缓存:- 在编译图(或指定入口点)时指定缓存
- 为节点指定缓存策略。每个缓存策略支持:
key_func用于基于节点输入生成缓存键,默认为输入的hash加 pickle。ttl,缓存的生存时间(秒)。如果未指定,缓存将永不过期。
- 第一次运行需要两秒钟(由于模拟的昂贵计算)。
- 第二次运行利用缓存并快速返回。
边
边定义了逻辑如何路由以及图如何决定停止。这是您的智能体工作方式以及不同节点相互通信的重要部分。有几种关键的边类型:- 普通边:直接从一节点到下一节点。
- 条件边:调用函数以确定接下来要前往哪个节点。
- 入口点:用户输入到达时首先调用的节点。
- 条件入口点:调用函数以确定用户输入到达时首先调用哪些节点。
普通边
如果您总是想从节点 A 到节点 B,可以直接使用add_edge 方法。
条件边
如果您想可选地路由到一个或多个边(或可选地终止),可以使用add_conditional_edges 方法。此方法接受一个节点名称和一个在该节点执行后要调用的“路由函数”:
routing_function 接受图的当前 state 并返回值。
默认情况下,routing_function 的返回值用作要将状态发送到下一个的节点名称(或节点列表)。所有这些节点将在下一个超级步骤中作为一部分并行运行。
您可以选择提供一个字典,将 routing_function 的输出映射到下一个节点的名称。
入口点
入口点是图启动时运行的第一个节点。您可以使用来自虚拟START 节点到要执行的第一节点的 add_edge 方法来指定进入图的位置。
条件入口点
条件入口点允许您根据自定义逻辑从不同的节点开始。您可以使用来自虚拟START 节点的 add_conditional_edges 来完成此操作。
routing_function 的输出映射到下一个节点的名称。
Send
默认情况下,Nodes 和 Edges 是预先定义的,并在相同的共享状态上运行。但是,可能存在确切边在事前未知的情况,和/或您可能希望同时存在不同版本的 State。一个常见的例子是 map-reduce 设计模式。在这种设计模式中,第一个节点可能会生成对象列表,您可能希望对所有这些对象应用其他节点。对象的数量可能在事前未知(意味着边的数量可能未知),并且下游 Node 的输入 State 应该不同(每个生成的对象一个)。
为了支持这种设计模式,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 步。递归限制可以在运行时设置在任何图上,并通过配置字典传递给 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。了解更多
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

