使用 AgentExecutor 构建智能体(遗留版本)
本节将介绍使用传统的 LangChain AgentExecutor 进行开发。这些工具适合入门,但在达到一定阶段后,您可能会需要它们无法提供的灵活性和控制力。对于处理更高级的代理(Agents),我们建议您查看 LangGraph Agents 或 迁移指南。
仅凭自身,语言模型无法采取行动——它们只会输出文本。 LangChain 的一个重要应用场景是创建智能体(agents)。 智能体是一种系统,它利用大语言模型(LLM)作为推理引擎,以决定应采取哪些行动以及这些行动的输入应为何。随后,这些行动的结果可以被反馈回智能体,由它判断是否需要采取更多行动,或者是否可以结束任务。
在本教程中,我们将构建一个能够与多种不同工具交互的智能体:一个是本地数据库,另一个是搜索引擎。您将能够向该智能体提问、观察其调用工具的过程,并与之进行对话。
概念
我们将涵盖的概念包括:
- 使用 语言模型,特别是它们的工具调用能力
- 创建一个 检索器 以向我们的代理暴露特定信息
- 使用搜索 工具 在线查找信息
Chat History,它允许聊天机器人“记住”过去的交互,并在回应后续问题时将其考虑在内。- 使用 LangSmith 调试和追踪您的应用程序
设置
Jupyter Notebook
本指南(以及文档中的大多数其他指南)使用 Jupyter notebooks,并假设读者也是如此。Jupyter notebooks 非常适合学习如何与 LLM 系统协作,因为很多时候可能会出现问题(输出意外、API 不可用等),而在交互式环境中逐步完成指南是更好地理解它们的绝佳方式。
此教程及其他教程最方便在 Jupyter notebook 中运行。有关如何安装的说明,请参见 此处。
安装
安装 LangChain 请运行:
- Pip
- Conda
pip install langchain
conda install langchain -c conda-forge
有关更多详细信息,请参阅我们的 安装指南。
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()
定义工具
我们首先需要创建要使用的工具。我们将使用两个工具:Tavily(用于在线搜索)以及一个针对我们将创建的本地索引的检索器。
Tavily
LangChain 内置了一个工具,可让您轻松使用 Tavily 搜索引擎。 请注意,这需要 API 密钥——Tavily 提供免费层级,但如果您没有或不想创建密钥,也可以随时忽略此步骤。
一旦您创建了 API 密钥,您需要将其导出为:
export TAVILY_API_KEY="..."
from langchain_community.tools.tavily_search import 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
'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 Search 工具。
这还没有调用该工具——它只是告诉我们去调用。为了真正调用它,我们需要创建我们的代理。
创建智能体
现在我们已经定义了工具和大型语言模型(LLM),我们可以创建智能体。我们将使用工具调用型智能体——有关此类智能体以及其他选项的更多信息,请参阅此指南。
我们可以首先选择要使用的提示,以指导智能体。
如果您想查看此提示的内容并访问 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
[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)
运行智能体
我们现在可以在一些查询上运行代理了!请注意,目前这些都是无状态查询(它不会记住之前的交互)。
首先,让我们看看它在无需调用工具时如何响应:
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 追踪,以确认其是否有效地调用了搜索工具。
在内存中添加
如前所述,该智能体是无状态的。这意味着它不会记住之前的交互。为了赋予它记忆,我们需要传入之前的 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
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 Agents 进行开发。它们适合入门,但在达到一定阶段后,您可能会需要它们无法提供的灵活性和控制权。要开发更高级的 Agents,我们建议您查看 LangGraph。
如果您想继续使用 LangChain 智能体,以下是一些优秀的进阶指南: