多智能体系统¶
一个智能体是 一个使用 LLM 来决定应用程序控制流的系统。随着您开发这些系统,它们可能会随着时间的推移变得更加复杂,更难管理和扩展。例如,您可能会遇到以下问题
- 智能体拥有过多可用工具,并且在决定下一步调用哪个工具时做出糟糕的决策
- 上下文变得过于复杂,单个智能体无法跟踪
- 系统中需要多个专业领域(例如,规划者、研究员、数学专家等)
为了解决这些问题,您可以考虑将应用程序分解为多个更小、独立的智能体,并将它们组合成一个多智能体系统。这些独立智能体可以像一个提示和一次 LLM 调用一样简单,也可以像一个 ReAct 智能体一样复杂(等等!)。
使用多智能体系统的主要好处是
- 模块化:独立智能体使开发、测试和维护智能体系统变得更容易。
- 专业化:您可以创建专注于特定领域的专家智能体,这有助于提高整个系统的性能。
- 控制:您可以明确控制智能体如何通信(而不是依赖于函数调用)。
多智能体架构¶
在多智能体系统中连接智能体有几种方法
- 网络:每个智能体可以与所有其他智能体通信(多对多连接)。任何智能体都可以决定下一步调用哪个智能体。
- 监督者:每个智能体与一个单独的监督者智能体通信。监督者智能体决定下一步应该调用哪个智能体。
- 分层:您可以定义一个包含监督者之监督者的多智能体系统。这是监督者架构的推广,允许更复杂的控制流。
- 自定义多智能体工作流:每个智能体仅与智能体子集通信。流程的部分是确定性的,只有一些智能体可以决定下一步调用哪些其他智能体。
交接¶
在多智能体架构中,智能体可以表示为图节点。每个智能体节点执行其步骤并决定是完成执行还是路由到另一个智能体,包括可能路由到自身(例如,在循环中运行)。多智能体交互中的一个常见模式是交接,即一个智能体将控制权交给另一个智能体。交接允许您指定
- 目的地:要导航到的目标智能体(例如,要去的节点名称)
- 负载:传递给该智能体的信息(例如,状态更新)
为了在 LangGraph 中实现交接,智能体节点可以返回 Command
对象,该对象允许您结合控制流和状态更新
const agent = (state: typeof StateAnnotation.State) => {
const goto = getNextAgent(...) // 'agent' / 'another_agent'
return new Command({
// Specify which agent to call next
goto: goto,
// Update the graph state
update: {
foo: "bar",
}
});
};
在更复杂的场景中,每个智能体节点本身就是一个图(即一个子图),其中一个智能体子图中的节点可能想要导航到另一个智能体。例如,如果您有两个智能体 alice
和 bob
(父图中的子图节点),并且 alice
需要导航到 bob
,您可以在 Command
对象中设置 graph=Command.PARENT
const some_node_inside_alice = (state) => {
return new Command({
goto: "bob",
update: {
foo: "bar",
},
// specify which graph to navigate to (defaults to the current graph)
graph: Command.PARENT,
})
}
网络¶
在此架构中,智能体被定义为图节点。每个智能体可以与所有其他智能体通信(多对多连接),并可以决定下一步调用哪个智能体。此架构适用于没有清晰智能体层次结构或没有智能体调用特定顺序的问题。
import {
StateGraph,
Annotation,
MessagesAnnotation,
Command
} from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "gpt-4o-mini",
});
const agent1 = async (state: typeof MessagesAnnotation.State) => {
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
// to determine which agent to call next. a common pattern is to call the model
// with a structured output (e.g. force it to return an output with a "next_agent" field)
const response = await model.withStructuredOutput(...).invoke(...);
return new Command({
update: {
messages: [response.content],
},
goto: response.next_agent,
});
};
const agent2 = async (state: typeof MessagesAnnotation.State) => {
const response = await model.withStructuredOutput(...).invoke(...);
return new Command({
update: {
messages: [response.content],
},
goto: response.next_agent,
});
};
const agent3 = async (state: typeof MessagesAnnotation.State) => {
...
return new Command({
update: {
messages: [response.content],
},
goto: response.next_agent,
});
};
const graph = new StateGraph(MessagesAnnotation)
.addNode("agent1", agent1, {
ends: ["agent2", "agent3" "__end__"],
})
.addNode("agent2", agent2, {
ends: ["agent1", "agent3", "__end__"],
})
.addNode("agent3", agent3, {
ends: ["agent1", "agent2", "__end__"],
})
.addEdge("__start__", "agent1")
.compile();
监督者¶
在此架构中,我们将智能体定义为节点,并添加一个监督者节点(LLM),该节点决定下一步应该调用哪个智能体节点。我们使用Command
根据监督者的决定将执行路由到相应的智能体节点。此架构也非常适合并行运行多个智能体或使用map-reduce 模式。
import {
StateGraph,
MessagesAnnotation,
Command,
} from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "gpt-4o-mini",
});
const supervisor = async (state: typeof MessagesAnnotation.State) => {
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
// to determine which agent to call next. a common pattern is to call the model
// with a structured output (e.g. force it to return an output with a "next_agent" field)
const response = await model.withStructuredOutput(...).invoke(...);
// route to one of the agents or exit based on the supervisor's decision
// if the supervisor returns "__end__", the graph will finish execution
return new Command({
goto: response.next_agent,
});
};
const agent1 = async (state: typeof MessagesAnnotation.State) => {
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
// and add any additional logic (different models, custom prompts, structured output, etc.)
const response = await model.invoke(...);
return new Command({
goto: "supervisor",
update: {
messages: [response],
},
});
};
const agent2 = async (state: typeof MessagesAnnotation.State) => {
const response = await model.invoke(...);
return new Command({
goto: "supervisor",
update: {
messages: [response],
},
});
};
const graph = new StateGraph(MessagesAnnotation)
.addNode("supervisor", supervisor, {
ends: ["agent1", "agent2", "__end__"],
})
.addNode("agent1", agent1, {
ends: ["supervisor"],
})
.addNode("agent2", agent2, {
ends: ["supervisor"],
})
.addEdge("__start__", "supervisor")
.compile();
查看此教程,了解监督者多智能体架构的示例。
自定义多智能体工作流¶
在此架构中,我们将单个智能体添加为图节点,并在自定义工作流中提前定义调用智能体的顺序。在 LangGraph 中,工作流可以通过两种方式定义
-
显式控制流(普通边):LangGraph 允许您通过普通图边显式定义应用程序的控制流(即智能体通信的顺序)。这是上述架构中最具确定性的变体 — 我们总是提前知道下一步将调用哪个智能体。
-
动态控制流(条件边):在 LangGraph 中,您可以允许 LLM 决定应用程序控制流的一部分。这可以通过使用
Command
来实现。
import {
StateGraph,
MessagesAnnotation,
} from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "gpt-4o-mini",
});
const agent1 = async (state: typeof MessagesAnnotation.State) => {
const response = await model.invoke(...);
return { messages: [response] };
};
const agent2 = async (state: typeof MessagesAnnotation.State) => {
const response = await model.invoke(...);
return { messages: [response] };
};
const graph = new StateGraph(MessagesAnnotation)
.addNode("agent1", agent1)
.addNode("agent2", agent2)
// define the flow explicitly
.addEdge("__start__", "agent1")
.addEdge("agent1", "agent2")
.compile();
智能体之间的通信¶
构建多智能体系统时最重要的事情是弄清楚智能体如何通信。有几个不同的考虑因素
图状态¶
要通过图状态进行通信,需要将单个智能体定义为图节点。这些可以作为函数添加,也可以作为整个子图添加。在图执行的每个步骤中,智能体节点接收图的当前状态,执行智能体代码,然后将更新后的状态传递给下一个节点。
通常,智能体节点共享一个状态模式。但是,您可能希望设计具有不同状态模式的智能体节点。
不同的状态模式¶
一个智能体可能需要与其余智能体具有不同的状态模式。例如,搜索智能体可能只需要跟踪查询和检索到的文档。在 LangGraph 中,有两种方法可以实现这一点
- 使用单独的状态模式定义子图智能体。如果子图与父图之间没有共享状态键(通道),则重要的一步是添加输入/输出转换,以便父图知道如何与子图通信。
- 定义具有私有输入状态模式的智能体节点函数,该模式与整体图状态模式不同。这允许传递执行该特定智能体所需的唯一信息。
共享消息列表¶
智能体之间最常见的通信方式是通过共享状态通道,通常是消息列表。这假定状态中始终至少有一个由智能体共享的通道(键)。通过共享消息列表进行通信时,还有一个额外的考虑因素:智能体应该共享其思维过程的完整历史还是仅共享最终结果?
共享完整历史¶
智能体可以与其他所有智能体共享其思维过程的完整历史(即“草稿本”)。此“草稿本”通常看起来像一个消息列表。共享完整思维过程的好处在于它可能帮助其他智能体做出更好的决策,并提高整个系统的推理能力。缺点是随着智能体数量和复杂性的增加,“草稿本”会迅速增长,可能需要额外的内存管理策略。
共享最终结果¶
智能体可以拥有自己的私有“草稿本”,并且仅与其余智能体共享最终结果。此方法可能更适合拥有许多智能体或更复杂智能体的系统。在这种情况下,您需要定义具有不同状态模式的智能体
对于作为工具调用的智能体,监督者根据工具模式确定输入。此外,LangGraph 允许在运行时向单个工具传递状态,因此下属智能体如果需要可以访问父级状态。