[LangChain]Build an Agent

0 Useful Docs

1 概述

语言模型本身无法执行操作——它们仅能输出文本。LangChain 的一个重要使用场景是创建agents。Agents是利用语言模型作为推理引擎来决定需要采取哪些行动及执行这些行动所需的输入的系统。在执行了某些操作之后,其结果可以反馈给语言模型,以判断是否需要进一步的操作,或者是否可以结束任务。这通常通过工具调用来实现。

2 Setup

1
%pip install -U langchain-community langgraph langchain-anthropic tavily-python langgraph-checkpoint-sqlite

Tavily

我们将使用Tavily(a search engine),需要申请API key

1
2
3
4
import getpass
import os

os.environ["TAVILY_API_KEY"] = getpass.getpass()

3 Define tools

我们首先需要创建想要使用的工具。这里主要选择Tavily——一个搜索引擎。在LangChain中,有一个内置的工具可以方便地将Tavily搜索引擎作为工具使用。

1
2
3
4
5
6
7
8
from langchain_community.tools.tavily_search import TavilySearchResults

search = TavilySearchResults(max_results=2)
search_results = search.invoke("what is the weather in beijing")
print(search_results)
# If we want, we can create other tools.
# Once we have all the tools we want, we can put them in a list that we will reference later.
tools = [search]

API参考:TavilySearchResults

4 Using Language Models

接下来介绍如何启用模型进行工具调用。为此,我们使用.bind_tools方法将工具提供给语言模型,使其能够识别这些工具。

1
2
3
4
5
6
model_with_tools = model.bind_tools(tools)

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

print(f"内容字符串: {response.content}")
print(f"工具调用: {response.tool_calls}")

通过上述代码,我们首先将工具绑定到语言模型上,创建了一个具备工具调用能力的新模型实例model_with_tools。然后,我们使用这个新模型实例来响应一条普通的消息”Hi!”,并打印出响应的内容字段以及任何可能产生的工具调用信息。这种方式使得模型不仅可以处理用户的输入消息,还能够根据需要调用相应的工具来完成特定任务。

1
2
3
4
response = model_with_tools.invoke([HumanMessage(content="What's the weather in Beijing?")])

print(f"ContentString: {response.content}")
print(f"ToolCalls: {response.tool_calls}")

这一步实际上还没有调用该工具——它只是告诉我们需要这样做。为了实际调用这个工具,我们将需要创建一个代理(agent)来执行这一操作。

5 Create the agent

我们已经定义了工具和语言模型(LLM),我们可以据此来创建一个agent。我们将使用LangGraph来构建这个代理。当前,我们采用高级接口来构建代理,但LangGraph的优点在于这一高级接口背后有一个底层的高度可控API,可以修改agent的逻辑。

请注意,这里传递的是model而非model_with_tools。原因在于create_react_agent方法会在内部自动调用.bind_tools来绑定工具。

1
2
3
4
from langgraph.prebuilt import create_react_agent

# 初始化代理,传入模型和工具集
agent_executor = create_react_agent(model, tools)

API参考:create_react_agent

6 Run the agent

我们现在可以运行agent以处理一些查询了,请注意,目前这些查询都是无状态的(即它不会记住之前的交互)。值得注意的是,agent在交互结束时会返回最终状态(包括所有输入)。

首先,让我们看看当无需调用任何工具时,agent是如何响应的:

1
2
response = agent_executor.invoke({"messages": [HumanMessage(content="hi!")]})
response["messages"]
1
2
[HumanMessage(content='hi!', additional_kwargs={}, response_metadata={}, id='491de594-753d-41ae-b27b-938f07dbd922'),
AIMessage(content='Hello! How can I assist you today?', additional_kwargs={}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'stop', 'request_id': '60bfce1c-75b7-9441-930a-80bb80494344', 'token_usage': {'input_tokens': 207, 'output_tokens': 9, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 216}}, id='run-5fc64c04-b535-45f2-8b7c-0ad928501045-0')]

接下来,我们尝试一个需要调用外部工具的查询示例:

