在LangChain中构建ReAct智能体:执行器、临时工作区及其局限性
构建一个能够使用工具的ReAct智能体,了解该智能体与AgentExecutor如何分工协作,以及为何LangGraph在处理控制流、循环和状态方面更具优势。
什么是智能体
人工智能智能体是一种能够从人类那里获取高级目标,然后自行规划、决策并采取行动的系统。它会利用外部工具、API或知识源来执行一系列步骤,全程保持上下文信息,跨多个步骤进行推理,当有新信息出现时进行调整,最终朝着既定目标前进。
简而言之,智能体就是大型语言模型加上一些工具。大型语言模型负责推理和决策,而工具则负责实际操作。
智能体的特征
- 目标驱动。你只需说明想要什么,无需指定实现方式。
- 自行规划。智能体无需帮助即可将问题拆解并安排任务顺序。
- 使用工具。它会调用API、计算器、搜索功能等。大型语言模型知道有哪些工具以及何时使用它们。
代码中的实用智能体
下面的脚本构建了一个拥有两种工具的小型智能体:通过DuckDuckGo进行网络搜索以及查询天气信息,然后向它提出一个包含两个部分的问题。天气查询功能需要使用天气服务提供商的API密钥,此处以占位符形式展示。
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
import requests
from langchain_community.tools import DuckDuckGoSearchRun
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub # It is a place from where we can get many type of prompts, so if we want any predefined prompt, we can get it from here
# Step 1: Creating tool definitions
search_tool = DuckDuckGoSearchRun()
@tool
def get_weather_data(city: str) -> str:
"""
This function fetches the current weather data for a given city
"""
url = f'https://api.weatherstack.com/current?access_key=4d1d8ae207a8c845a52df8a67bf3623e&query={city}'
response = requests.get(url)
return response.json()
llm = ChatOpenAI()
# Step 2: Pull the ReAct prompt from LangChain Hub
prompt = hub.pull("hwchase17/react") # pulls the standard ReAct agent prompt
# The agent which we are gonna make is a ReAct agent (reasoning + action = ReAct)
# ReAct is a design pattern
# Step 3: Create the ReAct agent manually with the pulled prompt
agent = create_react_agent(
llm=llm,
tools=[search_tool, get_weather_data],
prompt=prompt
)
# Step 4: Wrap it with AgentExecutor
agent_executor = AgentExecutor(
agent=agent,
tools=[search_tool, get_weather_data],
verbose=True # whatever the agent thinks, it will be visible to us
)
# Step 5: Invoke
response = agent_executor.invoke({"input": "Find the capital of Madhya Pradesh, then find it's current weather condition"})
print(response)
'''
Entering new AgentExecutor chain...
I should first find out the capital of Madhya Pradesh and then check the current weather condition for that city.
Action: duckduckgo_search
Action Input: "capital of Madhya Pradesh" Madhya Pradesh, state of India that is situated in the heart of the country. It has no coastline and no international frontier. Its physiography is characterized by low hills, extensive plateaus, and river valleys. The capital is Bhopal, in the west-central part of the state. Bhopal, city, capital of Madhya Pradesh state, central India. Situated in the fertile plain of the Malwa Plateau, the city lies just north of the Vindhya Range, along the slopes of a sandstone ridge. It is a major rail junction and has an airport. Pop. (2001) 1,437,354; (2011) 1,798,218. Bhopal was Indore (/ ɪ n ˈ d ɔːr / ⓘ; ISO: Indaura, Hindi: [ɪn̪d̪ɔːr]) is the largest and most populous city in the Indian state of Madhya Pradesh. [15] It is the commercial hub of Madhya Pradesh. It is consistently ranked as the cleanest city in India. [16] It serves as the headquarters of both the Indore District and the Indore Division.It is also considered the state education hub and ... In 1956, Bhopal became part of the state of Madhya Pradesh. Bhopal district was carved out on October 2, 1972, and is one of the 45 districts in the state. 4. Why is Bhopal famous? Bhopal is the capital city of the Indian state of Madhya Pradesh. It is known as the City of Lakes due to the presence of various natural and artificial lakes. In 1948, Madhya Bharat was created, with Indore designated as its summer capital and Gwalior as its winter capital. This arrangement highlights Indore's significance even then. However, Madhya Bharat was a temporary entity, and the map of central India was soon to be redrawn. The Creation of Madhya PradeshNow that I know the capital of Madhya Pradesh is Bhopal, I can use the get_weather_data function to check its current weather condition.
Action: get_weather_data
Action Input: Bhopal {'request': {'type': 'City', 'query': 'Bhopal, India', 'language': 'en', 'unit': 'm'}, 'location': {'name': 'Bhopal', 'country': 'India', 'region': 'Madhya Pradesh', 'lat': '23.267', 'lon': '77.400', 'timezone_id': 'Asia/Kolkata', 'localtime': '2025-05-01 17:52', 'localtime_epoch': 1746121920, 'utc_offset': '5.50'}, 'current': {'observation_time': '12:22 PM', 'temperature': 40, 'weather_code': 116, 'weather_icons': ['https://cdn.worldweatheronline.com/images/wsymbols01_png_64/wsymbol_0002_sunny_intervals.png'], 'weather_descriptions': ['Partly Cloudy '], 'astro': {'sunrise': '05:47 AM', 'sunset': '06:48 PM', 'moonrise': '08:38 AM', 'moonset': '11:01 PM', 'moon_phase': 'Waxing Crescent', 'moon_illumination': 15}, 'air_quality': {'co': '510.6', 'no2': '1.665', 'o3': '180', 'so2': '10.175', 'pm2_5': '29.045', 'pm10': '68.635', 'us-epa-index': '2', 'gb-defra-index': '2'}, 'wind_speed': 12, 'wind_degree': 302, 'wind_dir': 'WNW', 'pressure': 1005, 'precip': 0, 'humidity': 7, 'cloudcover': 25, 'feelslike': 40, 'uv_index': 1, 'visibility': 6, 'is_day': 'yes'}}The current weather condition in Bhopal, Madhya Pradesh is partly cloudy with a temperature of 40°C.
Final Answer: The capital of Madhya Pradesh is Bhopal, and the current weather condition in Bhopal is partly cloudy with a temperature of 40°C.
> Finished chain.
{'input': "Find the capital of Madhya Pradesh, then find it's current weather condition", 'output': 'The capital of Madhya Pradesh is Bhopal, and the current weather condition in Bhopal is partly cloudy with a temperature of 40°C.'}
'''
response['output']
# The capital of Madhya Pradesh is Bhopal, and the current weather condition in Bhopal is partly cloudy with a temperature of 40°C.
脚本中的五个编号步骤涵盖了工具定义、从LangChain中心获取标准的ReAct提示词、创建智能体、将其封装在执行器中以及调用该执行器。最后输出的追踪信息显示,智能体决定先搜索首都,然后再查看天气状况。
ReAct
LangChain支持多种智能体设计方法,此处介绍的是ReAct,即“推理加行动”的缩写。它让模型在多个步骤构成的结构化循环中,在内部推理(即思考过程)与外部行动(如调用工具)之间交替进行。
模型不会一次性给出答案,而是逐步思考,决定下一步该做什么,并可能调用API、计算器或网络搜索等工具来辅助决策。
这种模式适用于:
- 需要多步解决的复杂问题;
- 需要借助工具才能完成的任务;
- 需要让推理过程可见且可审计的情况。
例如,可以要求模型给出法国首都的人口数量。模型首先会推断出需要确定首都,然后查找相关信息,发现答案是巴黎;接着又推断出需要巴黎的人口数据,再次搜索后最终给出答案。
ReAct包含五个要素:
- 思考。即智能体的内部推理过程,它会在其中权衡当前状态并规划下一步行动,同时解释为何选择特定工具或还有哪些信息未知。
- 动作。即决定使用工具箱中的某个工具,通过相应的函数或API来调用它。
- 动作输入。即传递给该工具的参数。如果动作是
search,则输入为具体的查询文本,比如“德里的天气”。
驱动此循环的提示词模板如下:
Answer the following questions as best you can. You have access to the following tools:
{tools}
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action …
(this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question: {input}
Thought:{agent_scratchpad} —
在那个模板中,Observation指的仅仅是工具运行后返回的结果。
智能体的临时存储区
该临时存储区充当智能体的短期记忆或工作笔记,用于记录当前任务的历史:之前的思考内容、所采取的行动(如工具调用),以及返回的Observation结果。
通过查看这份笔记,智能体会记住自己已经找出了法国的首都,现在需要查询巴黎的人口数量。这是一个临时存储空间,每次执行器返回新信息时都会更新。一旦最终答案被发送给用户,该临时存储区通常会被清空或归档,因为该任务已完成其功能。
一旦想到大语言模型是无状态的,这个名称就很好理解了。它无法自然地记住代码循环中几秒钟前发生的事情。因此,LangChain会获取当前循环的完整历史记录,每次向智能体询问下一步该做什么时都会将其重新插入提示词中。
智能体与AgentExecutor
这两者承担不同的任务。
智能体是思考者,有点像项目经理。它读取请求,将其分解为更小的步骤,并决定需要使用哪些工具。它的输入包含用户的提问以及迄今为止已完成的所有操作。
AgentExecutor则是执行者,更像助手。它听从智能体的指令,实际运行每个工具对应的代码,然后将结果反馈回来。
另一种理解方式是:代理相当于绘制蓝图的建筑师,而执行者则是负责实际建造的施工队。没有代理就没有计划,没有执行者,计划就永远只能停留在纸上。
执行者负责协调整个流程:
- 它将输入数据及之前的消息发送给代理,以便代理了解问题及已发生的情况。
- 它接收代理反馈的下一步操作指令。
- 它使用这些输入数据运行相应的工具。
- 它将工具的观测结果添加到历史记录中。
- 它将更新后的历史记录连同用户的问题一起反馈给代理,这一过程会不断重复,直到代理给出最终答案。
将查询德里温度的请求乘以10。智能体判断需要使用天气工具,执行器调用该工具,获取观测结果后,智能体决定需要进行乘法运算,执行器执行该运算,最终智能体给出答案。执行器会完成智能体要求的任何操作。
这种方法的局限性
预构建的执行器适用于简单场景,但当智能体处理更复杂的问题时就会暴露出明显缺陷。下文的比较对象是LangGraph,即LangChain的基于图的库。还需注意,较新版本的LangChain更推荐使用LangGraph来构建智能体,因此在基于旧版执行器类进行开发之前,请先查阅最新文档。
1. 黑箱问题
标准的 AgentExecutor 是预先构建好的。只需为其提供工具和提示语,一个看不见的 while 循环就会持续运行直至任务完成。想要干预它的运行十分困难。如果要求智能体在花钱前始终先询问人类,或必须按照先执行步骤A再执行步骤B的固定顺序操作,执行器会予以抵制,因为它被设计为完全自主运行的。
以拥有 search_flights 和 book_flight 工具的智能体为例,在 LangChain 的执行器中,大语言模型会在一个隐藏的循环中决定下一步该做什么。当被要求预订航班时,它可能会直接进行搜索并立即完成预订而无需询问用户,即便用户希望先对任何购买行为进行确认,它也可能选择价格高达一万美元的航班。像强制确认这样的中间步骤无法被严格执行,因为控制流程是隐式的,由模型来掌控。
在 LangGraph 中,工作流被定义为明确的步骤或节点,例如搜索航班、申请审批以及完成预订。这些节点相互连接,因此系统在预订前必须先通过审批流程。这种方式能够实现完全的控制,并便于实施规则、添加人工检查点、重试机制或条件分支。
2. 循环与回路
实际的工作流很少是线性的。它们往往需要回退、修正错误,以及对多组文档进行循环处理。
- LangChain 的链结构主要是以有向无环图的形式设计的,因此数据只能单向流动。
- LangGraph 则专为处理循环而设计,它将流程建模为一张映射图,其中节点代表函数,边则代表节点之间的路径。
在文档问答任务中,LangChain流程可能会检索文档、生成答案并返回结果。如果答案有误或不完整,它不会自动回退去重新检索或优化查询,除非有额外的手动逻辑干预。一旦流程继续前进,就很难再回头进行自我改进。
LangGraph允许你明确地设计循环流程:检索、作答、检查质量,如果结果不佳,则优化查询并再次检索,如此反复直至得到满意结果。该系统可以在节点之间循环,直到满足特定条件,这非常适合需要迭代、修正和反馈的任务。
3. 状态与持久性
这对系统的可扩展性至关重要。
LangChain 的内存主要涉及对话上下文,例如记住之前的聊天内容以便模型给出更好的回答。它与工作流程并无紧密关联,也不会跟踪任务的进展。除非被明确存储,否则系统重启时这些内存通常会丢失,因此长时间运行的任务并不可靠。
LangGraph 将内存视为工作流程的完整状态,而不仅仅是聊天历史。在每一步中,它都会保存已完成的操作、产生的输出以及下一步该做什么。如果流程在中间停止,它可以从停下的地方继续执行。此外,它还支持暂停、在人工干预下查看和编辑状态,甚至返回到之前的步骤,这使得复杂的多步骤任务更加稳健。
想象一下这样一个漫长的任务:需要逐一处理十份文档,但在第7步时程序崩溃了。由于没有内置的持久化功能,所有进度都会丢失,你必须从头开始,而且默认情况下不会留下任何进度记录。而如果在每个节点之后都能保存状态,那么你就可以从第7步重新开始。此外,你还可以暂停任务,让他人查看或修改当前状态后再继续,或者回到之前的某个步骤,这就像是一种时间旅行。这样一来,复杂的流程就能变得更加可靠、可恢复,同时也更易于调试和审计。
关键在于,基本的执行器难以用来构建可扩展、适用于生产环境的智能体,正因如此才有了基于图的工具来处理这类任务。
相关阅读
- 智能体内存快照、技能、衰减与分支作用域 — 为编程智能体设计的嵌入式向量数据库内存层新增的四种功能:可合并的便携式快照、技能激活、带冲突检测的衰减机制,以及基于分支的作用域内存。
- 你的第一个LangGraph智能体:具备工具与路由功能的足球助手 — 逐步指导的LangGraph教程:共享状态、工具、规划节点、条件边以及Markdown格式的总结,所有内容均围绕一个模拟的足球数据助手构建。