如何构建多智能体网络(函数式 API)¶
在本操作指南中,我们将演示如何实现一种多智能体网络架构,其中每个智能体可以与任何其他智能体通信(多对多连接),并可以决定接下来调用哪个智能体。我们将使用 LangGraph 的函数式 API——单个智能体将定义为任务,智能体之间的切换将在主entrypoint() 中定义。
import { entrypoint, task } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
// Define a tool to signal intent to hand off to a different agent
const transferToHotelAdvisor = tool(async () => {
return "Successfully transferred to hotel advisor";
}, {
name: "transferToHotelAdvisor",
description: "Ask hotel advisor agent for help.",
schema: z.object({}),
returnDirect: true,
});
// define an agent
const travelAdvisorTools = [transferToHotelAdvisor, ...];
const travelAdvisor = createReactAgent({
llm: model,
tools: travelAdvisorTools,
});
// define a task that calls an agent
const callTravelAdvisor = task("callTravelAdvisor", async (messages: BaseMessage[]) => {
const response = travelAdvisor.invoke({ messages });
return response.messages;
});
const networkGraph = entrypoint(
{ name: "networkGraph" },
async (messages: BaseMessageLike[]) => {
let callActiveAgent = callTravelAdvisor;
let agentMessages;
while (true) {
agentMessages = await callActiveAgent(messages);
messages = addMessages(messages, agentMessages);
callActiveAgent = getNextAgent(messages);
}
return messages;
});
设置¶
注意
本指南要求 @langchain/langgraph>=0.2.42
。
首先,安装此示例所需的依赖项
接下来,我们需要为 Anthropic(我们将使用的 LLM)设置 API 密钥
为 LangGraph 开发设置 LangSmith
注册 LangSmith 可快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用——在此阅读更多关于如何入门的信息。
旅行智能体示例¶
在此示例中,我们将构建一个旅行助手智能体团队,它们可以相互通信。
我们将创建 2 个智能体
travelAdvisor
:可以帮助推荐旅行目的地。可以向hotelAdvisor
寻求帮助。hotelAdvisor
:可以帮助推荐酒店。可以向travelAdvisor
寻求帮助。
这是一个全连接网络 - 每个智能体都可以与任何其他智能体通信。
首先,让我们创建智能体将使用的一些工具
import { tool } from "@langchain/core/tools";
import { z } from "zod";
// Tool for getting travel recommendations
const getTravelRecommendations = tool(async () => {
const destinations = ["aruba", "turks and caicos"];
return destinations[Math.floor(Math.random() * destinations.length)];
}, {
name: "getTravelRecommendations",
description: "Get recommendation for travel destinations",
schema: z.object({}),
});
// Tool for getting hotel recommendations
const getHotelRecommendations = tool(async (input: { location: "aruba" | "turks and caicos" }) => {
const recommendations = {
"aruba": [
"The Ritz-Carlton, Aruba (Palm Beach)",
"Bucuti & Tara Beach Resort (Eagle Beach)"
],
"turks and caicos": ["Grace Bay Club", "COMO Parrot Cay"]
};
return recommendations[input.location];
}, {
name: "getHotelRecommendations",
description: "Get hotel recommendations for a given destination.",
schema: z.object({
location: z.enum(["aruba", "turks and caicos"])
}),
});
// Define a tool to signal intent to hand off to a different agent
// Note: this is not using Command(goto) syntax for navigating to different agents:
// `workflow()` below handles the handoffs explicitly
const transferToHotelAdvisor = tool(async () => {
return "Successfully transferred to hotel advisor";
}, {
name: "transferToHotelAdvisor",
description: "Ask hotel advisor agent for help.",
schema: z.object({}),
// Hint to our agent implementation that it should stop
// immediately after invoking this tool
returnDirect: true,
});
const transferToTravelAdvisor = tool(async () => {
return "Successfully transferred to travel advisor";
}, {
name: "transferToTravelAdvisor",
description: "Ask travel advisor agent for help.",
schema: z.object({}),
// Hint to our agent implementation that it should stop
// immediately after invoking this tool
returnDirect: true,
});
转移工具
您可能已经注意到,我们在转移工具中使用了 tool(... { returnDirect: true })
。这样做是为了让单个智能体(例如 travelAdvisor
)在调用这些工具后可以提前退出 ReAct 循环,而无需最后一次调用模型来处理工具调用的结果。这是我们期望的行为,因为我们希望在智能体调用此工具时立即检测到并立即将控制权交给另一个智能体。
注意:这是为与预构建的 createReactAgent
一起使用而设计的——如果您正在构建自定义智能体,请确保手动添加逻辑来处理标记为 returnDirect
的工具的提前退出。
现在让我们定义智能体任务并将它们组合成一个多智能体网络工作流
import {
AIMessage,
type BaseMessageLike
} from "@langchain/core/messages";
import { ChatAnthropic } from "@langchain/anthropic";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import {
addMessages,
entrypoint,
task,
} from "@langchain/langgraph";
const model = new ChatAnthropic({
model: "claude-3-5-sonnet-latest",
});
const travelAdvisorTools = [
getTravelRecommendations,
transferToHotelAdvisor,
];
// Define travel advisor ReAct agent
const travelAdvisor = createReactAgent({
llm: model,
tools: travelAdvisorTools,
stateModifier: [
"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc).",
"If you need hotel recommendations, ask 'hotel_advisor' for help.",
"You MUST include human-readable response before transferring to another agent.",
].join(" "),
});
// You can also add additional logic like changing the input to the agent / output from the agent, etc.
// NOTE: we're invoking the ReAct agent with the full history of messages in the state
const callTravelAdvisor = task("callTravelAdvisor", async (messages: BaseMessageLike[]) => {
const response = await travelAdvisor.invoke({ messages });
return response.messages;
});
const hotelAdvisorTools = [
getHotelRecommendations,
transferToTravelAdvisor,
];
// Define hotel advisor ReAct agent
const hotelAdvisor = createReactAgent({
llm: model,
tools: hotelAdvisorTools,
stateModifier: [
"You are a hotel expert that can provide hotel recommendations for a given destination.",
"If you need help picking travel destinations, ask 'travel_advisor' for help.",
"You MUST include a human-readable response before transferring to another agent."
].join(" "),
});
// Add task for hotel advisor
const callHotelAdvisor = task("callHotelAdvisor", async (messages: BaseMessageLike[]) => {
const response = await hotelAdvisor.invoke({ messages });
return response.messages;
});
const networkGraph = entrypoint(
"networkGraph",
async (messages: BaseMessageLike[]) => {
// Converts inputs to LangChain messages as a side-effect
let currentMessages = addMessages([], messages);
let callActiveAgent = callTravelAdvisor;
while (true) {
const agentMessages = await callActiveAgent(currentMessages);
currentMessages = addMessages(currentMessages, agentMessages);
// Find the last AI message
// If one of the handoff tools is called, the last message returned
// by the agent will be a ToolMessage because we set them to have
// "returnDirect: true". This means that the last AIMessage will
// have tool calls.
// Otherwise, the last returned message will be an AIMessage with
// no tool calls, which means we are ready for new input.
const aiMsg = [...agentMessages].reverse()
.find((m): m is AIMessage => m.getType() === "ai");
// If no tool calls, we're done
if (!aiMsg?.tool_calls?.length) {
break;
}
// Get the last tool call and determine next agent
const toolCall = aiMsg.tool_calls.at(-1)!;
if (toolCall.name === "transferToTravelAdvisor") {
callActiveAgent = callTravelAdvisor;
} else if (toolCall.name === "transferToHotelAdvisor") {
callActiveAgent = callHotelAdvisor;
} else {
throw new Error(`Expected transfer tool, got '${toolCall.name}'`);
}
}
return messages;
});
最后,让我们定义一个助手来渲染智能体输出
const prettyPrintMessages = (update: Record<string, any>) => {
// Handle tuple case with namespace
if (Array.isArray(update)) {
const [ns, updateData] = update;
// Skip parent graph updates in the printouts
if (ns.length === 0) {
return;
}
const graphId = ns[ns.length - 1].split(":")[0];
console.log(`Update from subgraph ${graphId}:\n`);
update = updateData;
}
if (update.__metadata__?.cached) {
return;
}
// Print updates for each node
for (const [nodeName, updateValue] of Object.entries(update)) {
console.log(`Update from node ${nodeName}:\n`);
const coercedMessages = addMessages([], updateValue.messages);
for (const message of coercedMessages) {
const textContent = typeof message.content === "string"
? message.content
: JSON.stringify(message.content);
// Print message content based on role
if (message.getType() === "ai") {
console.log("=".repeat(33) + " Assistant Message " + "=".repeat(33));
console.log(textContent);
console.log();
} else if (message.getType() === "human") {
console.log("=".repeat(33) + " Human Message " + "=".repeat(33));
console.log(textContent);
console.log();
} else if (message.getType() === "tool") {
console.log("=".repeat(33) + " Tool Message " + "=".repeat(33));
console.log(textContent);
console.log();
}
}
console.log("\n");
}
};
让我们来测试一下
const stream = await networkGraph.stream([{
role: "user",
content: "i wanna go somewhere warm in the caribbean. pick one destination and give me hotel recommendations"
}], { subgraphs: true })
for await (const chunk of stream) {
prettyPrintMessages(chunk);
}
Update from subgraph callTravelAdvisor:
Update from node agent:
================================= Assistant Message =================================
[{"type":"text","text":"I'll help you find a warm Caribbean destination and then get specific hotel recommendations for you.\n\nLet me first get some destination recommendations for the Caribbean region."},{"type":"tool_use","id":"toolu_019fN1etkqtCSausSv8XufhL","name":"getTravelRecommendations","input":{}}]
Update from subgraph callTravelAdvisor:
Update from node tools:
================================= Tool Message =================================
turks and caicos
Update from subgraph callTravelAdvisor:
Update from node agent:
================================= Assistant Message =================================
[{"type":"text","text":"Great! I recommend Turks and Caicos for your Caribbean getaway. This beautiful British Overseas Territory is known for its stunning white-sand beaches, crystal-clear turquoise waters, and perfect warm weather year-round. Grace Bay Beach in Providenciales (often called \"Provo\") is consistently ranked among the world's best beaches. The islands offer excellent snorkeling, diving, and water sports opportunities, plus a relaxed Caribbean atmosphere.\n\nNow, let me connect you with our hotel advisor to get specific accommodation recommendations for Turks and Caicos."},{"type":"tool_use","id":"toolu_01UHAnBBK9zm2nAEh7brR7TY","name":"transferToHotelAdvisor","input":{}}]
Update from subgraph callTravelAdvisor:
Update from node tools:
================================= Tool Message =================================
Successfully transferred to hotel advisor
Update from subgraph callHotelAdvisor:
Update from node agent:
================================= Assistant Message =================================
[{"type":"text","text":"Let me get some hotel recommendations for Turks and Caicos:"},{"type":"tool_use","id":"toolu_012GUHBGXxyzwE5dY6nePq9s","name":"getHotelRecommendations","input":{"location":"turks and caicos"}}]
Update from subgraph callHotelAdvisor:
Update from node tools:
================================= Tool Message =================================
[
"Grace Bay Club",
"COMO Parrot Cay"
]
Update from subgraph callHotelAdvisor:
Update from node agent:
================================= Assistant Message =================================
Based on the recommendations, here are two excellent options in Turks and Caicos:
1. Grace Bay Club: This luxurious resort is located on the world-famous Grace Bay Beach. It offers all-oceanfront suites, exceptional dining options, and top-notch amenities including multiple pools, a spa, and various water sports activities. The resort is perfect for both couples and families, with adult-only and family-friendly sections.
2. COMO Parrot Cay: This exclusive private island resort offers the ultimate luxury experience. Located on its own island, it features pristine beaches, world-class spa treatments, and exceptional dining. The resort is known for its privacy, making it a favorite among celebrities. The rooms and villas offer sophisticated design with private pools and direct beach access.
Would you like more specific information about either of these properties or shall I search for additional options?
travelAdvisor
选择了一个目的地,然后决定调用 hotelAdvisor
获取更多信息!