Skip to main content
Open In ColabOpen on GitHub

使用 AgentExecutor(旧版)构建代理

重要

本节将介绍使用旧版 LangChain AgentExecutor 进行开发。这些工具非常适合入门,但超过一定阶段后,您可能需要它们无法提供的灵活性和控制力。对于更高级的代理开发,我们建议查看 LangGraph 代理迁移指南

仅靠语言模型本身无法采取行动——它们只会输出文本。 LangChain的一个重要用例是创建代理。 代理是利用大型语言模型作为推理引擎,以决定应采取哪些操作以及这些操作的输入应该是什么的系统。 这些操作的结果可以被反馈给代理,然后由代理判断是否需要进一步操作,或者是否可以结束。

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

概念

我们将涵盖的概念有:

  • 使用 语言模型,特别是它们的工具调用能力
  • 创建一个 检索器,以向我们的代理暴露特定信息
  • 使用搜索 工具 在线查找信息
  • Chat History,这使得聊天机器人能够“记住”之前的互动,并在回答后续问题时加以考虑。
  • 使用 LangSmith 调试和追踪您的应用程序

设置

Jupyter Notebook

本指南(以及文档中的大多数其他指南)使用 Jupyter 笔记本,并假设读者也熟悉该工具。Jupyter 笔记本非常适合学习如何使用大语言模型系统,因为很多时候会出现问题(如输出意外、API 服务中断等),在交互式环境中逐步跟随指南操作,是更好地理解这些内容的绝佳方式。

其他教程可能最方便在 Jupyter Notebook 中运行。有关安装说明,请参见 此处

安装

要安装 LangChain,请运行:

pip install langchain

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

LangSmith

使用 LangChain 构建的许多应用程序都包含多个步骤,以及多次调用大型语言模型(LLM)。 随着这些应用程序变得越来越复杂,能够检查链或代理内部的具体情况变得至关重要。 实现这一点的最佳方式是使用 LangSmith

在您通过上方链接注册后,请确保设置您的环境变量以开始记录追踪信息:

export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY="..."

或者,如果在笔记本中,你可以通过以下方式设置它们:

import getpass
import os

os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = getpass.getpass()

定义工具

我们首先需要创建想要使用的工具。我们将使用两个工具:<a t="C0">Tavily</a>(用于在线搜索)以及一个我们自己创建的本地索引检索器

Tavily

LangChain 内置了一个工具,可轻松将 Tavily 搜索引擎用作工具。 请注意,这需要一个 API 密钥——他们提供免费层级,但如果你没有或不想创建一个,也可以随时跳过此步骤。

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

export TAVILY_API_KEY="..."
from langchain_community.tools.tavily_search import TavilySearchResults
API 参考:TavilySearchResults
search = TavilySearchResults(max_results=2)
search.invoke("what is the weather in SF")
[{'url': 'https://www.weatherapi.com/',
'content': "{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.78, 'lon': -122.42, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1714000492, 'localtime': '2024-04-24 16:14'}, 'current': {'last_updated_epoch': 1713999600, 'last_updated': '2024-04-24 16:00', 'temp_c': 15.6, 'temp_f': 60.1, 'is_day': 1, 'condition': {'text': 'Overcast', 'icon': '//cdn.weatherapi.com/weather/64x64/day/122.png', 'code': 1009}, 'wind_mph': 10.5, 'wind_kph': 16.9, 'wind_degree': 330, 'wind_dir': 'NNW', 'pressure_mb': 1018.0, 'pressure_in': 30.06, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 72, 'cloud': 100, 'feelslike_c': 15.6, 'feelslike_f': 60.1, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 5.0, 'gust_mph': 14.8, 'gust_kph': 23.8}}"},
{'url': 'https://www.weathertab.com/en/c/e/04/united-states/california/san-francisco/',
'content': 'San Francisco Weather Forecast for Apr 2024 - Risk of Rain Graph. Rain Risk Graph: Monthly Overview. Bar heights indicate rain risk percentages. Yellow bars mark low-risk days, while black and grey bars signal higher risks. Grey-yellow bars act as buffers, advising to keep at least one day clear from the riskier grey and black days, guiding ...'}]

检索器