1
2
3
4
response = agent_executor.invoke(
{"messages": [HumanMessage(content="whats the weather in Beijing?")]}
)
response["messages"]
1
2
3
4
[HumanMessage(content='whats the weather in Beijing?', additional_kwargs={}, response_metadata={}, id='2c31c734-c1c6-4014-a569-c3a3cebc06da'),
AIMessage(content='', additional_kwargs={'tool_calls': [{'function': {'name': 'tavily_search_results_json', 'arguments': '{"query": "weather in Beijing"}'}, 'index': 0, 'id': 'call_ea3d6f7e7b2b4c468e08db', 'type': 'function'}]}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'tool_calls', 'request_id': '4cb2e32c-cbb0-9dab-8b96-6e8c8557101a', 'token_usage': {'input_tokens': 212, 'output_tokens': 22, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 234}}, id='run-ff14cb98-e3af-4d58-b414-b24db3e131d9-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'weather in Beijing'}, 'id': 'call_ea3d6f7e7b2b4c468e08db', 'type': 'tool_call'}]),
ToolMessage(content='[{"title": "Beijing weather in March 2025 ⋆ Beijing temperature in March ...", "url": "https://www.meteoprog.com/weather/Beijing/month/march/", "content": "Beijing (China) weather in March 2025 ☀️ Accurate weather forecast for Beijing in ... 30 Mar. +18°+7°. 31 Mar. +18°+6°. 1 Apr. +17°+5°. 2 Apr. +18°+5°. 3 Apr. +19°", "score": 0.92588276}, {"title": "Weather in Beijing", "url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'Beijing\', \'region\': \'Beijing\', \'country\': \'China\', \'lat\': 39.9289, \'lon\': 116.3883, \'tz_id\': \'Asia/Shanghai\', \'localtime_epoch\': 1743323294, \'localtime\': \'2025-03-30 16:28\'}, \'current\': {\'last_updated_epoch\': 1743322500, \'last_updated\': \'2025-03-30 16:15\', \'temp_c\': 15.0, \'temp_f\': 59.0, \'is_day\': 1, \'condition\': {\'text\': \'Sunny\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/113.png\', \'code\': 1000}, \'wind_mph\': 8.7, \'wind_kph\': 14.0, \'wind_degree\': 188, \'wind_dir\': \'S\', \'pressure_mb\': 1022.0, \'pressure_in\': 30.18, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 12, \'cloud\': 0, \'feelslike_c\': 14.2, \'feelslike_f\': 57.5, \'windchill_c\': 13.1, \'windchill_f\': 55.5, \'heatindex_c\': 14.1, \'heatindex_f\': 57.4, \'dewpoint_c\': -14.4, \'dewpoint_f\': 6.1, \'vis_km\': 10.0, \'vis_miles\': 6.0, \'uv\': 1.2, \'gust_mph\': 10.0, \'gust_kph\': 16.2}}", "score": 0.9137338}]', name='tavily_search_results_json', id='3ae1be75-a463-4e07-8d2d-d77353953703', tool_call_id='call_ea3d6f7e7b2b4c468e08db', artifact={'query': 'weather in Beijing', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'url': 'https://www.meteoprog.com/weather/Beijing/month/march/', 'title': 'Beijing weather in March 2025 ⋆ Beijing temperature in March ...', 'content': 'Beijing (China) weather in March 2025 ☀️ Accurate weather forecast for Beijing in ... 30 Mar. +18°+7°. 31 Mar. +18°+6°. 1 Apr. +17°+5°. 2 Apr. +18°+5°. 3 Apr. +19°', 'score': 0.92588276, 'raw_content': None}, {'title': 'Weather in Beijing', 'url': 'https://www.weatherapi.com/', 'content': "{'location': {'name': 'Beijing', 'region': 'Beijing', 'country': 'China', 'lat': 39.9289, 'lon': 116.3883, 'tz_id': 'Asia/Shanghai', 'localtime_epoch': 1743323294, 'localtime': '2025-03-30 16:28'}, 'current': {'last_updated_epoch': 1743322500, 'last_updated': '2025-03-30 16:15', 'temp_c': 15.0, 'temp_f': 59.0, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 8.7, 'wind_kph': 14.0, 'wind_degree': 188, 'wind_dir': 'S', 'pressure_mb': 1022.0, 'pressure_in': 30.18, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 12, 'cloud': 0, 'feelslike_c': 14.2, 'feelslike_f': 57.5, 'windchill_c': 13.1, 'windchill_f': 55.5, 'heatindex_c': 14.1, 'heatindex_f': 57.4, 'dewpoint_c': -14.4, 'dewpoint_f': 6.1, 'vis_km': 10.0, 'vis_miles': 6.0, 'uv': 1.2, 'gust_mph': 10.0, 'gust_kph': 16.2}}", 'score': 0.9137338, 'raw_content': None}], 'response_time': 2.25}),
AIMessage(content='The current weather in Beijing, China is as follows:\n\n- Temperature: 15.0°C (59.0°F)\n- Condition: Sunny\n- Wind: 14.0 kph (8.7 mph) from the South\n- Pressure: 1022.0 mb (30.18 in)\n- Humidity: 12%\n- Feels like: 14.2°C (57.5°F)\n\n![](//cdn.weatherapi.com/weather/64x64/day/113.png)\n\nThis information was last updated on March 30, 2025, at 16:15 local time.', additional_kwargs={}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'stop', 'request_id': '3bb7c52a-37a1-9027-8c4b-f84e53ff7c42', 'token_usage': {'input_tokens': 868, 'output_tokens': 146, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 1014}}, id='run-f8728282-0494-4ee8-96c2-c338b16504fe-0')]

7 Streaming Messages

当代理执行包含多个步骤的任务时,整个过程可能会花费一些时间。为了能够实时显示中间进度,我们可以使用流模式将消息即时返回。

下面是一个展示如何使用stream方法来实现实时反馈的例子:

1
2
3
4
5
for step in agent_executor.stream(
{"messages": [HumanMessage(content="whats the weather in Beijing?")]},
stream_mode="values",
):
step["messages"][-1].pretty_print()

通过设置stream_mode"values",我们可以逐条获取并打印出处理流程中的最新消息,从而让用户清楚地了解到任务处理的进展状态。这种方法特别适用于那些需要较长时间完成的复杂任务,增强了用户体验的透明度和互动性。

8 Streaming tokens

除了流式返回messages外,流式返回tokens也非常有用。这可以通过指定stream_mode="messages"来实现。

修改后的示例展示了如何使用这种方法:

1
2
3
4
5
6
for step, metadata in agent_executor.stream(
{"messages": [HumanMessage(content="whats the weather in Beijing?")]},
stream_mode="messages",
):
if metadata["langgraph_node"] == "agent" and (text := step.text()):
print(text, end="|")

9 Adding in memory

如前所述,此agent是无状态的,这意味着它不会记住之前的交互。为了赋予其记忆功能,我们需要传入一个检查点器(checkpointer)。当传入检查点器时,在调用agent时还需要传入一个thread_id(以便识别要从中恢复的线程/对话)。

1
2
3
from langgraph.checkpoint.memory import MemorySaver

memory = MemorySaver()

API参考:MemorySaver

1
2
3
agent_executor = create_react_agent(model, tools, checkpointer=memory)

config = {"configurable": {"thread_id": "abc123"}}
1
2
3
4
5
6
7
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="hi im bob!")]}, config
):
print(chunk)
print("----")


1
2
{'agent': {'messages': [AIMessage(content="Hello Bob! It's nice to meet you. How can I assist you today?", additional_kwargs={}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'stop', 'request_id': 'e4bb52a1-dace-9ba3-9616-415b74f7a642', 'token_usage': {'input_tokens': 209, 'output_tokens': 17, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 226}}, id='run-d9607f3f-9acf-400b-8ce6-111669896d2f-0')]}}
----
1
2
3
4
5
for chunk in agent_executor.stream(
{"messages": [HumanMessage(content="whats my name?")]}, config
):
print(chunk)
print("----")
1
2
{'agent': {'messages': [AIMessage(content="Your name is Bob! You just told me that a moment ago. Is there anything else you'd like to know or discuss?", additional_kwargs={}, response_metadata={'model_name': 'qwen-max', 'finish_reason': 'stop', 'request_id': '9d4641e4-97b8-9c56-8aa3-bd2403de8c65', 'token_usage': {'input_tokens': 241, 'output_tokens': 26, 'prompt_tokens_details': {'cached_tokens': 0}, 'total_tokens': 267}}, id='run-28866282-08bf-4f49-a8e5-ce69d813baad-0')]}}
----

[LangChain]Build an Agent
https://erlsrnby04.github.io/2025/03/30/LangChain-Build-an-Agent/
作者
ErlsrnBy04
发布于
2025年3月30日
许可协议