如何使用子图¶
子图 允许您构建具有多个组件的复杂系统,这些组件本身就是图。使用子图的一个常见用例是构建 多代理系统。
添加子图时的主要问题是父图和子图如何通信,即它们在图执行期间如何在彼此之间传递状态。有两种情况
- 父图和子图共享模式键。在这种情况下,您可以添加带有已编译子图的节点
- 父图和子图具有不同的模式。在这种情况下,您必须添加调用子图的节点函数:当父图和子图具有不同的状态模式,并且您需要在调用子图之前或之后转换状态时,这很有用
下面我们展示了如何为每种场景添加子图。
设置¶
首先,让我们安装所需的软件包
设置 LangSmith 以进行 LangGraph 开发
注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序——阅读更多关于如何开始使用的信息 这里。
添加带有已编译子图的节点¶
一种常见的情况是父图和子图通过共享状态键(通道)进行通信。例如,在 多代理 系统中,代理通常通过共享的 消息 键进行通信。
如果您的子图与父图共享状态键,您可以按照以下步骤将其添加到您的图中
- 定义子图工作流程(以下示例中的
subgraph_builder
)并编译它 - 在定义父图工作流程时,将编译后的子图传递给
.add_node
方法
让我们看一个简单的例子。
from langgraph.graph import START, StateGraph
from typing import TypedDict
# Define subgraph
class SubgraphState(TypedDict):
foo: str # note that this key is shared with the parent graph state
bar: str
def subgraph_node_1(state: SubgraphState):
return {"bar": "bar"}
def subgraph_node_2(state: SubgraphState):
# note that this node is using a state key ('bar') that is only available in the subgraph
# and is sending update on the shared state key ('foo')
return {"foo": state["foo"] + state["bar"]}
subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_node(subgraph_node_2)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
subgraph = subgraph_builder.compile()
# Define parent graph
class ParentState(TypedDict):
foo: str
def node_1(state: ParentState):
return {"foo": "hi! " + state["foo"]}
builder = StateGraph(ParentState)
builder.add_node("node_1", node_1)
# note that we're adding the compiled subgraph as a node to the parent graph
builder.add_node("node_2", subgraph)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
graph = builder.compile()
API 参考: START | StateGraph
您可以看到来自父图的最终输出包括子图调用的结果(即字符串"bar"
)。如果您想查看来自子图的输出,您可以在流式处理时指定 subgraphs=True
。有关来自子图的流式处理的更多信息,请参阅此 操作指南。
((), {'node_1': {'foo': 'hi! foo'}})
(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_1': {'bar': 'bar'}})
(('node_2:e58e5673-a661-ebb0-70d4-e298a7fc28b7',), {'subgraph_node_2': {'foo': 'hi! foobar'}})
((), {'node_2': {'foo': 'hi! foobar'}})
添加调用子图的节点函数¶
对于更复杂的系统,您可能希望定义与父图具有完全不同模式(没有共享键)的子图。例如,在多代理 RAG 系统中,搜索代理可能只需要跟踪查询和检索到的文档。
如果您的应用程序是这种情况,您需要定义一个调用子图的节点函数。此函数需要在调用子图之前将输入(父)状态转换为子图状态,并在从节点返回状态更新之前将结果转换回父状态。
下面我们展示了如何修改我们的原始示例以从节点内部调用子图。
警告
您不能在同一个节点内调用多个子图。
# Define subgraph
class SubgraphState(TypedDict):
# note that none of these keys are shared with the parent graph state
bar: str
baz: str
def subgraph_node_1(state: SubgraphState):
return {"baz": "baz"}
def subgraph_node_2(state: SubgraphState):
return {"bar": state["bar"] + state["baz"]}
subgraph_builder = StateGraph(SubgraphState)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_node(subgraph_node_2)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph_builder.add_edge("subgraph_node_1", "subgraph_node_2")
subgraph = subgraph_builder.compile()
# Define parent graph
class ParentState(TypedDict):
foo: str
def node_1(state: ParentState):
return {"foo": "hi! " + state["foo"]}
def node_2(state: ParentState):
# transform the state to the subgraph state
response = subgraph.invoke({"bar": state["foo"]})
# transform response back to the parent state
return {"foo": response["bar"]}
builder = StateGraph(ParentState)
builder.add_node("node_1", node_1)
# note that instead of using the compiled subgraph we are using `node_2` function that is calling the subgraph
builder.add_node("node_2", node_2)
builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
graph = builder.compile()
((), {'node_1': {'foo': 'hi! foo'}})
(('node_2:c47d7ea3-7798-87c4-adf4-2543a91d6891',), {'subgraph_node_1': {'baz': 'baz'}})
(('node_2:c47d7ea3-7798-87c4-adf4-2543a91d6891',), {'subgraph_node_2': {'bar': 'hi! foobaz'}})
((), {'node_2': {'foo': 'hi! foobaz'}})