我们还将基于我们自己的数据创建一个检索器。有关此处每个步骤的更详细解释,请参阅 本教程

from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
docs = loader.load()
documents = RecursiveCharacterTextSplitter(
chunk_size=1000, chunk_overlap=200
).split_documents(docs)
vector = FAISS.from_documents(documents, OpenAIEmbeddings())
retriever = vector.as_retriever()
retriever.invoke("how to upload a dataset")[0]
Document(page_content='# The data to predict and grade over    evaluators=[exact_match], # The evaluators to score the results    experiment_prefix="sample-experiment", # The name of the experiment    metadata={      "version": "1.0.0",      "revision_id": "beta"    },)import { Client, Run, Example } from \'langsmith\';import { runOnDataset } from \'langchain/smith\';import { EvaluationResult } from \'langsmith/evaluation\';const client = new Client();// Define dataset: these are your test casesconst datasetName = "Sample Dataset";const dataset = await client.createDataset(datasetName, {    description: "A sample dataset in LangSmith."});await client.createExamples({    inputs: [        { postfix: "to LangSmith" },        { postfix: "to Evaluations in LangSmith" },    ],    outputs: [        { output: "Welcome to LangSmith" },        { output: "Welcome to Evaluations in LangSmith" },    ],    datasetId: dataset.id,});// Define your evaluatorconst exactMatch = async ({ run, example }: { run: Run; example?:', metadata={'source': 'https://docs.smith.langchain.com/overview', 'title': 'Getting started with LangSmith | \uf8ffü¶úÔ∏è\uf8ffüõ†Ô∏è LangSmith', 'description': 'Introduction', 'language': 'en'})

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

from langchain.tools.retriever import create_retriever_tool
retriever_tool = create_retriever_tool(
retriever,
"langsmith_search",
"Search for information about LangSmith. For any questions about LangSmith, you must use this tool!",
)

工具

现在我们已经创建了两者,接下来可以创建一个后续将使用的工具列表。

tools = [search, retriever_tool]

使用语言模型

接下来,让我们学习如何通过调用工具来使用语言模型。LangChain 支持多种不同的语言模型,您可以随意切换使用——请在下面选择您想要使用的模型!

pip install -qU "langchain[openai]"
import getpass
import os

if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter API key for OpenAI: ")

from langchain.chat_models import init_chat_model

model = init_chat_model("gpt-4", model_provider="openai")

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

from langchain_core.messages import HumanMessage

response = model.invoke([HumanMessage(content="hi!")])
response.content
API 参考:HumanMessage
'Hello! How can I assist you today?'

现在我们可以看到启用此模型进行工具调用的效果。为了实现这一点,我们使用 .bind_tools 来让语言模型了解这些工具

model_with_tools = model.bind_tools(tools)

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

response = model_with_tools.invoke([HumanMessage(content="Hi!")])

print(f"ContentString: {response.content}")
print(f"ToolCalls: {response.tool_calls}")
ContentString: Hello! How can I assist you today?
ToolCalls: []

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

response = model_with_tools.invoke([HumanMessage(content="What's the weather in SF?")])

print(f"ContentString: {response.content}")
print(f"ToolCalls: {response.tool_calls}")
ContentString: 
ToolCalls: [{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_4HteVahXkRAkWjp6dGXryKZX'}]

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

这还只是告诉我们该调用那个工具,并没有真正调用。为了真正调用它,我们需要创建一个代理。

创建代理

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

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

如果你想查看此提示的内容并且拥有 LangSmith 的访问权限,可以前往:

https://smith.langchain.com/hub/hwchase17/openai-functions-agent

from langchain import hub

# Get the prompt to use - you can modify this!
prompt = hub.pull("hwchase17/openai-functions-agent")
prompt.messages
API 参考:Hub
[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[], template='You are a helpful assistant')),
MessagesPlaceholder(variable_name='chat_history', optional=True),
HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['input'], template='{input}')),
MessagesPlaceholder(variable_name='agent_scratchpad')]

现在,我们可以使用LLM、提示词和工具来初始化代理。代理负责接收输入并决定采取什么行动。关键的是,代理本身并不执行这些操作——这是由AgentExecutor(下一步)完成的。有关如何思考这些组件的更多信息,请参阅我们的 概念指南

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

from langchain.agents import create_tool_calling_agent

agent = create_tool_calling_agent(model, tools, prompt)

最后,我们将代理(大脑)与 AgentExecutor 中的工具结合在一起(该组件将反复调用代理并执行工具)。

from langchain.agents import AgentExecutor

agent_executor = AgentExecutor(agent=agent, tools=tools)
API 参考:AgentExecutor

运行代理

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

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

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

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

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

agent_executor.invoke({"input": "how can langsmith help with testing?"})
{'input': 'how can langsmith help with testing?',
'output': 'LangSmith is a platform that aids in building production-grade Language Learning Model (LLM) applications. It can assist with testing in several ways:\n\n1. **Monitoring and Evaluation**: LangSmith allows close monitoring and evaluation of your application. This helps you to ensure the quality of your application and deploy it with confidence.\n\n2. **Tracing**: LangSmith has tracing capabilities that can be beneficial for debugging and understanding the behavior of your application.\n\n3. **Evaluation Capabilities**: LangSmith has built-in tools for evaluating the performance of your LLM. \n\n4. **Prompt Hub**: This is a prompt management tool built into LangSmith that can help in testing different prompts and their responses.\n\nPlease note that to use LangSmith, you would need to install it and create an API key. The platform offers Python and Typescript SDKs for utilization. It works independently and does not require the use of LangChain.'}

让我们来看看 LangSmith 跟踪,以确保它确实调用了该功能。

现在我们来尝试一个需要调用搜索工具的例子:

agent_executor.invoke({"input": "whats the weather in sf?"})
{'input': 'whats the weather in sf?',
'output': 'The current weather in San Francisco is partly cloudy with a temperature of 16.1°C (61.0°F). The wind is coming from the WNW at a speed of 10.5 mph. The humidity is at 67%. [source](https://www.weatherapi.com/)'}

我们可以查看 LangSmith trace,以确保它能有效调用搜索工具。

添加内存

如前所述,此代理是无状态的。这意味着它不会记住之前的交互。为了赋予它记忆功能,我们需要传入之前交互的 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
agent_executor.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?'}
from langchain_core.messages import AIMessage, HumanMessage
API 参考:AIMessage | HumanMessage
agent_executor.invoke(
{
"chat_history": [
HumanMessage(content="hi! my name is bob"),
AIMessage(content="Hello Bob! How can I assist you today?"),
],
"input": "what's my name?",
}
)
{'chat_history': [HumanMessage(content='hi! my name is bob'),
AIMessage(content='Hello Bob! How can I assist you today?')],
'input': "what's my name?",
'output': 'Your name is Bob. How can I assist you further?'}

如果我们希望自动跟踪这些消息,可以将此内容包装在 RunnableWithMessageHistory 中。有关如何使用此功能的更多信息,请参阅 此指南

from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

store = {}


def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]

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

  • input_messages_key: 用于添加到对话历史记录的输入键。
  • history_messages_key: 用于添加已加载消息的密钥。
agent_with_chat_history = RunnableWithMessageHistory(
agent_executor,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history",
)
agent_with_chat_history.invoke(
{"input": "hi! I'm bob"},
config={"configurable": {"session_id": "<foo>"}},
)
{'input': "hi! I'm bob",
'chat_history': [],
'output': 'Hello Bob! How can I assist you today?'}
agent_with_chat_history.invoke(
{"input": "what's my name?"},
config={"configurable": {"session_id": "<foo>"}},
)
{'input': "what's my name?",
'chat_history': [HumanMessage(content="hi! I'm bob"),
AIMessage(content='Hello Bob! How can I assist you today?')],
'output': 'Your name is Bob.'}

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

结论

结束了!在本快速入门中,我们介绍了如何创建一个简单的智能体。智能体是一个复杂的话题,还有许多内容需要学习!

重要

本节介绍了使用LangChain Agent进行开发。它们非常适合入门,但超过某个阶段后,您可能需要灵活性和控制力,而这些是它们无法提供的。要开发更高级的Agent,我们建议查看 LangGraph

如果你想继续使用 LangChain 代理,一些优秀的进阶指南包括: