Langgraph译文2:多智能体系统
文件路径
https://github.com/langchain-ai/langgraph
langgraph/docs/docs/concepts/multi_agent.md
多智能体系统(Multi-agent systems)
一个智能体是“使用 LLM 来决定应用控制流的系统”。随着你开发这些系统,它们可能会随时间变得更复杂,从而更难以管理和扩展。例如,你可能会遇到以下问题:
- 智能体可用的工具过多,导致对下一步应调用哪个工具的决策不佳
- 单一智能体难以跟踪过于复杂的上下文
- 系统需要多个专长领域(例如规划、研究、数学专家等)
为了解决这些问题,你可以考虑将应用拆分成多个更小、独立的智能体,并将它们组合成一个多智能体系统。这些独立智能体可以简单到一个提示词加一次 LLM 调用,也可以复杂到一个 ReAct 智能体(甚至更复杂)。
使用多智能体系统的主要好处有:
- 模块化:将智能体拆分有助于更容易地开发、测试和维护智能体系统。
- 专精化:你可以创建聚焦于特定领域的专家智能体,从而提升整体系统性能。
- 可控性:你可以显式地控制智能体如何通信(而不仅仅依赖函数调用)。
多智能体架构(Multi-agent architectures)
在多智能体系统中,有多种方式连接智能体:
- 网络(Network):每个智能体可以与其他所有智能体通信。任何智能体都可以决定下一步调用哪个智能体。
- 监督者(Supervisor):每个智能体与一个单独的监督者智能体通信。由监督者智能体决定下一步应调用哪个智能体。
- 监督者(工具调用):这是监督者架构的一种特殊形式。各个智能体可以表示为工具。在这种情况下,监督者智能体使用“可调用工具”的 LLM 来决定应调用哪个智能体工具以及传递给那些智能体的参数。
- 层级式(Hierarchical):你可以定义一个拥有监督者的监督者的多智能体系统。这是监督者架构的推广,允许更复杂的控制流。
- 自定义多智能体工作流:每个智能体仅与部分智能体通信。流程的部分是确定性的,只有少数智能体可以决定下一步调用哪个智能体。
交接(Handoffs)
在多智能体架构中,智能体可以表示为图节点。每个智能体节点会执行它的步骤,并决定是结束执行还是路由到另一个智能体,包括可能路由到自身(例如循环运行)。多智能体交互中的一个常见模式是交接,即一个智能体将控制权“移交”给另一个智能体。交接允许你指定:
- 目的地(destination):要导航到的目标智能体(例如要前往的节点名称)
- 载荷(payload):传递给该智能体的信息(例如状态更新)
要在 LangGraph 中实现交接,智能体节点可以返回 Command
对象,用于同时组合控制流与状态更新:
:::python
def agent(state) -> Command[Literal["agent", "another_agent"]]:# 路由/停止的条件可以是任何东西,例如 LLM 工具调用/结构化输出等goto = get_next_agent(...) # 'agent' / 'another_agent'return Command(# 指定下一步要调用的智能体goto=goto,# 更新图状态update={"my_state_key": "my_state_value"})
:::js
graph.addNode((state) => {// 路由/停止的条件可以是任何东西,例如 LLM 工具调用/结构化输出等const goto = getNextAgent(...); // 'agent' / 'another_agent'return new Command({// 指定下一步要调用的智能体goto,// 更新图状态update: { myStateKey: "myStateValue" }});
})
在更复杂的场景中,每个智能体节点本身也是一个图(即子图),某个智能体子图中的节点可能需要导航到不同的智能体。例如,如果你有两个智能体 alice
和 bob
(位于父图中的子图节点),并且 alice
需要导航到 bob
,你可以在 Command
对象中设置 graph=Command.PARENT
:
:::python
def some_node_inside_alice(state):return Command(goto="bob",update={"my_state_key": "my_state_value"},# 指定要导航到哪个图(默认为当前图)graph=Command.PARENT,)
在更复杂的场景中,每个智能体节点本身也是一个图(即子图),某个智能体子图中的节点可能需要导航到不同的智能体。例如,如果你有两个智能体 alice
和 bob
(位于父图中的子图节点),并且 alice
需要导航到 bob
,你可以在 Command
对象中设置 graph: Command.PARNT
:
:::js
alice.addNode((state) => {return new Command({goto: "bob",update: { myStateKey: "myStateValue" },// 指定要导航到哪个图(默认为当前图)graph: Command.PARENT,});
});
如果你需要为通过 `Command(graph=Command.PARENT)` 通信的子图提供可视化支持,你需要使用带有 `Command` 注解的节点函数对它们进行封装:
而不是这样:
:::python
builder.add_node(alice)
你需要这样做:
def call_alice(state) -> Command[Literal["bob"]]:return alice.invoke(state)builder.add_node("alice", call_alice)
如果你需要为使用 `Command({ graph: Command.PARENT })` 进行通信的子图提供可视化支持,你需要使用带有 `Command` 注解的节点函数对它们进行封装:
而不是这样:
:::js
builder.addNode("alice", alice);
你需要这样做:
builder.addNode("alice", (state) => alice.invoke(state), { ends: ["bob"] });
作为工具的交接(Handoffs as tools)
最常见的智能体类型之一是工具调用型智能体。对于这类智能体,一个常见模式是将交接封装到一次工具调用中:
:::python
from langchain_core.tools