在 [2]
已复制!
// process.env.OPENAI_API_KEY = "sk_...";
// process.env.TAVILY_API_KEY = "sk_...";
// Optional tracing in LangSmith
// process.env.LANGCHAIN_API_KEY = "sk_...";
// process.env.LANGCHAIN_TRACING_V2 = "true";
// process.env.LANGCHAIN_PROJECT = "Agent Supervisor: LangGraphJS";
// process.env.OPENAI_API_KEY = "sk_..."; // process.env.TAVILY_API_KEY = "sk_..."; // 可选的 LangSmith 追踪 // process.env.LANGCHAIN_API_KEY = "sk_..."; // process.env.LANGCHAIN_TRACING_V2 = "true"; // process.env.LANGCHAIN_PROJECT = "Agent Supervisor: LangGraphJS";
在 [3]
已复制!
import "dotenv/config";
import "dotenv/config";
定义状态¶
首先定义图的状态。这将只是一个消息列表,以及一个用于跟踪最近发送者的键
在 [4]
已复制!
import { END, Annotation } from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";
// This defines the object that is passed between each node
// in the graph. We will create different nodes for each agent and tool
const AgentState = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (x, y) => x.concat(y),
default: () => [],
}),
// The agent node that last performed work
next: Annotation<string>({
reducer: (x, y) => y ?? x ?? END,
default: () => END,
}),
});
import { END, Annotation } from "@langchain/langgraph"; import { BaseMessage } from "@langchain/core/messages"; // 这定义了在每个节点之间传递的对象 // 在图中。我们将为每个代理和工具创建不同的节点 const AgentState = Annotation.Root({ messages: Annotation({ reducer: (x, y) => x.concat(y), default: () => [], }), // 上次执行工作的代理节点 next: Annotation({ reducer: (x, y) => y ?? x ?? END, default: () => END, }), });
创建工具¶
对于此示例,您将创建一个代理来使用搜索引擎进行网络研究,以及一个代理来创建图表。在下面定义他们将使用的工具
在 [5]
已复制!
require("esm-hook"); // Only for running this in TSLab. See: https://github.com/yunabe/tslab/issues/72
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
import { DynamicStructuredTool } from "@langchain/core/tools";
import * as d3 from "d3";
// ----------ATTENTION----------
// If attempting to run this notebook locally, you must follow these instructions
// to install the necessary system dependencies for the `canvas` package.
// https://npmjs.net.cn/package/canvas#compiling
// -----------------------------
import { createCanvas } from "canvas";
import { z } from "zod";
import * as tslab from "tslab";
const chartTool = new DynamicStructuredTool({
name: "generate_bar_chart",
description:
"Generates a bar chart from an array of data points using D3.js and displays it for the user.",
schema: z.object({
data: z
.object({
label: z.string(),
value: z.number(),
})
.array(),
}),
func: async ({ data }) => {
const width = 500;
const height = 500;
const margin = { top: 20, right: 30, bottom: 30, left: 40 };
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
const x = d3
.scaleBand()
.domain(data.map((d) => d.label))
.range([margin.left, width - margin.right])
.padding(0.1);
const y = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d.value) ?? 0])
.nice()
.range([height - margin.bottom, margin.top]);
const colorPalette = [
"#e6194B",
"#3cb44b",
"#ffe119",
"#4363d8",
"#f58231",
"#911eb4",
"#42d4f4",
"#f032e6",
"#bfef45",
"#fabebe",
];
data.forEach((d, idx) => {
ctx.fillStyle = colorPalette[idx % colorPalette.length];
ctx.fillRect(
x(d.label) ?? 0,
y(d.value),
x.bandwidth(),
height - margin.bottom - y(d.value),
);
});
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.moveTo(margin.left, height - margin.bottom);
ctx.lineTo(width - margin.right, height - margin.bottom);
ctx.stroke();
ctx.textAlign = "center";
ctx.textBaseline = "top";
x.domain().forEach((d) => {
const xCoord = (x(d) ?? 0) + x.bandwidth() / 2;
ctx.fillText(d, xCoord, height - margin.bottom + 6);
});
ctx.beginPath();
ctx.moveTo(margin.left, height - margin.top);
ctx.lineTo(margin.left, height - margin.bottom);
ctx.stroke();
ctx.textAlign = "right";
ctx.textBaseline = "middle";
const ticks = y.ticks();
ticks.forEach((d) => {
const yCoord = y(d); // height - margin.bottom - y(d);
ctx.moveTo(margin.left, yCoord);
ctx.lineTo(margin.left - 6, yCoord);
ctx.stroke();
ctx.fillText(d.toString(), margin.left - 8, yCoord);
});
await tslab.display.png(canvas.toBuffer());
return "Chart has been generated and displayed to the user!";
},
});
const tavilyTool = new TavilySearchResults();
require("esm-hook"); // 仅供在 TSLab 中运行此笔记本。参见:https://github.com/yunabe/tslab/issues/72 import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; import { DynamicStructuredTool } from "@langchain/core/tools"; import * as d3 from "d3"; // ----------注意---------- // 如果尝试在本地运行此笔记本,您必须按照以下说明 // 安装 `canvas` 包所需的系统依赖项。 // https://npmjs.net.cn/package/canvas#compiling // ----------------------------- import { createCanvas } from "canvas"; import { z } from "zod"; import * as tslab from "tslab"; const chartTool = new DynamicStructuredTool({ name: "generate_bar_chart", description: "使用 D3.js 从一组数据点生成条形图,并将其显示给用户。", schema: z.object({ data: z .object({ label: z.string(), value: z.number(), }) .array(), }), func: async ({ data }) => { const width = 500; const height = 500; const margin = { top: 20, right: 30, bottom: 30, left: 40 }; const canvas = createCanvas(width, height); const ctx = canvas.getContext("2d"); const x = d3 .scaleBand() .domain(data.map((d) => d.label)) .range([margin.left, width - margin.right]) .padding(0.1); const y = d3 .scaleLinear() .domain([0, d3.max(data, (d) => d.value) ?? 0]) .nice() .range([height - margin.bottom, margin.top]); const colorPalette = [ "#e6194B", "#3cb44b", "#ffe119", "#4363d8", "#f58231", "#911eb4", "#42d4f4", "#f032e6", "#bfef45", "#fabebe", ]; data.forEach((d, idx) => { ctx.fillStyle = colorPalette[idx % colorPalette.length]; ctx.fillRect( x(d.label) ?? 0, y(d.value), x.bandwidth(), height - margin.bottom - y(d.value), ); }); ctx.beginPath(); ctx.strokeStyle = "black"; ctx.moveTo(margin.left, height - margin.bottom); ctx.lineTo(width - margin.right, height - margin.bottom); ctx.stroke(); ctx.textAlign = "center"; ctx.textBaseline = "top"; x.domain().forEach((d) => { const xCoord = (x(d) ?? 0) + x.bandwidth() / 2; ctx.fillText(d, xCoord, height - margin.bottom + 6); }); ctx.beginPath(); ctx.moveTo(margin.left, height - margin.top); ctx.lineTo(margin.left, height - margin.bottom); ctx.stroke(); ctx.textAlign = "right"; ctx.textBaseline = "middle"; const ticks = y.ticks(); ticks.forEach((d) => { const yCoord = y(d); // height - margin.bottom - y(d); ctx.moveTo(margin.left, yCoord); ctx.lineTo(margin.left - 6, yCoord); ctx.stroke(); ctx.fillText(d.toString(), margin.left - 8, yCoord); }); await tslab.display.png(canvas.toBuffer()); return "图表已生成并显示给用户!"; }, }); const tavilyTool = new TavilySearchResults();
创建代理主管¶
主管在我们的工作代理之间路由工作。
在 [6]
已复制!
import { z } from "zod";
import { JsonOutputToolsParser } from "langchain/output_parsers";
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
const members = ["researcher", "chart_generator"] as const;
const systemPrompt =
"You are a supervisor tasked with managing a conversation between the" +
" following workers: {members}. Given the following user request," +
" respond with the worker to act next. Each worker will perform a" +
" task and respond with their results and status. When finished," +
" respond with FINISH.";
const options = [END, ...members];
// Define the routing function
const routingTool = {
name: "route",
description: "Select the next role.",
schema: z.object({
next: z.enum([END, ...members]),
}),
}
const prompt = ChatPromptTemplate.fromMessages([
["system", systemPrompt],
new MessagesPlaceholder("messages"),
[
"system",
"Given the conversation above, who should act next?" +
" Or should we FINISH? Select one of: {options}",
],
]);
const formattedPrompt = await prompt.partial({
options: options.join(", "),
members: members.join(", "),
});
const llm = new ChatOpenAI({
modelName: "gpt-4o",
temperature: 0,
});
const supervisorChain = formattedPrompt
.pipe(llm.bindTools(
[routingTool],
{
tool_choice: "route",
},
))
.pipe(new JsonOutputToolsParser())
// select the first one
.pipe((x) => (x[0].args));
import { z } from "zod"; import { JsonOutputToolsParser } from "langchain/output_parsers"; import { ChatOpenAI } from "@langchain/openai"; import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; const members = ["researcher", "chart_generator"] as const; const systemPrompt = "您是一名主管,负责管理以下工作人员之间的对话:{members}。鉴于以下用户请求," + "使用应执行操作的下一个工作人员进行响应。每个工作人员将执行一项" + "任务,并使用其结果和状态进行响应。完成后," + "使用 FINISH 进行响应。"; const options = [END, ...members]; // 定义路由函数 const routingTool = { name: "route", description: "选择下一个角色。", schema: z.object({ next: z.enum([END, ...members]), }), } const prompt = ChatPromptTemplate.fromMessages([ ["system", systemPrompt], new MessagesPlaceholder("messages"), [ "system", "鉴于上述对话,谁应该执行下一步操作?" + "或者我们应该 FINISH?选择以下一项:{options}", ], ]); const formattedPrompt = await prompt.partial({ options: options.join(", "), members: members.join(", "), }); const llm = new ChatOpenAI({ modelName: "gpt-4o", temperature: 0, }); const supervisorChain = formattedPrompt .pipe(llm.bindTools( [routingTool], { tool_choice: "route", }, )) .pipe(new JsonOutputToolsParser()) // 选择第一个 .pipe((x) => (x[0].args));
在 [7]
已复制!
import { HumanMessage } from "@langchain/core/messages";
await supervisorChain.invoke({
messages: [
new HumanMessage({
content: "write a report on birds.",
}),
],
});
import { HumanMessage } from "@langchain/core/messages"; await supervisorChain.invoke({ messages: [ new HumanMessage({ content: "写一份关于鸟类的报告。", }), ], });
{ next: 'researcher' }
构建图¶
我们已准备好开始构建图。首先,创建要添加到图中的代理。
在 [8]
已复制!
import { RunnableConfig } from "@langchain/core/runnables";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { SystemMessage } from "@langchain/core/messages";
// Recall llm was defined as ChatOpenAI above
// It could be any other language model
const researcherAgent = createReactAgent({
llm,
tools: [tavilyTool],
messageModifier: new SystemMessage("You are a web researcher. You may use the Tavily search engine to search the web for" +
" important information, so the Chart Generator in your team can make useful plots.")
})
const researcherNode = async (
state: typeof AgentState.State,
config?: RunnableConfig,
) => {
const result = await researcherAgent.invoke(state, config);
const lastMessage = result.messages[result.messages.length - 1];
return {
messages: [
new HumanMessage({ content: lastMessage.content, name: "Researcher" }),
],
};
};
const chartGenAgent = createReactAgent({
llm,
tools: [chartTool],
messageModifier: new SystemMessage("You excel at generating bar charts. Use the researcher's information to generate the charts.")
})
const chartGenNode = async (
state: typeof AgentState.State,
config?: RunnableConfig,
) => {
const result = await chartGenAgent.invoke(state, config);
const lastMessage = result.messages[result.messages.length - 1];
return {
messages: [
new HumanMessage({ content: lastMessage.content, name: "ChartGenerator" }),
],
};
};
import { RunnableConfig } from "@langchain/core/runnables"; import { createReactAgent } from "@langchain/langgraph/prebuilt"; import { SystemMessage } from "@langchain/core/messages"; // 回想一下,上面将 llm 定义为 ChatOpenAI // 它可以是任何其他语言模型 const researcherAgent = createReactAgent({ llm, tools: [tavilyTool], messageModifier: new SystemMessage("您是一名网络研究员。您可以使用 Tavily 搜索引擎搜索网络以获取" + "重要信息,以便您的团队中的图表生成器可以制作有用的图表。") }) const researcherNode = async ( state: typeof AgentState.State, config?: RunnableConfig, ) => { const result = await researcherAgent.invoke(state, config); const lastMessage = result.messages[result.messages.length - 1]; return { messages: [ new HumanMessage({ content: lastMessage.content, name: "Researcher" }), ], }; }; const chartGenAgent = createReactAgent({ llm, tools: [chartTool], messageModifier: new SystemMessage("您擅长生成条形图。使用研究人员的信息来生成图表。") }) const chartGenNode = async ( state: typeof AgentState.State, config?: RunnableConfig, ) => { const result = await chartGenAgent.invoke(state, config); const lastMessage = result.messages[result.messages.length - 1]; return { messages: [ new HumanMessage({ content: lastMessage.content, name: "ChartGenerator" }), ], }; };
现在我们可以创建图本身!添加节点,并添加边来定义如何在图中执行工作。
在 [9]
已复制!
import { START, StateGraph } from "@langchain/langgraph";
// 1. Create the graph
const workflow = new StateGraph(AgentState)
// 2. Add the nodes; these will do the work
.addNode("researcher", researcherNode)
.addNode("chart_generator", chartGenNode)
.addNode("supervisor", supervisorChain);
// 3. Define the edges. We will define both regular and conditional ones
// After a worker completes, report to supervisor
members.forEach((member) => {
workflow.addEdge(member, "supervisor");
});
workflow.addConditionalEdges(
"supervisor",
(x: typeof AgentState.State) => x.next,
);
workflow.addEdge(START, "supervisor");
const graph = workflow.compile();
import { START, StateGraph } from "@langchain/langgraph"; // 1. 创建图 const workflow = new StateGraph(AgentState) // 2. 添加节点;这些节点将执行工作 .addNode("researcher", researcherNode) .addNode("chart_generator", chartGenNode) .addNode("supervisor", supervisorChain); // 3. 定义边。我们将定义常规边和条件边 // 工作人员完成工作后,向主管报告 members.forEach((member) => { workflow.addEdge(member, "supervisor"); }); workflow.addConditionalEdges( "supervisor", (x: typeof AgentState.State) => x.next, ); workflow.addEdge(START, "supervisor"); const graph = workflow.compile();
调用团队¶
创建完图后,我们现在就可以调用它,看看它的表现!
在 [10]
已复制!
let streamResults = graph.stream(
{
messages: [
new HumanMessage({
content: "What were the 3 most popular tv shows in 2023?",
}),
],
},
{ recursionLimit: 100 },
);
for await (const output of await streamResults) {
if (!output?.__end__) {
console.log(output);
console.log("----");
}
}
let streamResults = graph.stream( { messages: [ new HumanMessage({ content: "2023 年最受欢迎的 3 部电视剧是什么?", }), ], }, { recursionLimit: 100 }, ); for await (const output of await streamResults) { if (!output?.__end__) { console.log(output); console.log("----"); } }
{ supervisor: { next: 'researcher' } } ---- { researcher: { messages: [ HumanMessage { "content": "Based on the search results, the three most popular TV shows in 2023 were:\n\n1. **Succession** - This show has been highly acclaimed and frequently mentioned in various lists of top TV shows for 2023.\n2. **The Last of Us** - An adaptation of the popular video game, this show has received significant attention and praise.\n3. **The Bear** - This series has also been highlighted as one of the best and most popular shows of the year.\n\nThese shows have been consistently mentioned across different sources, including Rotten Tomatoes, Metacritic, and IMDb.", "name": "Researcher", "additional_kwargs": {}, "response_metadata": {} } ] } } ---- { supervisor: { next: 'chart_generator' } } ----
{ chart_generator: { messages: [ HumanMessage { "content": "Here is a bar chart representing the three most popular TV shows in 2023:\n\n1. **Succession**\n2. **The Last of Us**\n3. **The Bear**\n\n![Bar Chart](sandbox:/generated_chart.png)", "name": "ChartGenerator", "additional_kwargs": {}, "response_metadata": {} } ] } } ---- { supervisor: { next: '__end__' } } ----
在 [11]
已复制!
streamResults = graph.stream(
{
messages: [
new HumanMessage({
content: "Generate a bar chart of the US GDP growth from 2021-2023.",
}),
],
},
{ recursionLimit: 150 },
);
for await (const output of await streamResults) {
if (!output?.__end__) {
console.log(output);
console.log("----");
}
}
streamResults = graph.stream( { messages: [ new HumanMessage({ content: "生成 2021 年至 2023 年美国 GDP 增长的条形图。", }), ], }, { recursionLimit: 150 }, ); for await (const output of await streamResults) { if (!output?.__end__) { console.log(output); console.log("----"); } }
{ supervisor: { next: 'researcher' } } ---- { researcher: { messages: [ HumanMessage { "content": "Based on the search results, here are the US GDP growth rates for the years 2021 to 2023:\n\n- **2021**: 5.9%\n- **2022**: 2.1%\n- **2023**: 3.3% (advance estimate for the fourth quarter)\n\nThis data can be used to generate a bar chart. I'll pass this information to the Chart Generator in the team.", "name": "Researcher", "additional_kwargs": {}, "response_metadata": {} } ] } } ---- { supervisor: { next: 'chart_generator' } } ----
{ chart_generator: { messages: [ HumanMessage { "content": "Here is the bar chart showing the US GDP growth from 2021 to 2023:\n\n![US GDP Growth 2021-2023](sandbox:/generated_chart.png)\n\n- **2021**: 5.9%\n- **2022**: 2.1%\n- **2023**: 3.3% (advance estimate)\n\nIf you need any further analysis or additional charts, feel free to ask!", "name": "ChartGenerator", "additional_kwargs": {}, "response_metadata": {} } ] } } ---- { supervisor: { next: '__end__' } } ----
您可以 点击此处 查看上述查询的 LangSmith 追踪。