跳至主要内容

如何使用旧版 LangChain 代理 (AgentExecutor)

先决条件

本指南假设您熟悉以下概念

语言模型本身无法执行操作 - 它们只会输出文本。代理是使用 LLM 作为推理引擎来确定要执行哪些操作以及这些操作的输入应该是什么的系统。然后,这些操作的结果可以反馈到代理中,并确定是否需要更多操作,或者是否可以完成。

在本教程中,我们将构建一个可以与多种不同工具交互的代理:一个工具是本地数据库,另一个工具是搜索引擎。您将能够向该代理提问、观察它调用工具以及与它进行对话。

信息

本节将介绍使用 LangChain 代理进行构建。LangChain 代理非常适合入门,但在达到一定程度后,您可能需要它们无法提供的灵活性。为了使用更高级的代理,我们建议您查看 LangGraph

概念

我们将介绍的概念是: - 使用 语言模型,尤其是它们的工具调用功能 - 创建一个 检索器 以将特定信息暴露给我们的代理 - 使用搜索 工具 在线查找内容 - 聊天历史记录,它允许聊天机器人“记住”过去的交互并在响应后续问题时将其考虑在内。 - 使用 LangSmith 调试和跟踪您的应用程序

设置

Jupyter 笔记本

本指南(以及文档中的大多数其他指南)使用 Jupyter 笔记本 并假设读者也是如此。Jupyter 笔记本非常适合学习如何使用 LLM 系统,因为很多时候事情可能会出错(意外输出、API 停机等),在交互式环境中完成指南是更好地理解它们的绝佳方式。

本指南和其他教程最好在 Jupyter 笔记本中运行。请参见 此处 以获取有关如何安装的说明。

安装

要安装 LangChain(以及用于 Web 加载器的 cheerio),请运行

yarn add langchain @langchain/core cheerio

有关更多详细信息,请参见我们的 安装指南

LangSmith

您使用 LangChain 构建的许多应用程序将包含多个步骤,并多次调用 LLM。随着这些应用程序变得越来越复杂,能够检查链或代理内部到底发生了什么变得至关重要。最有效的方法是使用 LangSmith

在您通过上面的链接注册后,请确保设置环境变量以开始记录跟踪

export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="..."

# Reduce tracing latency if you are not in a serverless environment
# export LANGCHAIN_CALLBACKS_BACKGROUND=true

定义工具

我们首先需要创建要使用的工具。我们将使用两个工具:Tavily(用于在线搜索),然后是我们在本地索引上创建的检索器

Tavily

LangChain 内置了一个工具,可以轻松使用 Tavily 搜索引擎作为工具。请注意,这需要一个 API 密钥 - 他们有一个免费层,但如果你没有或者不想创建一个,你可以随时忽略此步骤。

创建 API 密钥后,你需要将其导出为

export TAVILY_API_KEY="..."
import "cheerio"; // This is required in notebooks to use the `CheerioWebBaseLoader`
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";

const search = new TavilySearchResults({
maxResults: 2,
});

await search.invoke("what is the weather in SF");
`[{"title":"Weather in San Francisco","url":"https://www.weatherapi.com/","content":"{'location': {'n`... 1358 more characters

检索器

我们还将在我们自己的某些数据上创建检索器。有关此处每个步骤的更深入解释,请参见 本教程

import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { OpenAIEmbeddings } from "@langchain/openai";
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";

const loader = new CheerioWebBaseLoader(
"https://docs.smith.langchain.com/overview"
);
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
const documents = await splitter.splitDocuments(docs);
const vectorStore = await MemoryVectorStore.fromDocuments(
documents,
new OpenAIEmbeddings()
);
const retriever = vectorStore.asRetriever();

(await retriever.invoke("how to upload a dataset"))[0];
Document {
pageContent: 'description="A sample dataset in LangSmith.")client.create_examples( inputs=[ {"postfix": '... 891 more characters,
metadata: {
source: "https://docs.smith.langchain.com/overview",
loc: { lines: { from: 4, to: 4 } }
},
id: undefined
}

