如何构建多智能体网络¶
在本操作指南中,我们将演示如何实现多智能体网络架构。
每个智能体都可以表示为图中的一个节点,该节点执行智能体步骤并决定下一步做什么 - 完成执行或路由到另一个智能体(包括路由到自身,例如在循环中运行)。多智能体架构中路由的常见模式是移交。移交允许您指定
- 接下来导航到哪个智能体以及(例如,要转到的节点的名称)
- 要传递给该智能体的信息(例如,状态更新)
为了实现移交,智能体节点可以返回 Command
对象,该对象允许您组合控制流和状态更新
const agent = async (state) => {
// the condition for routing/halting can be anything
// e.g. LLM tool call / structured output, etc.
const goto = getNextAgent(...); // "agent" / "another_agent"
if (goto) {
return new Command({
goto,
update: {
myStateKey: "my_state_value",
}
});
}
...
}
设置¶
首先,让我们安装所需的软件包
设置 LangSmith 用于 LangGraph 开发
注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。 LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序 — 在此处阅读有关如何入门的更多信息。
旅行推荐示例¶
在本示例中,我们将构建一个旅行助理智能体团队,他们可以通过移交相互通信。
我们将创建 3 个智能体
travel_advisor
:可以帮助提供一般旅行目的地建议。可以向sightseeing_advisor
和hotel_advisor
寻求帮助。sightseeing_advisor
:可以帮助提供观光建议。可以向travel_advisor
和hotel_advisor
寻求帮助。hotel_advisor
:可以帮助提供酒店建议。可以向sightseeing_advisor
和hotel_advisor
寻求帮助。
这是一个完全连接的网络 - 每个智能体都可以与任何其他智能体对话。
为了实现智能体之间的移交,我们将使用具有结构化输出的 LLM。每个智能体的 LLM 将返回一个输出,其中包含其文本响应 (response
) 以及要路由到的下一个智能体 (goto
)。如果智能体有足够的信息来响应用户,则 goto
将包含 finish
。
现在,让我们定义我们的智能体节点和图!
import { ChatOpenAI } from "@langchain/openai";
import {
Command,
MessagesAnnotation,
StateGraph
} from "@langchain/langgraph";
import { z } from "zod";
const model = new ChatOpenAI({
model: "gpt-4o",
temperature: 0.1,
});
const makeAgentNode = (params: {
name: string,
destinations: string[],
systemPrompt: string
}) => {
return async (state: typeof MessagesAnnotation.State) => {
const possibleDestinations = ["__end__", ...params.destinations] as const;
// define schema for the structured output:
// - model's text response (`response`)
// - name of the node to go to next (or '__end__')
const responseSchema = z.object({
response: z.string().describe(
"A human readable response to the original question. Does not need to be a final response. Will be streamed back to the user."
),
goto: z.enum(possibleDestinations).describe("The next agent to call, or __end__ if the user's query has been resolved. Must be one of the specified values."),
});
const messages = [
{
role: "system",
content: params.systemPrompt
},
...state.messages,
];
const response = await model.withStructuredOutput(responseSchema, {
name: "router",
}).invoke(messages);
// handoff to another agent or halt
const aiMessage = {
role: "assistant",
content: response.response,
name: params.name,
};
return new Command({
goto: response.goto,
update: { messages: aiMessage }
});
}
};
const travelAdvisor = makeAgentNode({
name: "travel_advisor",
destinations: ["sightseeing_advisor", "hotel_advisor"],
systemPrompt: [
"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). ",
"If you need specific sightseeing recommendations, ask 'sightseeing_advisor' for help. ",
"If you need hotel recommendations, ask 'hotel_advisor' for help. ",
"If you have enough information to respond to the user, return '__end__'. ",
"Never mention other agents by name."
].join(""),
});
const sightseeingAdvisor = makeAgentNode({
name: "sightseeing_advisor",
destinations: ["travel_advisor", "hotel_advisor"],
systemPrompt: [
"You are a travel expert that can provide specific sightseeing recommendations for a given destination. ",
"If you need general travel help, go to 'travel_advisor' for help. ",
"If you need hotel recommendations, go to 'hotel_advisor' for help. ",
"If you have enough information to respond to the user, return 'finish'. ",
"Never mention other agents by name."
].join(""),
});
const hotelAdvisor = makeAgentNode({
name: "hotel_advisor",
destinations: ["travel_advisor", "sightseeing_advisor"],
systemPrompt: [
"You are a booking expert that provides hotel recommendations for a given destination. ",
"If you need general travel help, ask 'travel_advisor' for help. ",
"If you need specific sightseeing recommendations, ask 'sightseeing_advisor' for help. ",
"If you have enough information to respond to the user, return 'finish'. ",
"Never mention other agents by name.",
].join(""),
});
const graph = new StateGraph(MessagesAnnotation)
.addNode("travel_advisor", travelAdvisor, {
ends: ["sightseeing_advisor", "hotel_advisor", "__end__"],
})
.addNode("sightseeing_advisor", sightseeingAdvisor, {
ends: ["travel_advisor", "hotel_advisor", "__end__"],
})
.addNode("hotel_advisor", hotelAdvisor, {
ends: ["travel_advisor", "sightseeing_advisor", "__end__"],
})
// we'll always start with a general travel advisor
.addEdge("__start__", "travel_advisor")
.compile();
import * as tslab from "tslab";
const drawableGraph = await graph.getGraphAsync();
const image = await drawableGraph.drawMermaidPng();
const arrayBuffer = await image.arrayBuffer();
await tslab.display.png(new Uint8Array(arrayBuffer));
首先,让我们使用通用输入调用它
const simpleStream = await graph.stream({
messages: [{
role: "user",
content: "i wanna go somewhere warm in the caribbean",
}],
});
for await (const chunk of simpleStream) {
console.log(chunk);
}
{
travel_advisor: {
messages: {
role: 'assistant',
content: 'The Caribbean is a fantastic choice for warm weather and beautiful beaches. Some popular destinations include Jamaica, the Bahamas, the Dominican Republic, and Barbados. Each offers unique experiences, from vibrant culture and music to stunning natural landscapes.',
name: 'travel_advisor'
}
}
}
travel_advisor
) 运行了。现在让我们要求更多建议
const recommendationStream = await graph.stream({
messages: [{
role: "user",
content: "i wanna go somewhere warm in the caribbean. pick one destination, give me some things to do and hotel recommendations",
}],
});
for await (const chunk of recommendationStream) {
console.log(chunk);
}
{
travel_advisor: {
messages: {
role: 'assistant',
content: 'I recommend visiting Aruba, a beautiful Caribbean island known for its stunning beaches and warm climate.',
name: 'travel_advisor'
}
}
}
{
sightseeing_advisor: {
messages: {
role: 'assistant',
content: 'Aruba is a fantastic choice for a warm Caribbean getaway. Here are some activities you can enjoy:\n' +
'\n' +
"1. **Eagle Beach**: Relax on one of the world's most beautiful beaches, known for its pristine white sand and clear turquoise waters.\n" +
'\n' +
'2. **Arikok National Park**: Explore this national park that covers nearly 20% of the island, offering hiking trails, caves, and unique wildlife.\n' +
'\n' +
'3. **Palm Beach**: Enjoy water sports, beach bars, and vibrant nightlife along this bustling beach area.\n' +
'\n' +
'4. **Oranjestad**: Visit the colorful capital city for shopping, dining, and exploring local culture and history.\n' +
'\n' +
'5. **Snorkeling and Diving**: Discover the vibrant marine life and coral reefs around the island.\n' +
'\n' +
'6. **California Lighthouse**: Take a trip to this iconic lighthouse for panoramic views of the island.\n' +
'\n' +
"Now, let's find some hotel recommendations for your stay in Aruba.",
name: 'sightseeing_advisor'
}
}
}
{
hotel_advisor: {
messages: {
role: 'assistant',
content: 'For your stay in Aruba, here are some hotel recommendations:\n' +
'\n' +
'1. **Renaissance Aruba Resort & Casino**: Located in Oranjestad, this resort offers a private island, luxurious accommodations, and a casino.\n' +
'\n' +
'2. **Hilton Aruba Caribbean Resort & Casino**: Situated on Palm Beach, this resort features beautiful gardens, a spa, and multiple dining options.\n' +
'\n' +
'3. **The Ritz-Carlton, Aruba**: A luxury beachfront resort offering elegant rooms, a spa, and fine dining.\n' +
'\n' +
'4. **Divi Aruba All Inclusive**: Enjoy an all-inclusive experience with multiple restaurants, pools, and activities right on Druif Beach.\n' +
'\n' +
'5. **Bucuti & Tara Beach Resort**: An adults-only resort on Eagle Beach, known for its romantic setting and eco-friendly practices.\n' +
'\n' +
'These options provide a range of experiences from luxury to all-inclusive, ensuring a comfortable and enjoyable stay in Aruba.',
name: 'hotel_advisor'
}
}
}
travel_advisor
决定首先从 sightseeing_advisor
获取一些观光建议,然后 sightseeing_advisor
反过来调用 hotel_advisor
以获取更多信息。请注意,我们从未明确定义智能体应执行的顺序!
游戏 NPC 示例¶
在本示例中,我们将创建一个 非玩家角色 (NPC) 团队,他们都同时运行并共享游戏状态(资源)。在每个步骤中,每个 NPC 都将检查状态并决定是停止还是在下一步继续行动。如果继续,它将更新共享的游戏状态(生产或消耗资源)。
我们将创建 4 个 NPC 智能体
villager
:生产木材和食物直到足够,然后停止guard
:保护黄金并消耗食物。当食物不足时,离开岗位并停止merchant
:用木材换取黄金。当木材不足时,停止thief
:检查警卫是否在岗,并在警卫离开时偷走所有黄金,然后停止
我们的 NPC 智能体将是简单的节点函数(villager
、guard
等)。在图执行的每个步骤中,智能体函数将检查状态中的资源值,并决定它应该停止还是继续。如果它决定继续,它将更新状态中的资源值并循环回到自身以在下一步运行。
现在,让我们定义我们的智能体节点和图!
import { Command, StateGraph, Annotation } from "@langchain/langgraph";
const GameStateAnnotation = Annotation.Root({
// note that we're defining a reducer (operator.add) here.
// This will allow all agents to write their updates for resources concurrently.
wood: Annotation<number>({
default: () => 0,
reducer: (a, b) => a + b,
}),
food: Annotation<number>({
default: () => 0,
reducer: (a, b) => a + b,
}),
gold: Annotation<number>({
default: () => 0,
reducer: (a, b) => a + b,
}),
guardOnDuty: Annotation<boolean>,
});
/** Villager NPC that gathers wood and food. */
const villager = async (state: typeof GameStateAnnotation.State) => {
const currentResources = state.wood + state.food;
// Continue gathering until we have enough resources
if (currentResources < 15) {
console.log("Villager gathering resources.");
return new Command({
goto: "villager",
update: {
wood: 3,
food: 1,
},
});
}
// NOTE: Returning Command({goto: "__end__"}) is not necessary for the graph to run correctly
// but it's useful for visualization, to show that the agent actually halts
return new Command({
goto: "__end__",
});
}
/** Guard NPC that protects gold and consumes food. */
const guard = async (state: typeof GameStateAnnotation.State) => {
if (!state.guardOnDuty) {
return new Command({
goto: "__end__",
});
}
// Guard needs food to keep patrolling
if (state.food > 0) {
console.log("Guard patrolling.");
// Loop back to the 'guard' agent
return new Command({
goto: "guard",
update: { food: -1 },
});
}
console.log("Guard leaving to get food.");
return new Command({
goto: "__end__",
update: {
guardOnDuty: false,
},
});
};
/** Merchant NPC that trades wood for gold. */
const merchant = async (state: typeof GameStateAnnotation.State) => {
// Trade wood for gold when available
if (state.wood >= 5) {
console.log("Merchant trading wood for gold.");
return new Command({
goto: "merchant",
update: {
wood: -5,
gold: 1
}
});
}
return new Command({
goto: "__end__"
});
};
/** Thief NPC that steals gold if the guard leaves to get food. */
const thief = async (state: typeof GameStateAnnotation.State) => {
if (!state.guardOnDuty) {
console.log("Thief stealing gold.");
return new Command({
goto: "__end__",
update: { gold: -state.gold }
});
}
// keep thief on standby (loop back to the 'thief' agent)
return new Command({
goto: "thief"
});
};
const gameGraph = new StateGraph(GameStateAnnotation)
.addNode("villager", villager, {
ends: ["villager", "__end__"],
})
.addNode("guard", guard, {
ends: ["guard", "__end__"],
})
.addNode("merchant", merchant, {
ends: ["merchant", "__end__"],
})
.addNode("thief", thief, {
ends: ["thief", "__end__"],
})
.addEdge("__start__", "villager")
.addEdge("__start__", "guard")
.addEdge("__start__", "merchant")
.addEdge("__start__", "thief")
.compile();
import * as tslab from "tslab";
const drawableGameGraph = await gameGraph.getGraphAsync();
const gameImage = await drawableGameGraph.drawMermaidPng();
const gameArrayBuffer = await gameImage.arrayBuffer();
await tslab.display.png(new Uint8Array(gameArrayBuffer));
让我们使用一些初始状态运行它!
const gameStream = await gameGraph.stream({
wood: 10,
food: 3,
gold: 10,
guardOnDuty: true,
}, {
streamMode: "values",
});
for await (const state of gameStream) {
console.log("Game state", state);
console.log("-".repeat(50));
}
Game state { wood: 10, food: 3, gold: 10, guardOnDuty: true }
--------------------------------------------------
Villager gathering resources.
Guard patrolling.
Merchant trading wood for gold.
Game state { wood: 8, food: 3, gold: 11, guardOnDuty: true }
--------------------------------------------------
Villager gathering resources.
Guard patrolling.
Merchant trading wood for gold.
Game state { wood: 6, food: 3, gold: 12, guardOnDuty: true }
--------------------------------------------------
Villager gathering resources.
Guard patrolling.
Merchant trading wood for gold.
Game state { wood: 4, food: 3, gold: 13, guardOnDuty: true }
--------------------------------------------------
Villager gathering resources.
Guard patrolling.
Game state { wood: 7, food: 3, gold: 13, guardOnDuty: true }
--------------------------------------------------
Villager gathering resources.
Guard patrolling.
Game state { wood: 10, food: 3, gold: 13, guardOnDuty: true }
--------------------------------------------------
Villager gathering resources.
Guard patrolling.
Game state { wood: 13, food: 3, gold: 13, guardOnDuty: true }
--------------------------------------------------
Guard patrolling.
Game state { wood: 13, food: 2, gold: 13, guardOnDuty: true }
--------------------------------------------------
Guard patrolling.
Game state { wood: 13, food: 1, gold: 13, guardOnDuty: true }
--------------------------------------------------
Guard patrolling.
Game state { wood: 13, food: 0, gold: 13, guardOnDuty: true }
--------------------------------------------------
Guard leaving to get food.
Game state { wood: 13, food: 0, gold: 13, guardOnDuty: false }
--------------------------------------------------
Thief stealing gold.
Game state { wood: 13, food: 0, gold: 0, guardOnDuty: false }
--------------------------------------------------