跳到主要内容

如何使用遗留 LangChain 代理 (AgentExecutor)

先决条件

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

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

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

信息

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

概念

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

设置

Jupyter Notebook

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

本教程和其他教程最方便地运行在 Jupyter 笔记本中。有关如何安装的说明,请查看此处

安装

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

yarn add langchain 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`... 1347 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 } }
}
}

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

import { createRetrieverTool } from "langchain/tools/retriever";

const retrieverTool = await createRetrieverTool(retriever, {
name: "langsmith_search",
description:
"Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
});

工具

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

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 字符串。

import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({ model: "gpt-4", temperature: 0 });

import { HumanMessage } from "@langchain/core/messages";

const response = await model.invoke([new HumanMessage("hi!")]);

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

现在我们可以看看让此模型能够进行工具调用是怎样的。为了启用该功能,我们使用 .bind 将这些工具的知识传授给语言模型。

const modelWithTools = model.bindTools(tools);

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

const response = await modelWithTools.invoke([new HumanMessage("Hi!")]);

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

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

const response = await modelWithTools.invoke([
new HumanMessage("What's the weather in SF?"),
]);

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

我们可以看到现在没有内容,但有一个工具调用!它希望我们调用 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
}
},
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
},
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
}
},
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
},
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、提示和工具来初始化代理。代理负责接收输入并决定采取什么行动。至关重要的是,代理不执行这些操作 - 这些操作由 AgentExecutor(下一步)执行。有关如何理解这些组件的更多信息,请参阅我们的 概念指南

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

import { createToolCallingAgent } from "langchain/agents";

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

最后,我们将代理(大脑)与 AgentExecutor 中的工具结合起来(AgentExecutor 会重复调用代理并执行工具)。

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 be a valuable tool for testing in several ways:\n" +
"\n" +
"1. **Logging Traces**: LangSmith prov"... 960 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, California is partly cloudy with a temperature of 12.2°C (54.0"... 176 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?"
}
import { AIMessage, HumanMessage } from "@langchain/core/messages";

await agentExecutor.invoke({
chat_history: [
new HumanMessage("hi! my name is bob"),
new AIMessage("Hello Bob! How can I assist you today?"),
],
input: "what's my name?",
});
{
chat_history: [
HumanMessage {
lc_serializable: true,
lc_kwargs: {
content: "hi! my name is bob",
additional_kwargs: {},
response_metadata: {}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "hi! my name is bob",
name: undefined,
additional_kwargs: {},
response_metadata: {}
},
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: "Hello Bob! How can I assist you today?",
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: {},
response_metadata: {}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "Hello Bob! How can I assist you today?",
name: undefined,
additional_kwargs: {},
response_metadata: {},
tool_calls: [],
invalid_tool_calls: []
}
],
input: "what's my name?",
output: "Your name is Bob. How can I assist you further?"
}

如果我们希望自动跟踪这些消息,我们可以将其包装在 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 {
lc_serializable: true,
lc_kwargs: {
content: "hi! I'm bob",
additional_kwargs: {},
response_metadata: {}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "hi! I'm bob",
name: undefined,
additional_kwargs: {},
response_metadata: {}
},
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: "Hello, Bob! How can I assist you today?",
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: {},
response_metadata: {}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "Hello, Bob! How can I assist you today?",
name: undefined,
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 {
lc_serializable: true,
lc_kwargs: {
content: "hi! I'm bob",
additional_kwargs: {},
response_metadata: {}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "hi! I'm bob",
name: undefined,
additional_kwargs: {},
response_metadata: {}
},
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: "Hello, Bob! How can I assist you today?",
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: {},
response_metadata: {}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "Hello, Bob! How can I assist you today?",
name: undefined,
additional_kwargs: {},
response_metadata: {},
tool_calls: [],
invalid_tool_calls: []
},
HumanMessage {
lc_serializable: true,
lc_kwargs: {
content: "what's my name?",
additional_kwargs: {},
response_metadata: {}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "what's my name?",
name: undefined,
additional_kwargs: {},
response_metadata: {}
},
AIMessage {
lc_serializable: true,
lc_kwargs: {
content: "Your name is Bob. How can I assist you further?",
tool_calls: [],
invalid_tool_calls: [],
additional_kwargs: {},
response_metadata: {}
},
lc_namespace: [ "langchain_core", "messages" ],
content: "Your name is Bob. How can I assist you further?",
name: undefined,
additional_kwargs: {},
response_metadata: {},
tool_calls: [],
invalid_tool_calls: []
}
],
output: "Your name is Bob. How can I assist you further?"
}

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

下一步

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

信息

本节介绍了使用 LangChain 代理构建。LangChain 代理适合入门,但达到一定程度后,您可能需要它们不提供的灵活性,以及控制能力。对于使用更高级的代理,我们建议您查看 LangGraph

您还可以查看 本指南以帮助迁移到 LangGraph


此页面是否有帮助?


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