现在我们已经填充了我们将进行检索的索引,我们可以轻松地将其转换为工具(代理正确使用它所需的格式)

import { z } from "zod";
import { tool } from "@langchain/core/tools";

const retrieverTool = tool(
async ({ input }, config) => {
const docs = await retriever.invoke(input, config);
return docs.map((doc) => doc.pageContent).join("\n\n");
},
{
name: "langsmith_search",
description:
"Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
schema: z.object({
input: z.string(),
}),
}
);

工具

现在我们已经创建了这两个,我们可以创建一个工具列表,我们将在下游使用它。

const tools = [search, retrieverTool];

使用语言模型

接下来,让我们学习如何使用语言模型通过调用工具。LangChain 支持许多不同的语言模型,你可以互换使用 - 选择你想要使用的模型!

选择你的聊天模型

安装依赖项

yarn add @langchain/openai 

添加环境变量

OPENAI_API_KEY=your-api-key

实例化模型

import { ChatOpenAI } from "@langchain/openai";

const model = new ChatOpenAI({ model: "gpt-4" });

你可以通过传递一个消息列表来调用语言模型。默认情况下,响应是一个content 字符串。

const response = await model.invoke([
{
role: "user",
content: "hi!",
},
]);

response.content;
"Hello! How can I assist you today?"

现在我们可以看看启用此模型进行工具调用是什么样的。为了启用它,我们使用.bind 来让语言模型了解这些工具

const modelWithTools = model.bindTools(tools);

现在我们可以调用模型了。让我们首先用一条普通消息调用它,看看它如何响应。我们可以查看content 字段和tool_calls 字段。

const responseWithTools = await modelWithTools.invoke([
{
role: "user",
content: "Hi!",
},
]);

console.log(`Content: ${responseWithTools.content}`);
console.log(`Tool calls: ${responseWithTools.tool_calls}`);
Content: Hello! How can I assist you today?
Tool calls:

现在,让我们尝试用一些需要调用工具的输入来调用它。

const responseWithToolCalls = await modelWithTools.invoke([
{
role: "user",
content: "What's the weather in SF?",
},
]);

console.log(`Content: ${responseWithToolCalls.content}`);
console.log(
`Tool calls: ${JSON.stringify(responseWithToolCalls.tool_calls, null, 2)}`
);
Content:
Tool calls: [
{
"name": "tavily_search_results_json",
"args": {
"input": "current weather in San Francisco"
},
"type": "tool_call",
"id": "call_gtJ5rrjXswO8EIvePrxyGQbR"
}
]

我们可以看到现在没有内容,但有一个工具调用!它希望我们调用 Tavily 搜索工具。

这还没有调用该工具 - 它只是告诉我们这样做。为了真正调用它,我们需要创建我们的代理。

创建代理

现在我们已经定义了工具和 LLM,我们可以创建代理。我们将使用工具调用代理 - 有关此类型代理以及其他选项的更多信息,请参见 本指南

我们可以首先选择我们想要使用的提示来指导代理

import { ChatPromptTemplate } from "@langchain/core/prompts";

const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant"],
["placeholder", "{chat_history}"],
["human", "{input}"],
["placeholder", "{agent_scratchpad}"],
]);

