如何从传统的 LangChain 代理迁移到 LangGraph
本指南假定您熟悉以下概念: - 代理 - LangGraph.js - 工具调用
本文重点介绍如何从传统的 LangChain 代理迁移到更灵活的
LangGraph 代理。
LangChain 代理(尤其是
AgentExecutor
)具有多个配置参数。在本文中,我们将展示这些参数如何映射到使用
create_react_agent
预构建辅助方法的 LangGraph 反应代理执行器。
有关在 LangGraph 中构建代理工作流的更多信息,请查看 此处的文档。
预备知识
本操作指南使用 OpenAI 的 "gpt-4o-mini"
作为语言模型。如果您正在以笔记本形式运行本指南,请按如下方式设置您的
OpenAI API 密钥:
// process.env.OPENAI_API_KEY = "...";
// Optional, add tracing in LangSmith
// process.env.LANGSMITH_API_KEY = "ls...";
// process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true";
// process.env.LANGSMITH_TRACING = "true";
// process.env.LANGSMITH_PROJECT = "How to migrate: LangGraphJS";
// Reduce tracing latency if you are not in a serverless environment
// process.env.LANGCHAIN_CALLBACKS_BACKGROUND = "true";
基本用法
对于工具调用的 ReAct 风格智能代理的基本创建和使用,其功能是相同的。首先,我们定义一个模型和工具(tool(s)),然后使用它们来创建一个智能代理。
tool 函数可在 @langchain/core 版本 0.2.7 及以上中使用。
如果你使用的是更早版本的 core,请改用实例化并使用
DynamicStructuredTool。
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({
model: "gpt-4o-mini",
});
const magicTool = tool(
async ({ input }: { input: number }) => {
return `${input + 2}`;
},
{
name: "magic_function",
description: "Applies a magic function to an input.",
schema: z.object({
input: z.number(),
}),
}
);
const tools = [magicTool];
const query = "what is the value of magic_function(3)?";
对于 LangChain 的
AgentExecutor,
我们定义了一个带有代理草稿区占位符的提示词。可以通过以下方式调用该代理:
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { createToolCallingAgent } from "langchain/agents";
import { AgentExecutor } from "langchain/agents";
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant"],
["placeholder", "{chat_history}"],
["human", "{input}"],
["placeholder", "{agent_scratchpad}"],
]);
const agent = createToolCallingAgent({
llm,
tools,
prompt,
});
const agentExecutor = new AgentExecutor({
agent,
tools,
});
await agentExecutor.invoke({ input: query });
{
input: "what is the value of magic_function(3)?",
output: "The value of `magic_function(3)` is 5."
}
LangGraph 提供的预置 React Agent
执行器
管理一个由消息列表定义的状态。与 AgentExecutor
类似,它会持续处理该列表,直到代理输出中不再包含工具调用为止。要启动它,我们输入一个消息列表。输出将包含图的完整状态——在本例中,即对话历史记录以及表示中间工具调用的消息:
import { createReactAgent } from "@langchain/langgraph/prebuilt";
const app = createReactAgent({
llm,
tools,
});
let agentOutput = await app.invoke({
messages: [
{
role: "user",
content: query,
},
],
});
console.log(agentOutput);
{
messages: [
HumanMessage {
"id": "eeef343c-80d1-4ccb-86af-c109343689cd",
"content": "what is the value of magic_function(3)?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-A7exs2uRqEipaZ7MtRbXnqu0vT0Da",
"content": "",
"additional_kwargs": {
"tool_calls": [
{
"id": "call_MtwWLn000BQHeSYQKsbxYNR0",
"type": "function",
"function": "[Object]"
}
]
},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 55,
"totalTokens": 69
},
"finish_reason": "tool_calls",
"system_fingerprint": "fp_483d39d857"
},
"tool_calls": [
{
"name": "magic_function",
"args": {
"input": 3
},
"type": "tool_call",
"id": "call_MtwWLn000BQHeSYQKsbxYNR0"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 55,
"output_tokens": 14,
"total_tokens": 69
}
},
ToolMessage {
"id": "1001bf20-7cde-4f8b-81f1-1faa654a8bb4",
"content": "5",
"name": "magic_function",
"additional_kwargs": {},
"response_metadata": {},
"tool_call_id": "call_MtwWLn000BQHeSYQKsbxYNR0"
},
AIMessage {
"id": "chatcmpl-A7exsTk3ilzGzC8DuY8GpnKOaGdvx",
"content": "The value of `magic_function(3)` is 5.",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 78,
"totalTokens": 92
},
"finish_reason": "stop",
"system_fingerprint": "fp_54e2f484be"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 78,
"output_tokens": 14,
"total_tokens": 92
}
}
]
}
const messageHistory = agentOutput.messages;
const newQuery = "Pardon?";
agentOutput = await app.invoke({
messages: [...messageHistory, { role: "user", content: newQuery }],
});
{
messages: [
HumanMessage {
"id": "eeef343c-80d1-4ccb-86af-c109343689cd",
"content": "what is the value of magic_function(3)?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-A7exs2uRqEipaZ7MtRbXnqu0vT0Da",
"content": "",
"additional_kwargs": {
"tool_calls": [
{
"id": "call_MtwWLn000BQHeSYQKsbxYNR0",
"type": "function",
"function": "[Object]"
}
]
},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 55,
"totalTokens": 69
},
"finish_reason": "tool_calls",
"system_fingerprint": "fp_483d39d857"
},
"tool_calls": [
{
"name": "magic_function",
"args": {
"input": 3
},
"type": "tool_call",
"id": "call_MtwWLn000BQHeSYQKsbxYNR0"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 55,
"output_tokens": 14,
"total_tokens": 69
}
},
ToolMessage {
"id": "1001bf20-7cde-4f8b-81f1-1faa654a8bb4",
"content": "5",
"name": "magic_function",
"additional_kwargs": {},
"response_metadata": {},
"tool_call_id": "call_MtwWLn000BQHeSYQKsbxYNR0"
},
AIMessage {
"id": "chatcmpl-A7exsTk3ilzGzC8DuY8GpnKOaGdvx",
"content": "The value of `magic_function(3)` is 5.",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 78,
"totalTokens": 92
},
"finish_reason": "stop",
"system_fingerprint": "fp_54e2f484be"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 78,
"output_tokens": 14,
"total_tokens": 92
}
},
HumanMessage {
"id": "1f2a9f41-c8ff-48fe-9d93-e663ee9279ff",
"content": "Pardon?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-A7exyTe9Ofs63Ex3sKwRx3wWksNup",
"content": "The result of calling the `magic_function` with an input of 3 is 5.",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 20,
"promptTokens": 102,
"totalTokens": 122
},
"finish_reason": "stop",
"system_fingerprint": "fp_483d39d857"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 102,
"output_tokens": 20,
"total_tokens": 122
}
}
]
}
提示模板
使用传统的 LangChain 代理时,你需要传入一个提示模板。你可以通过它来控制代理。
在 LangGraph 中, react agent executor 默认没有提示词。你可以通过以下几种方式实现对代理的类似控制:
- 传入一个系统消息作为输入
- 使用一个系统消息初始化代理
- 使用一个函数初始化代理,该函数可在将消息传递给模型之前对其进行转换。
下面我们来看一下以上几种方式。我们将传入自定义指令,让代理以西班牙语进行回复。
首先,使用 LangChain 的 AgentExecutor:
const spanishPrompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant. Respond only in Spanish."],
["placeholder", "{chat_history}"],
["human", "{input}"],
["placeholder", "{agent_scratchpad}"],
]);
const spanishAgent = createToolCallingAgent({
llm,
tools,
prompt: spanishPrompt,
});
const spanishAgentExecutor = new AgentExecutor({
agent: spanishAgent,
tools,
});
await spanishAgentExecutor.invoke({ input: query });
{
input: "what is the value of magic_function(3)?",
output: "El valor de `magic_function(3)` es 5."
}
现在,让我们向 react agent executor 传递一个自定义的系统消息。
LangGraph 的预构建 create_react_agent
不直接接受提示模板作为参数,而是接受一个 messages_modifier
参数。此参数会在消息传递到模型之前对其进行修改,可以是以下四种值之一:
- 一个
SystemMessage,它会被添加到消息列表的开头。 - 一个
string,它会被转换为SystemMessage,并添加到消息列表的开头。 - 一个
Callable,它接收一个消息列表作为输入。输出结果将被传递给语言模型。 - 或者是一个
Runnable,它也应该接收一个消息列表作为输入。输出结果将被传递给语言模型。
下面是实际使用方式:
const systemMessage = "You are a helpful assistant. Respond only in Spanish.";
// This could also be a SystemMessage object
// const systemMessage = new SystemMessage("You are a helpful assistant. Respond only in Spanish.");
const appWithSystemMessage = createReactAgent({
llm,
tools,
messageModifier: systemMessage,
});
agentOutput = await appWithSystemMessage.invoke({
messages: [{ role: "user", content: query }],
});
agentOutput.messages[agentOutput.messages.length - 1];
AIMessage {
"id": "chatcmpl-A7ey8LGWAs8ldrRRcO5wlHM85w9T8",
"content": "El valor de `magic_function(3)` es 5.",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 89,
"totalTokens": 103
},
"finish_reason": "stop",
"system_fingerprint": "fp_483d39d857"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 89,
"output_tokens": 14,
"total_tokens": 103
}
}
我们也可以传入一个任意函数。该函数应该接收一个消息列表并输出一个消息列表。
在这里我们可以进行各种任意的消息格式化。在这种情况下,我们只需在消息列表的开头添加一个SystemMessage。
import {
BaseMessage,
SystemMessage,
HumanMessage,
} from "@langchain/core/messages";
const modifyMessages = (messages: BaseMessage[]) => {
return [
new SystemMessage("You are a helpful assistant. Respond only in Spanish."),
...messages,
new HumanMessage("Also say 'Pandemonium!' after the answer."),
];
};
const appWithMessagesModifier = createReactAgent({
llm,
tools,
messageModifier: modifyMessages,
});
agentOutput = await appWithMessagesModifier.invoke({
messages: [{ role: "user", content: query }],
});
console.log({
input: query,
output: agentOutput.messages[agentOutput.messages.length - 1].content,
});
{
input: "what is the value of magic_function(3)?",
output: "El valor de magic_function(3) es 5. ¡Pandemonium!"
}
内存
通过 LangChain 的
AgentExecutor,你可以添加聊天内存类,使其能够参与多轮对话。
import { ChatMessageHistory } from "@langchain/community/stores/message/in_memory";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
const memory = new ChatMessageHistory();
const agentExecutorWithMemory = new RunnableWithMessageHistory({
runnable: agentExecutor,
getMessageHistory: () => memory,
inputMessagesKey: "input",
historyMessagesKey: "chat_history",
});
const config = { configurable: { sessionId: "test-session" } };
agentOutput = await agentExecutorWithMemory.invoke(
{ input: "Hi, I'm polly! What's the output of magic_function of 3?" },
config
);
console.log(agentOutput.output);
agentOutput = await agentExecutorWithMemory.invoke(
{ input: "Remember my name?" },
config
);
console.log("---");
console.log(agentOutput.output);
console.log("---");
agentOutput = await agentExecutorWithMemory.invoke(
{ input: "what was that output again?" },
config
);
console.log(agentOutput.output);
The output of the magic function for the input 3 is 5.
---
Yes, your name is Polly! How can I assist you today?
---
The output of the magic function for the input 3 is 5.
在 LangGraph 中
在 LangGraph 中与此类型内存等效的功能是 持久化 和 检查点。
为代理添加一个 checkpointer,你就能免费获得聊天记忆功能。你还需要在
config 参数的 configurable 字段中传递一个
thread_id。请注意,我们每次请求只传递一条消息,但模型仍然具有之前运行的上下文信息:
import { MemorySaver } from "@langchain/langgraph";
const checkpointer = new MemorySaver();
const appWithMemory = createReactAgent({
llm: llm,
tools: tools,
checkpointSaver: checkpointer,
});
const langGraphConfig = {
configurable: {
thread_id: "test-thread",
},
};
agentOutput = await appWithMemory.invoke(
{
messages: [
{
role: "user",
content: "Hi, I'm polly! What's the output of magic_function of 3?",
},
],
},
langGraphConfig
);
console.log(agentOutput.messages[agentOutput.messages.length - 1].content);
console.log("---");
agentOutput = await appWithMemory.invoke(
{
messages: [{ role: "user", content: "Remember my name?" }],
},
langGraphConfig
);
console.log(agentOutput.messages[agentOutput.messages.length - 1].content);
console.log("---");
agentOutput = await appWithMemory.invoke(
{
messages: [{ role: "user", content: "what was that output again?" }],
},
langGraphConfig
);
console.log(agentOutput.messages[agentOutput.messages.length - 1].content);
Hi Polly! The output of the magic function for the input 3 is 5.
---
Yes, your name is Polly!
---
The output of the magic function for the input 3 was 5.
遍历步骤
使用 LangChain 的
AgentExecutor,
你可以通过
stream
方法来遍历步骤:
const langChainStream = await agentExecutor.stream({ input: query });
for await (const step of langChainStream) {
console.log(step);
}
{
intermediateSteps: [
{
action: {
tool: "magic_function",
toolInput: { input: 3 },
toolCallId: "call_IQZr1yy2Ug6904VkQg6pWGgR",
log: 'Invoking "magic_function" with {"input":3}\n',
messageLog: [
AIMessageChunk {
"id": "chatcmpl-A7eziUrDmLSSMoiOskhrfbsHqx4Sd",
"content": "",
"additional_kwargs": {
"tool_calls": [
{
"index": 0,
"id": "call_IQZr1yy2Ug6904VkQg6pWGgR",
"type": "function",
"function": "[Object]"
}
]
},
"response_metadata": {
"prompt": 0,
"completion": 0,
"finish_reason": "tool_calls",
"system_fingerprint": "fp_483d39d857"
},
"tool_calls": [
{
"name": "magic_function",
"args": {
"input": 3
},
"id": "call_IQZr1yy2Ug6904VkQg6pWGgR",
"type": "tool_call"
}
],
"tool_call_chunks": [
{
"name": "magic_function",
"args": "{\"input\":3}",
"id": "call_IQZr1yy2Ug6904VkQg6pWGgR",
"index": 0,
"type": "tool_call_chunk"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 61,
"output_tokens": 14,
"total_tokens": 75
}
}
]
},
observation: "5"
}
]
}
{ output: "The value of `magic_function(3)` is 5." }
在 LangGraph 中
在 LangGraph 中,使用 stream 方法原生地处理这些内容。
const langGraphStream = await app.stream(
{ messages: [{ role: "user", content: query }] },
{ streamMode: "updates" }
);
for await (const step of langGraphStream) {
console.log(step);
}
{
agent: {
messages: [
AIMessage {
"id": "chatcmpl-A7ezu8hirCENjdjR2GpLjkzXFTEmp",
"content": "",
"additional_kwargs": {
"tool_calls": [
{
"id": "call_KhhNL0m3mlPoJiboFMoX8hzk",
"type": "function",
"function": "[Object]"
}
]
},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 55,
"totalTokens": 69
},
"finish_reason": "tool_calls",
"system_fingerprint": "fp_483d39d857"
},
"tool_calls": [
{
"name": "magic_function",
"args": {
"input": 3
},
"type": "tool_call",
"id": "call_KhhNL0m3mlPoJiboFMoX8hzk"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 55,
"output_tokens": 14,
"total_tokens": 69
}
}
]
}
}
{
tools: {
messages: [
ToolMessage {
"content": "5",
"name": "magic_function",
"additional_kwargs": {},
"response_metadata": {},
"tool_call_id": "call_KhhNL0m3mlPoJiboFMoX8hzk"
}
]
}
}
{
agent: {
messages: [
AIMessage {
"id": "chatcmpl-A7ezuTrh8GC550eKa1ZqRZGjpY5zh",
"content": "The value of `magic_function(3)` is 5.",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 78,
"totalTokens": 92
},
"finish_reason": "stop",
"system_fingerprint": "fp_483d39d857"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 78,
"output_tokens": 14,
"total_tokens": 92
}
}
]
}
}
returnIntermediateSteps
在 AgentExecutor 上设置此参数允许用户访问 intermediate_steps,它将代理操作(例如工具调用)与其 结果配对。
const agentExecutorWithIntermediateSteps = new AgentExecutor({
agent,
tools,
returnIntermediateSteps: true,
});
const result = await agentExecutorWithIntermediateSteps.invoke({
input: query,
});
console.log(result.intermediateSteps);
[
{
action: {
tool: "magic_function",
toolInput: { input: 3 },
toolCallId: "call_mbg1xgLEYEEWClbEaDe7p5tK",
log: 'Invoking "magic_function" with {"input":3}\n',
messageLog: [
AIMessageChunk {
"id": "chatcmpl-A7f0NdSRSUJsBP6ENTpiQD4LzpBAH",
"content": "",
"additional_kwargs": {
"tool_calls": [
{
"index": 0,
"id": "call_mbg1xgLEYEEWClbEaDe7p5tK",
"type": "function",
"function": "[Object]"
}
]
},
"response_metadata": {
"prompt": 0,
"completion": 0,
"finish_reason": "tool_calls",
"system_fingerprint": "fp_54e2f484be"
},
"tool_calls": [
{
"name": "magic_function",
"args": {
"input": 3
},
"id": "call_mbg1xgLEYEEWClbEaDe7p5tK",
"type": "tool_call"
}
],
"tool_call_chunks": [
{
"name": "magic_function",
"args": "{\"input\":3}",
"id": "call_mbg1xgLEYEEWClbEaDe7p5tK",
"index": 0,
"type": "tool_call_chunk"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 61,
"output_tokens": 14,
"total_tokens": 75
}
}
]
},
observation: "5"
}
]
默认情况下, React 代理执行器 在 LangGraph 中会将所有消息追加到中心状态中。因此, 只需查看完整状态即可查看任何中间步骤。
agentOutput = await app.invoke({
messages: [{ role: "user", content: query }],
});
console.log(agentOutput.messages);
[
HumanMessage {
"id": "46a825b2-13a3-4f19-b1aa-7716c53eb247",
"content": "what is the value of magic_function(3)?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"id": "chatcmpl-A7f0iUuWktC8gXztWZCjofqyCozY2",
"content": "",
"additional_kwargs": {
"tool_calls": [
{
"id": "call_ndsPDU58wsMeGaqr41cSlLlF",
"type": "function",
"function": "[Object]"
}
]
},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 55,
"totalTokens": 69
},
"finish_reason": "tool_calls",
"system_fingerprint": "fp_483d39d857"
},
"tool_calls": [
{
"name": "magic_function",
"args": {
"input": 3
},
"type": "tool_call",
"id": "call_ndsPDU58wsMeGaqr41cSlLlF"
}
],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 55,
"output_tokens": 14,
"total_tokens": 69
}
},
ToolMessage {
"id": "ac6aa309-bbfb-46cd-ba27-cbdbfd848705",
"content": "5",
"name": "magic_function",
"additional_kwargs": {},
"response_metadata": {},
"tool_call_id": "call_ndsPDU58wsMeGaqr41cSlLlF"
},
AIMessage {
"id": "chatcmpl-A7f0i7iHyDUV6is6sgwtcXivmFZ1x",
"content": "The value of `magic_function(3)` is 5.",
"additional_kwargs": {},
"response_metadata": {
"tokenUsage": {
"completionTokens": 14,
"promptTokens": 78,
"totalTokens": 92
},
"finish_reason": "stop",
"system_fingerprint": "fp_54e2f484be"
},
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": {
"input_tokens": 78,
"output_tokens": 14,
"total_tokens": 92
}
}
]
maxIterations
AgentExecutor 实现了一个 maxIterations 参数,而在 LangGraph 中
则通过 recursionLimit 来控制。
请注意,在 LangChain 的 AgentExecutor
中,一次“迭代”包括一次完整的工具调用和执行。 在 LangGraph
中,每个步骤都会计入递归限制,因此我们需要乘以二(并加一)以获得等效的
结果。
以下是如何在旧版 AgentExecutor 中设置此参数的示例:
const badMagicTool = tool(
async ({ input: _input }) => {
return "Sorry, there was a temporary error. Please try again with the same input.";
},
{
name: "magic_function",
description: "Applies a magic function to an input.",
schema: z.object({
input: z.string(),
}),
}
);
const badTools = [badMagicTool];
const spanishAgentExecutorWithMaxIterations = new AgentExecutor({
agent: createToolCallingAgent({
llm,
tools: badTools,
prompt: spanishPrompt,
}),
tools: badTools,
verbose: true,
maxIterations: 2,
});
await spanishAgentExecutorWithMaxIterations.invoke({ input: query });
如果在 LangGraph.js 中达到了递归限制,框架将抛出一个特定类型的异常,我们可以像处理 AgentExecutor 一样捕获并管理该异常。
import { GraphRecursionError } from "@langchain/langgraph";
const RECURSION_LIMIT = 2 * 2 + 1;
const appWithBadTools = createReactAgent({ llm, tools: badTools });
try {
await appWithBadTools.invoke(
{
messages: [{ role: "user", content: query }],
},
{
recursionLimit: RECURSION_LIMIT,
}
);
} catch (e) {
if (e instanceof GraphRecursionError) {
console.log("Recursion limit reached.");
} else {
throw e;
}
}
Recursion limit reached.
下一步
你现在已经学会了如何将 LangChain 代理执行器迁移到 LangGraph。
接下来,查看其他 LangGraph 操作指南。