6.3.2 创建并使用Agents
为了更好地理解Agents,接下来将构建一个含有两种工具的代理:一种是用于在线搜索信息的工具,另一种是用于检索我们已经加载到索引中的特定数据的工具。
1. 设置LangSmith
在实际应用中,Agents代理在返回面向用户的输出之前,会根据输入自主决定采取一系列步骤。这使得调试这些系统特别棘手,同时使得可观察性变得尤为重要。LangSmith在这类情况下尤其有用,在使用LangChain构建代理时,所有步骤都将自动在LangSmith中进行跟踪。要设置LangSmith,只需要设置以下环境变量即可:
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="你的API密钥"
通过这些设置,LangSmith将帮助开发者监控和审查代理的行为,从而提高系统的可观察性和调试能力,这对于构建可靠和高效的代理系统至关重要。
2. 定义工具
在LangChain中构建Agents代理时,首先需要创建想要使用的工具。在接下来的内容中将使用两个工具:Tavily(用于在线搜索)和检索器。
(1)Tavily
在LangChain中,通过使用其内置工具TavilySearchResults,可以非常轻松的使用Tavily搜索引擎作为工具。TavilySearchResults允许开发者将Tavily搜索引擎集成到他们构建的代理中,以便执行网络搜索任务。下面是在LangChain中使用TavilySearchResults工具的基本步骤:
- API密钥:首先需要准备一个Tavily的API密钥。
- 设置环境变量:一旦获得了API密钥,需要将其导出为环境变量,以便LangChain可以使用它:
export TAVILY_API_KEY="你的Tavily API密钥"
- 导入工具,需要在代码中导入TavilySearchResults工具:
from langchain_community.tools.tavily_search import TavilySearchResults
- 创建工具实例:然后可以创建TavilySearchResults的一个实例:
search = TavilySearchResults()
- 执行搜索:可以使用上述实例执行搜索查询:
search.invoke("你想要搜索的关键词")
执行后会展示查询结果:
[{'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': 1712847697, 'localtime': '2024-04-11 8:01'}, 'current': {'last_updated_epoch': 1712847600, 'last_updated': '2024-04-11 08:00', 'temp_c': 11.1, 'temp_f': 52.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 10, 'wind_dir': 'N', 'pressure_mb': 1015.0, 'pressure_in': 29.98, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 97, 'cloud': 25, 'feelslike_c': 11.5, 'feelslike_f': 52.6, 'vis_km': 14.0, 'vis_miles': 8.0, 'uv': 4.0, 'gust_mph': 2.8, 'gust_kph': 4.4}}"},
{'url': 'https://www.yahoo.com/news/april-11-2024-san-francisco-122026435.html',
'content': "2024 NBA Mock Draft 6.0: Projections for every pick following March Madness With the NCAA tournament behind us, here's an updated look at Yahoo Sports' first- and second-round projections for the ..."},
{'url': 'https://world-weather.info/forecast/usa/san_francisco/april-2024/',
'content': 'Extended weather forecast in San Francisco. Hourly Week 10 days 14 days 30 days Year. Detailed ⚡ San Francisco Weather Forecast for April 2024 - day/night 🌡️ temperatures, precipitations - World-Weather.info.'},
{'url': 'https://www.wunderground.com/hourly/us/ca/san-francisco/94144/date/date/2024-4-11',
'content': 'Personal Weather Station. Inner Richmond (KCASANFR1685) Location: San Francisco, CA. Elevation: 207ft. Nearby Weather Stations. Hourly Forecast for Today, Thursday 04/11Hourly for Today, Thu 04/11 ...'},
{'url': 'https://weatherspark.com/h/y/557/2024/Historical-Weather-during-2024-in-San-Francisco-California-United-States',
'content': 'San Francisco Temperature History 2024\nHourly Temperature in 2024 in San Francisco\nCompare San Francisco to another city:\nCloud Cover in 2024 in San Francisco\nDaily Precipitation in 2024 in San Francisco\nObserved Weather in 2024 in San Francisco\nHours of Daylight and Twilight in 2024 in San Francisco\nSunrise & Sunset with Twilight and Daylight Saving Time in 2024 in San Francisco\nSolar Elevation and Azimuth in 2024 in San Francisco\nMoon Rise, Set & Phases in 2024 in San Francisco\nHumidity Comfort Levels in 2024 in San Francisco\nWind Speed in 2024 in San Francisco\nHourly Wind Speed in 2024 in San Francisco\nHourly Wind Direction in 2024 in San Francisco\nAtmospheric Pressure in 2024 in San Francisco\nData Sources\n See all nearby weather stations\nLatest Report — 3:56 PM\nWed, Jan 24, 2024\xa0\xa0\xa0\xa013 min ago\xa0\xa0\xa0\xa0UTC 23:56\nCall Sign KSFO\nTemp.\n60.1°F\nPrecipitation\nNo Report\nWind\n6.9 mph\nCloud Cover\nMostly Cloudy\n1,800 ft\nRaw: KSFO 242356Z 18006G19KT 10SM FEW015 BKN018 BKN039 16/12 A3004 RMK AO2 SLP171 T01560122 10156 20122 55001\n While having the tremendous advantages of temporal and spatial completeness, these reconstructions: (1) are based on computer models that may have model-based errors, (2) are coarsely sampled on a 50 km grid and are therefore unable to reconstruct the local variations of many microclimates, and (3) have particular difficulty with the weather in some coastal areas, especially small islands.\n We further caution that our travel scores are only as good as the data that underpin them, that weather conditions at any given location and time are unpredictable and variable, and that the definition of the scores reflects a particular set of preferences that may not agree with those of any particular reader.\n 2024 Weather History in San Francisco California, United States\nThe data for this report comes from the San Francisco International Airport.'}]
TavilySearchResults封装了与Tavily搜索引擎的交互,使得在LangChain代理中集成在线搜索功能变得更加简单和直接。通过这种方式,代理可以利用网络搜索来获取信息,并将这些信息用于决策或回答用户的查询。
(2)检索器
在LangChain中,创建检索器(Retriever)是一个重要的步骤,它允许代理从自有数据中检索特定信息。创建检索器的基本步骤如下所示:
- 导入必要的库:首先,需要从LangChain社区库中导入文档加载器、向量存储、嵌入生成器和文本分割器。
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
- 加载文档:使用WebBaseLoader加载指定URL的文档内容。
loader = WebBaseLoader("https://docs.smith.langchain.com/overview")
docs = loader.load()
- 文本分割:为了处理长文档,使用RecursiveCharacterTextSplitter将文档分割成较小的块,同时保留一定的重叠部分以维持上下文。
documents = RecursiveCharacterTextSplitter(
chunk_size=1000, chunk_overlap=200
).split_documents(docs)
- 创建向量存储:利用FAISS(一种高效的相似性搜索和密集向量聚类库)和OpenAI生成的嵌入,从文档中创建向量存储。
vector = FAISS.from_documents(documents, OpenAIEmbeddings())
- 生成检索器:从向量存储创建检索器,该检索器能够根据查询返回相关的文档。
retriever = vector.as_retriever()
- 执行检索:使用检索器执行一个简单的查询,以验证其功能。
retriever.invoke("how to upload a dataset")[0]
执行后会返回检索结果:
{
"Document": {
"page_content": "import Clientfrom langsmith.evaluation import evaluateclient = Client()# Define dataset: these are your test casesdataset_name = 'Sample Dataset'dataset = client.create_dataset(dataset_name, description='A sample dataset in LangSmith.')client.create_examples( inputs=[ {'postfix': 'to LangSmith'}, {'postfix': 'to Evaluations in LangSmith'}, ], outputs=[ {'output': 'Welcome to LangSmith'}, {'output': 'Welcome to Evaluations in LangSmith'}, ], dataset_id=dataset.id,)# Define your evaluatordef exact_match(run, example): return {'score': run.outputs['output'] == example.outputs['output']}experiment_results = evaluate( lambda input: 'Welcome ' + input['postfix'], # Your AI system goes here data=dataset_name, # 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':",
"metadata": {
"source": "https://docs.smith.langchain.com/overview",
"title": "Getting started with LangSmith | 🦜️🛠️ 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]
通过上述步骤创建了两个工具:一个用于在线搜索,另一个用于从我们的本地数据检索信息。这使得代理能够更智能地处理信息检索任务,提高了代理的功能性和效率。
3. 创建代理
现在已经定义了工具,接下来可以创建一个代理。请看下面的实现步骤,将创建一个OpenAI Functions类型的代理。
(1)首先,需要选择一个引导代理行为的LLM,此处使用ChatOpenAI。
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
在上述代码中,参数model指定了使用的OpenAI模型,而参数temperature控制了生成响应的随机性。
(2)选择引导代理的提示(prompt):接下来,需要选择或创建一个提示来引导代理的行为。、
#如果获取要使用的提示,可以修改此处
prompt = hub.pull("hwchase17/openai-functions-agent")
prompt.messages
上述代码从LangChain的Hub中拉取了一个预定义的提示模板。
(3)初始化代理:现在,可以使用选定的LLM、提示和工具来初始化代理,代理负责接收输入并决定采取什么行动。比较重要的是,代理本身不执行这些行动(这一点将由下一步的AgentExecutor来完成)。
from langchain.agents import create_tool_calling_agent
agent = create_tool_calling_agent(llm, tools, prompt)
在上述代码中,函数create_tool_calling_agent创建了一个代理,将根据接收到的输入和提示来调用相应的工具。
(4)结合代理与AgentExecutor:最后,将代理(大脑)与AgentExecutor(将重复调用代理并执行工具)内的工具结合起来。
from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
在上述代码中,AgentExecutor负责根据代理的决策来执行具体的工具调用,并且可以设置为详细模式(verbose=True),以便在执行过程中提供更多的输出信息。
通过上述步骤,构建了一个完整的代理系统,它结合了先进的语言模型、定制的提示和有用的工具,以实现复杂的任务自动化。
4. 运行代理
接下来将介绍使用LangChain中的代理来执行实际查询的过程,并介绍了如何通过AgentExecutor来运行和观察代理的行为的用法。通过这种方式,代理可以被用来自动化各种基于信息检索和数据处理的任务。
(1)现在可以在前面创建的几个查询的基础上运行代理,例如下面的查询:
agent_executor.invoke({"input": "hi!"})
请注意,目前这些查询都是无状态的(即代理不会记住之前的交互),上述查询将触发代理的响应,例如:
> Entering new AgentExecutor chain...
Hello! How can I assist you today?
> Finished chain.
{'input': 'hi!', 'output': 'Hello! How can I assist you today?'}
(2)查询LangSmith的帮助:可以使用代理解答有关LangSmith如何帮助测试的问题。
agent_executor.invoke({"input": "how can langsmith help with testing?"})
上述代理将调用langsmith_search工具,并返回有关LangSmith如何辅助测试的详细信息。
(3)检索天气信息:还可以使用代理检索关于特定地点(如旧金山)的天气信息。
agent_executor.invoke({"input": "whats the weather in sf?"})
执行上述代码,代理将调用tavily_search_results_json工具来搜索并返回旧金山的天气情况,在通过代理获取的输出结果中可能包括当前天气状况、温度、风速和湿度等信息,如:
{'input': 'whats the weather in sf?', 'output': 'The current weather in San Francisco is partly cloudy with a temperature of 52.0°F (11.1°C). The wind speed is 3.6 kph coming from the north, and the humidity is at 97%.'}
(4)代理执行器(AgentExecutor):AgentExecutor是执行代理决策并运行工具的组件,它将重复调用代理并执行所需的工具,同时提供了详细的执行过程输出。
5. 添加记忆功能
到目前为止,前面创建的代理是无状态的,这意味着它不会记住之前的交互,在接下来将介绍为代理添加记忆功能的方法。通过添加记忆功能,代理能够根据之前的交互来提供更加个性化和连贯的回答,这对于构建能够进行复杂对话和提供个性化服务的智能代理系统是非常重要的。
(1)为了让代理具有记忆功能,需要在每次调用时传入先前的chat_history(对话历史)。例如在下面的代码中传入了一个空的消息列表作为chat_history。
agent_executor.invoke({"input": "hi! my name is bob", "chat_history": []})
这意味着这是与代理的第一次交互,所以没有先前的对话消息。尽管是空的,这仍然为将来可能的交互建立了一个框架,使得代理能够开始跟踪消息历史。例如下面可能是此时代理的响应例子:
> Entering new AgentExecutor chain...
Hello Bob! How can I assist you today?
> Finished chain.
{'input': 'hi! my name is bob',
'chat_history': [],
'output': 'Hello Bob! How can I assist you today?'}
在后续的交互中,chat_history 将包含之前交换的消息,这样代理就可以根据之前的对话内容来提供更加相关和连贯的响应。这种设计允许代理随着时间的推移而“记忆”用户的交互,从而模拟更自然和个性化的对话体验。
(2)使用消息类:为了在后续的交互中使用对话历史,需要从langchain_core.message中导入类AIMessage和类HumanMessage。
from langchain_core.messages import AIMessage, HumanMessage
(3)调用带有对话历史的代理:接下来传递包含之前对话消息的列表和新的输入给代理。
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?",
}
)
下面是此时上述代理的响应例子:
> Entering new AgentExecutor chain...
Your name is Bob. How can I assist you, Bob?
> Finished chain.
{'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, Bob?'}
(4)自动跟踪消息:如果想要自动跟踪这些消息,可以将代理包装在一个RunnableWithMessageHistory对象中。
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
message_history = ChatMessageHistory()
agent_with_chat_history = RunnableWithMessageHistory(
agent_executor,
lambda session_id: message_history,
input_messages_key="input",
history_messages_key="chat_history",
)
(5)使用包装后的代理:现在我们可以调用agent_with_chat_history,它将自动包含对话历史。
agent_with_chat_history.invoke(
{"input": "hi! I'm bob"},
config={"configurable": {"session_id": "<foo>"}}
)
下面是此时上述代理的响应例子:
> Entering new AgentExecutor chain...
Hello Bob! How can I assist you today?
> Finished chain.
{'input': "hi! I'm bob",
'chat_history': [],
'output': 'Hello Bob! How can I assist you today?'}
(6)再次使用一个具有记忆功能的代理来处理查询。
agent_with_chat_history.invoke(
{"input": "what's my name?"},
# 这是因为在大多数真实场景中,需要一个会话ID
# 在这里并没有真正使用它,因为我们使用的是一个简单的、在内存中的ChatMessageHistory
config={"configurable": {"session_id": "<foo>"}},
)
上面代码中的注释部分解释了为什么在调用invoke方法时传递了一个config参数,其中包含了一个session_id。下面是此时上述代理的响应例子:
> Entering new AgentExecutor chain...
Your name is Bob! How can I help you, Bob?
> Finished chain.
{'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! How can I help you, Bob?'}
在实际应用中,会话ID通常用于跟踪和管理特定的用户会话,但在当前的示例中,由于使用了简单的、在内存中的ChatMessageHistory,会话ID并没有被实际使用。<foo>是一个占位符,表示在实际使用时应该替换为一个有效的会话ID。