创建和运行 Agent
单独来说,语言模型无法采取行动 - 它们只能输出文本。
LangChain 的一个重要用例是创建代理。
代理是使用 LLM 作为推理引擎的系统,用于确定应采取哪些行动以及这些行动的输入应该是什么。
然后可以将这些行动的结果反馈给代理,并确定是否需要更多行动,或者是否可以结束。
在本次课程中,我们将构建一个可以与多种不同工具进行交互的代理:一个是本地数据库,另一个是搜索引擎。您将能够向该代理提问,观察它调用工具,并与它进行对话。
下面将介绍使用 LangChain 代理进行构建。LangChain 代理适合入门,但在一定程度之后,我们可能希望拥有它们无法提供的灵活性和控制性。要使用更高级的代理,我们建议查看 LangGraph。
一、概念
我们将涵盖的概念包括:
使用语言模型,特别是它们的工具调用能力
创建检索器以向我们的代理公开特定信息
使用搜索工具在线查找信息
聊天历史,允许聊天机器人“记住”过去的交互,并在回答后续问题时考虑它们。
使用LangSmith调试和跟踪您的应用程序
二、安装
要安装 LangChain,请运行:
1 | pip install langchain |
LangSmith
使用 LangChain 构建的许多应用程序将包含多个步骤,其中会多次调用 LLM。
随着这些应用程序变得越来越复杂,能够检查链或代理内部发生了什么变得至关重要。
这样做的最佳方式是使用 LangSmith。
在上面的链接注册后,请确保设置您的环境变量以开始记录跟踪:
1 | export LANGCHAIN_TRACING_V2="true" |
三、定义工具
我们首先需要创建我们想要使用的工具。我们将使用两个工具:Tavily(用于在线搜索),然后是我们将创建的本地索引上的检索器。
1. Tavily
LangChain 中有一个内置工具,可以轻松使用 Tavily 搜索引擎作为工具。
请注意,这需要一个 API 密钥 - 他们有一个免费的层级,但如果您没有或不想创建一个,您可以忽略这一步。
创建 API 密钥后,您需要将其导出为:
1 | export TAVILY_API_KEY="..." |
1 | from langchain_community.tools.tavily_search import TavilySearchResults |
1 | search = TavilySearchResults(max_results=2) |
1 | print(search.invoke("今天上海天气怎么样")) |
1 | [{'url': 'http://sh.cma.gov.cn/sh/tqyb/jrtq/', 'content': '上海今天气温度30℃~38℃,偏南风风力4-5级,有多云和雷阵雨的可能。生活气象指数显示,气温高,人体感觉不舒适,不适宜户外活动。'}] |
2. Retriever
Retriever 是 langchain 库中的一个模块,用于检索工具。检索工具的主要用途是从大型文本集合或知识库中找到相关信息。它们通常用于问答系统、对话代理和其他需要从大量文本数据中提取信息的应用程序。
我们还将在自己的一些数据上创建一个 Retriever。有关每个步骤的更深入解释,请参阅此教程。
1 | #示例:tools_retriever.pyfrom langchain_community.document_loaders import WebBaseLoader |
1 | retriever.invoke("猫的特征")[0] |
1 | page_content='聽覺[编辑] |
现在,我们已经填充了我们将要进行Retriever的索引,我们可以轻松地将其转换为一个工具(代理程序正确使用所需的格式)。
1 | from langchain.tools.retriever import create_retriever_tool |
1 | retriever_tool = create_retriever_tool( |
3. 工具
既然我们都创建好了,我们可以创建一个工具列表,以便在下游使用。
1 | tools = [search, retriever_tool] |
四、使用语言模型
接下来,让我们学习如何使用语言模型来调用工具。LangChain 支持许多可以互换使用的不同语言模型 - 选择您想要使用的语言模型!
1 | from langchain_openai import ChatOpenAI |
您可以通过传入消息列表来调用语言模型。默认情况下,响应是一个 content
字符串。
1 | from langchain_core.messages import HumanMessage |
1 | 'Hello! How can I assist you today?' |
现在,我们可以看看如何使这个模型能够调用工具。为了使其具备这种能力,我们使用 .bind_tools
来让语言模型了解这些工具。
1 | model_with_tools = model.bind_tools(tools) |
现在我们可以调用模型了。让我们首先用一个普通的消息来调用它,看看它的响应。我们可以查看 content
字段和 tool_calls
字段。
1 | response = model_with_tools.invoke([HumanMessage(content="你好")]) |
1 | ContentString: 你好!有什么可以帮助你的吗? |
现在,让我们尝试使用一些期望调用工具的输入来调用它。
1 | response = model_with_tools.invoke([HumanMessage(content="今天上海天气怎么样")]) |
1 | ContentString: |
我们可以看到现在没有内容,但有一个工具调用!它要求我们调用 Tavily Search 工具。
这并不是在调用该工具 - 它只是告诉我们要调用。为了实际调用它,我们将创建我们的代理程序。
五、创建代理程序
既然我们已经定义了工具和 LLM,我们可以创建代理程序。我们将使用一个工具调用代理程序 - 有关此类代理程序以及其他选项的更多信息,请参阅此指南。
我们可以首先选择要用来指导代理程序的提示。
如果您想查看此提示的内容并访问LangSmith,您可以转到:
https://smith.langchain.com/hub/hwchase17/openai-functions-agent
1 | #示例:agent_tools_create.py |
1 | [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
。
1 | from langchain.agents import create_tool_calling_agent |
最后,我们将代理(大脑)与 AgentExecutor 中的工具结合起来(AgentExecutor 将重复调用代理并执行工具)。
1 | from langchain.agents import AgentExecutor |
六、运行代理
现在,我们可以在几个查询上运行代理!请注意,目前这些都是无状态查询(它不会记住先前的交互)。
首先,让我们看看当不需要调用工具时它如何回应:
1 | #示例:agent_tools_run.pyprint(agent_executor.invoke({"input": "你好"})) |
1 | {'input': '你好', 'output': '你好!有什么我可以帮助你的吗?'} |
为了确切了解底层发生了什么(并确保它没有调用工具),我们可以查看 LangSmith 跟踪。
现在让我们尝试一个应该调用检索器的示例:
1 | print(agent_executor.invoke({"input": "猫的特征"})) |
1 | {'input': '猫的特征', 'output': '猫有许多显著的特征,包括以下几点:\n\n**听觉**:猫每只耳朵都有32条独立的肌肉控制耳壳转动。它们可以单独朝向不同的音源转动,使得在向猎物移动时仍能对周围其他音源保持直接接触。猫的听觉比人类和狗更敏锐,能听到更高的频率。\n\n**嗅觉**:家猫的嗅觉比人类灵敏14倍,鼻腔内有2亿个嗅觉受器,数量甚至超过某些品种的狗。\n\n**味觉**:由于早期的演化,猫失去了甜的味觉,但它们能感知酸、苦、咸味,并选择适合自己口味的食物。不过总的来说,猫的味觉并不算完善,相比一般人类平均有9000个味蕾,猫一般平均只有473个味蕾,且不喜欢低于室温的食物。\n\n**触觉**:猫在磨蹭时身上会散发出特别的费洛蒙,当这些独有的费洛蒙留下时,目的就是在宣誓主权,提醒其他猫这是我的。\n\n**被毛**:猫的被毛长度可以根据成为长毛猫,短毛猫和无毛猫。\n\n**视觉**:猫的夜视能力和追踪视觉活动物体相当出色,夜视能力是人类的六倍。猫的眼睛具有微光观察能力,即使只有微弱的月光也能分辨物体。\n\n**骨骼**:猫的骨骼共有230块,其中脊椎骨占了30块。\n\n**爪子**:猫的爪子尖锐且具有伸缩作用,能向外张开或向内收缩藏起来。\n\n**地域性攻击**:猫是很有地域性的动物,会在自己的地盤留下气味,利用下巴、耳朵及尾部的皮脂腺磨蹭物体以标记领地。\n\n**与狗的关系**:一般认为猫和狗互相厌恶,但经过训练和适应,猫和狗可能理解同一种“语言”并和睦相处。\n\n以上是猫的一些主要特征,但每只猫都有其个性和独特之处。'} |
让我们查看 LangSmith 跟踪以确保它实际上在调用该工具。
现在让我们尝试一个需要调用搜索工具的示例:
1 | print(agent_executor.invoke({"input": "今天上海天气怎么样"})) |
1 | {'input': '今天上海天气怎么样', 'output': '很抱歉,我无法获取实时的天气信息。你可以访问上海市气象局的网站这里来查询今天的气象状况、预警信息、生活指数等。同时,该网站还提供了未来几天的天气趋势和空气质量状况,以及气象科普、气象视频、气候变化等其他相关服务和信息。'} |
七、添加记忆
如前所述,此代理是无状态的。这意味着它不会记住先前的交互。要给它记忆,我们需要传递先前的 chat_history
。注意:由于我们使用的提示,它需要被称为 chat_history
。如果我们使用不同的提示,我们可以更改变量名
1 | # 示例:agent_tools_memory.py |
1 | {'input': '你好,我的名字是Cyber', 'chat_history': [], 'output': '你好,Cyber,很高兴见到你!有什么我可以帮助你的吗?'} |
1 | from langchain_core.messages import AIMessage, HumanMessage |
1 | agent_executor.invoke( |
如果我们想要自动跟踪这些消息,我们可以将其包装在一个 RunnableWithMessageHistory 中。
1 | # 示例:agent_tools_memory_store.py |
因为我们有多个输入,我们需要指定两个事项:
input_messages_key
:用于将输入添加到对话历史记录中的键。history_messages_key
:用于将加载的消息添加到其中的键。
1 | agent_with_chat_history = RunnableWithMessageHistory( |
1 | response = agent_with_chat_history.invoke( |
1 | {'input': 'Hi,我的名字是Cyber', 'chat_history': [], 'output': '你好,Cyber!很高兴认识你。有什么我可以帮助你的吗?'} |
1 | response = agent_with_chat_history.invoke( |
1 | {'input': '我叫什么名字?', 'chat_history': [HumanMessage(content='Hi,我的名字是Cyber'), AIMessage(content='你好,Cyber!很高兴认识你。有什么我可以帮助你的吗?')], 'output': '你的名字是Cyber。'} |
LangSmith 示例跟踪:https://smith.langchain.com/public/98c8d162-60ae-4493-aa9f-992d87bd0429/r