console.log(prompt.promptMessages);
[
SystemMessagePromptTemplate {
lc_serializable: true,
lc_kwargs: {
prompt: PromptTemplate {
lc_serializable: true,
lc_kwargs: {
inputVariables: [],
templateFormat: "f-string",
template: "You are a helpful assistant"
},
lc_runnable: true,
name: undefined,
lc_namespace: [ "langchain_core", "prompts", "prompt" ],
inputVariables: [],
outputParser: undefined,
partialVariables: undefined,
templateFormat: "f-string",
template: "You are a helpful assistant",
validateTemplate: true,
additionalContentFields: undefined
}
},
lc_runnable: true,
name: undefined,
lc_namespace: [ "langchain_core", "prompts", "chat" ],
inputVariables: [],
additionalOptions: {},
prompt: PromptTemplate {
lc_serializable: true,
lc_kwargs: {
inputVariables: [],
templateFormat: "f-string",
template: "You are a helpful assistant"
},
lc_runnable: true,
name: undefined,
lc_namespace: [ "langchain_core", "prompts", "prompt" ],
inputVariables: [],
outputParser: undefined,
partialVariables: undefined,
templateFormat: "f-string",
template: "You are a helpful assistant",
validateTemplate: true,
additionalContentFields: undefined
},
messageClass: undefined,
chatMessageClass: undefined
},
MessagesPlaceholder {
lc_serializable: true,
lc_kwargs: { variableName: "chat_history", optional: true },
lc_runnable: true,
name: undefined,
lc_namespace: [ "langchain_core", "prompts", "chat" ],
variableName: "chat_history",
optional: true
},
HumanMessagePromptTemplate {
lc_serializable: true,
lc_kwargs: {
prompt: PromptTemplate {
lc_serializable: true,
lc_kwargs: {
inputVariables: [Array],
templateFormat: "f-string",
template: "{input}"
},
lc_runnable: true,
name: undefined,
lc_namespace: [ "langchain_core", "prompts", "prompt" ],
inputVariables: [ "input" ],
outputParser: undefined,
partialVariables: undefined,
templateFormat: "f-string",
template: "{input}",
validateTemplate: true,
additionalContentFields: undefined
}
},
lc_runnable: true,
name: undefined,
lc_namespace: [ "langchain_core", "prompts", "chat" ],
inputVariables: [ "input" ],
additionalOptions: {},
prompt: PromptTemplate {
lc_serializable: true,
lc_kwargs: {
inputVariables: [ "input" ],
templateFormat: "f-string",
template: "{input}"
},
lc_runnable: true,
name: undefined,
lc_namespace: [ "langchain_core", "prompts", "prompt" ],
inputVariables: [ "input" ],
outputParser: undefined,
partialVariables: undefined,
templateFormat: "f-string",
template: "{input}",
validateTemplate: true,
additionalContentFields: undefined
},
messageClass: undefined,
chatMessageClass: undefined
},
MessagesPlaceholder {
lc_serializable: true,
lc_kwargs: { variableName: "agent_scratchpad", optional: true },
lc_runnable: true,
name: undefined,
lc_namespace: [ "langchain_core", "prompts", "chat" ],
variableName: "agent_scratchpad",
optional: true
}
]

现在,我们可以使用 LLM、提示和工具初始化代理。代理负责接收输入并决定采取哪些操作。至关重要的是,代理不会执行这些操作 - 这是由代理执行器(下一步)完成的。有关如何考虑这些组件的更多信息,请参见我们的 概念指南

请注意,我们传递的是model,而不是modelWithTools。这是因为createToolCallingAgent 会在幕后为我们调用.bind

import { createToolCallingAgent } from "langchain/agents";

const agent = await createToolCallingAgent({ llm: model, tools, prompt });

最后,我们将代理(大脑)与代理执行器内的工具结合起来(它将反复调用代理并执行工具)。

import { AgentExecutor } from "langchain/agents";

const agentExecutor = new AgentExecutor({
agent,
tools,
});

运行代理

现在我们可以对一些查询运行代理了!请注意,目前,这些都是**无状态**查询(它不会记住之前的交互)。

首先,让我们看看当不需要调用工具时它如何响应

await agentExecutor.invoke({ input: "hi!" });
{ input: "hi!", output: "Hello! How can I assist you today?" }

为了确切地了解幕后发生了什么(并确保它没有调用工具),我们可以查看 LangSmith 跟踪

现在让我们在一个它应该调用检索器的示例中尝试一下

await agentExecutor.invoke({ input: "how can langsmith help with testing?" });
{
input: "how can langsmith help with testing?",
output: "LangSmith can assist with testing in several ways, particularly for applications built using large l"... 1474 more characters
}

让我们查看 LangSmith 跟踪 以确保它确实调用了它。

现在让我们尝试一个需要调用搜索工具的示例

await agentExecutor.invoke({ input: "whats the weather in sf?" });
{
input: "whats the weather in sf?",
output: "The current weather in San Francisco is as follows:\n" +
"\n" +
"- **Temperature**: 15.6°C (60.1°F)\n" +
"- **Conditio"... 303 more characters
}

我们可以查看 LangSmith 跟踪 以确保它有效地调用了搜索工具。

添加内存

如前所述,此代理是无状态的。这意味着它不会记住之前的交互。要赋予它记忆,我们需要传入之前的chat_history

**注意**:输入变量需要称为chat_history,因为我们使用的是提示。如果我们使用不同的提示,我们可以更改变量名称。

// Here we pass in an empty list of messages for chat_history because it is the first message in the chat
await agentExecutor.invoke({ input: "hi! my name is bob", chat_history: [] });
{
input: "hi! my name is bob",
chat_history: [],
output: "Hello Bob! How can I assist you today?"
}
await agentExecutor.invoke({
chat_history: [
{ role: "user", content: "hi! my name is bob" },
{ role: "assistant", content: "Hello Bob! How can I assist you today?" },
],
input: "what's my name?",
});
{
chat_history: [
{ role: "user", content: "hi! my name is bob" },
{
role: "assistant",
content: "Hello Bob! How can I assist you today?"
}
],
input: "what's my name?",
output: "Your name is Bob. How can I help you today, Bob?"
}

如果我们想自动跟踪这些消息,我们可以将其包装在 RunnableWithMessageHistory 中。

由于我们有多个输入,我们需要指定两件事

  • inputMessagesKey:用于添加到对话历史记录的输入键。
  • historyMessagesKey:将加载的消息添加到其中的键。

有关如何使用它的更多信息,请参见 本指南

import { ChatMessageHistory } from "@langchain/community/stores/message/in_memory";
import { BaseChatMessageHistory } from "@langchain/core/chat_history";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";

const store = {};

function getMessageHistory(sessionId: string): BaseChatMessageHistory {
if (!(sessionId in store)) {
store[sessionId] = new ChatMessageHistory();
}
return store[sessionId];
}

const agentWithChatHistory = new RunnableWithMessageHistory({
runnable: agentExecutor,
getMessageHistory,
inputMessagesKey: "input",
historyMessagesKey: "chat_history",
});

await agentWithChatHistory.invoke(
{ input: "hi! I'm bob" },
{ configurable: { sessionId: "<foo>" } }
);
{
input: "hi! I'm bob",
chat_history: [
HumanMessage {
"content": "hi! I'm bob",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"content": "Hello Bob! How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
],
output: "Hello Bob! How can I assist you today?"
}
await agentWithChatHistory.invoke(
{ input: "what's my name?" },
{ configurable: { sessionId: "<foo>" } }
);
{
input: "what's my name?",
chat_history: [
HumanMessage {
"content": "hi! I'm bob",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"content": "Hello Bob! How can I assist you today?",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
},
HumanMessage {
"content": "what's my name?",
"additional_kwargs": {},
"response_metadata": {}
},
AIMessage {
"content": "Your name is Bob! How can I help you today, Bob?",
"additional_kwargs": {},
"response_metadata": {},
"tool_calls": [],
"invalid_tool_calls": []
}
],
output: "Your name is Bob! How can I help you today, Bob?"
}

LangSmith 跟踪示例:https://smith.langchain.com/public/98c8d162-60ae-4493-aa9f-992d87bd0429/r

下一步

就这样!在本快速入门中,我们介绍了如何创建一个简单的代理。代理是一个复杂的主题,还有很多东西要学!

信息

本节介绍了使用 LangChain 代理进行构建。LangChain 代理非常适合入门,但超过一定程度后,你可能需要它们无法提供的灵活性或控制。对于使用更高级的代理,我们建议查看 LangGraph

你也可以查看 本指南以帮助迁移到 LangGraph


本页内容是否有用?


你也可以在 GitHub 上留下详细的反馈 GitHub.