如何使用 Command 组合控制流和状态更新¶
将控制流(边)和状态更新(节点)结合起来可能会很有用。例如,你可能希望在同一个节点中既执行状态更新,又决定接下来要前往哪个节点。LangGraph 提供了一种方法,通过从节点函数返回一个 Command
对象来实现这一点:
def my_node(state: State) -> Command[Literal["my_other_node"]]:
return Command(
# 状态更新
update={"foo": "bar"},
# 控制流
goto="my_other_node"
)
如果你正在使用子图,你可能希望从子图内的一个节点导航到另一个子图(即父图中的不同节点)。为此,你可以在 Command
中指定 graph=Command.PARENT
:
def my_node(state: State) -> Command[Literal["my_other_node"]]:
return Command(
update={"foo": "bar"},
goto="other_subgraph", # 其中 `other_subgraph` 是父图中的一个节点
graph=Command.PARENT
)
使用 Command.PARENT
进行状态更新
当你从子图节点向父图节点发送更新,且更新的键同时存在于父图和子图的状态模式中时,你**必须**为你在父图状态中更新的键定义一个归约器。请参阅下面的示例。
本指南展示了如何使用 Command
在你的 LangGraph 应用程序中添加动态控制流。
安装设置¶
首先,让我们安装所需的软件包。
为 LangGraph 开发设置 LangSmith
注册 LangSmith 以快速发现问题并提升你的 LangGraph 项目的性能。LangSmith 允许你使用跟踪数据来调试、测试和监控使用 LangGraph 构建的大语言模型应用程序 — 点击 此处 了解更多关于如何开始使用的信息。
让我们创建一个包含 3 个节点的简单图:A、B 和 C。我们将首先执行节点 A,然后根据节点 A 的输出决定接下来是前往节点 B 还是节点 C。
基本用法¶
import random
from typing_extensions import TypedDict, Literal
from langgraph.graph import StateGraph, START
from langgraph.types import Command
# Define graph state
class State(TypedDict):
foo: str
# Define the nodes
def node_a(state: State) -> Command[Literal["node_b", "node_c"]]:
print("Called A")
value = random.choice(["a", "b"])
# this is a replacement for a conditional edge function
if value == "a":
goto = "node_b"
else:
goto = "node_c"
# note how Command allows you to BOTH update the graph state AND route to the next node
return Command(
# this is the state update
update={"foo": value},
# this is a replacement for an edge
goto=goto,
)
def node_b(state: State):
print("Called B")
return {"foo": state["foo"] + "b"}
def node_c(state: State):
print("Called C")
return {"foo": state["foo"] + "c"}
现在我们可以使用上述节点创建 StateGraph
。请注意,该图没有用于路由的条件边!这是因为控制流是在 node_a
内部使用 Command
定义的。
builder = StateGraph(State)
builder.add_edge(START, "node_a")
builder.add_node(node_a)
builder.add_node(node_b)
builder.add_node(node_c)
# NOTE: there are no edges between nodes A, B and C!
graph = builder.compile()
重要
你可能已经注意到,我们使用 Command
作为返回类型注解,例如 Command[Literal["node_b", "node_c"]]
。这对于图形渲染是必要的,它会告知 LangGraph node_a
可以导航到 node_b
和 node_c
。
如果我们多次运行该图,我们会看到它根据节点 A 中的随机选择走不同的路径(A -> B 或 A -> C)。
在父图中导航到一个节点¶
现在让我们演示如何从子图内部导航到父图中的不同节点。我们将通过把上述示例中的 node_a
转换为一个单节点图来实现这一点,然后将该单节点图作为子图添加到我们的父图中。
import operator
from typing_extensions import Annotated
class State(TypedDict):
# NOTE: we define a reducer here
foo: Annotated[str, operator.add]
def node_a(state: State):
print("Called A")
value = random.choice(["a", "b"])
# this is a replacement for a conditional edge function
if value == "a":
goto = "node_b"
else:
goto = "node_c"
# note how Command allows you to BOTH update the graph state AND route to the next node
return Command(
update={"foo": value},
goto=goto,
# this tells LangGraph to navigate to node_b or node_c in the parent graph
# NOTE: this will navigate to the closest parent graph relative to the subgraph
graph=Command.PARENT,
)
subgraph = StateGraph(State).add_node(node_a).add_edge(START, "node_a").compile()
def node_b(state: State):
print("Called B")
# NOTE: since we've defined a reducer, we don't need to manually append
# new characters to existing 'foo' value. instead, reducer will append these
# automatically (via operator.add)
return {"foo": "b"}
def node_c(state: State):
print("Called C")
return {"foo": "c"}
builder = StateGraph(State)
builder.add_edge(START, "subgraph")
builder.add_node("subgraph", subgraph)
builder.add_node(node_b)
builder.add_node(node_c)
graph = builder.compile()