基于LangChain构建ReAct Agent:让AI学会"思考-行动-观察"
基于LangChain构建ReAct Agent:让AI学会"思考-行动-观察"
从原理到实战,手把手教你构建一个能自主推理和行动的AI智能体
一、什么是ReAct Agent?
1.1 从一个问题说起
想象一下,你让AI帮你查"北京今天天气怎么样,适合穿什么衣服"。传统的大模型会怎么做?它可能会根据训练记忆给出一个大概的回答——但这种回答可能是过时的、不准确的。
如果换一种方式:让AI先思考需要查天气,然后行动——调用天气API获取实时数据,再观察结果,根据温度数据进一步思考穿衣建议,最后给出回答。这就是ReAct(Reasoning + Acting)模式的核心思想。
ReAct Agent的工作方式其实挺像人类解决问题的过程。它不是一次性把整个流程都规划好,而是在有一个整体目标的前提下,走一步看一步:先观察当前情况,思考下一步该做什么,执行动作,再观察结果,根据结果决定下一步。这个过程会一直循环,直到任务完成。
1.2 传统方式 vs ReAct方式
| 传统编程/提示词 | ReAct Agent | |
|---|---|---|
| 决策方式 | 预先定义好所有步骤 | 每步根据观察动态决策 |
| 灵活性 | 遇到意外情况容易卡住 | 能根据中间结果调整策略 |
| 可解释性 | 黑盒 | 思考过程可见 |
| 适用场景 | 流程固定的任务 | 复杂、不确定的任务 |
打个比方,传统方式就像你出门前把整个路线都查好,然后严格按照路线走。而ReAct更像是你出门后,走一段路,看看周围环境,再决定下一步往哪走。
1.3 ReAct的核心循环:思考→行动→观察
ReAct Agent的核心是一个不断循环的过程:
┌─────────────────────────────────────────────────────┐
│ │
│ 思考 (Thought) ──→ 行动 (Action) ──→ 观察 (Observation)
│ ↑ │ │
│ └──────────────────────────────┘ │
│ │
│ 直到获得最终答案 │
└─────────────────────────────────────────────────────┘
具体来说:
- Thought(思考):LLM根据当前上下文,推理下一步该做什么
- Action(行动):调用一个工具(如搜索、计算器、API请求)
- Observation(观察):获得工具执行的结果
- 循环:根据观察结果,决定是否继续或给出最终答案
二、环境准备
2.1 安装依赖
# 创建虚拟环境(可选)
python -m venv react_env
source react_env/bin/activate # Linux/Mac
# react_env\Scripts\activate # Windows
# 安装核心依赖
pip install langchain langchain-openai langchain-community
pip install python-dotenv
pip install duckduckgo-search # 用于网络搜索工具
pip install tavily-python # 备选:更专业的搜索API
2.2 配置API Key
创建一个 .env 文件:
OPENAI_API_KEY=your_openai_api_key_here
# 如果使用Tavily搜索
TAVILY_API_KEY=your_tavily_api_key_here
三、从零构建ReAct Agent
3.1 定义工具(Tools)
工具是Agent的"手脚",用来执行具体的操作。我们以计算器和文本长度统计两个简单工具为例:
from langchain.tools import tool
import os
from dotenv import load_dotenv
load_dotenv()
# ---------- 工具1:计算器 ----------
@tool
def calculator(expression: str) -> str:
"""
计算数学表达式的结果。
Args:
expression: 要计算的数学表达式,如 "2 + 3 * 4"
"""
try:
# 安全起见,只允许基本运算
allowed_chars = set("0123456789+-*/(). ")
if not all(c in allowed_chars for c in expression):
return "错误:表达式包含非法字符"
result = eval(expression)
return f"计算结果:{result}"
except Exception as e:
return f"计算错误:{str(e)}"
# ---------- 工具2:文本长度统计 ----------
@tool
def get_text_length(text: str) -> str:
"""
统计文本的字符长度(不含空格)。
Args:
text: 需要统计长度的文本
"""
length = len(text.replace(" ", ""))
return f"文本长度(不含空格):{length} 个字符"
# ---------- 工具3:获取当前时间 ----------
from datetime import datetime
@tool
def get_current_time() -> str:
"""获取当前的日期和时间。"""
now = datetime.now()
return f"当前时间:{now.strftime('%Y年%m月%d日 %H:%M:%S')}"
💡 新手说明:@tool 是LangChain提供的装饰器,它能把一个普通函数变成Agent可调用的工具。函数名和文档字符串会被LLM读取,用来决定何时调用这个工具。
3.2 构建ReAct提示模板
ReAct Agent需要一个特定的提示模板,告诉LLM如何按照"思考→行动→观察"的格式输出:
from langchain.prompts import PromptTemplate
react_prompt_template = """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}"""
prompt = PromptTemplate.from_template(react_prompt_template)
这个模板是ReAct Agent的核心。它通过Few-shot示例的方式,告诉LLM应该如何组织自己的"思考过程":先想(Thought),再做(Action),然后看结果(Observation),循环直到得出最终答案。
3.3 创建Agent
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_react_agent
# 1. 初始化LLM
llm = ChatOpenAI(
model="gpt-4o-mini", # 也可以使用 gpt-3.5-turbo
temperature=0, # 温度设为0让输出更确定
)
# 2. 收集工具
tools = [calculator, get_text_length, get_current_time]
# 3. 创建ReAct Agent
agent = create_react_agent(
llm=llm,
tools=tools,
prompt=prompt,
)
# 4. 创建Agent执行器
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 打印详细的思考过程
max_iterations=5, # 防止无限循环
handle_parsing_errors=True,
)
💡 新手说明:
create_react_agent是LangChain提供的ReAct Agent工厂函数AgentExecutor负责运行Agent,管理循环逻辑verbose=True会打印出Agent每一步的"思考"过程,方便调试和理解max_iterations是安全阀,防止Agent陷入死循环
3.4 运行Agent
# ---------- 测试:多步推理 ----------
def run_agent(query: str):
print("\n" + "="*60)
print(f"用户问题: {query}")
print("="*60)
response = agent_executor.invoke({"input": query})
print(f"\n最终回答: {response['output']}")
# 测试1:简单计算
run_agent("请计算 25 * 17 等于多少?")
# 测试2:需要组合工具的任务
run_agent("请计算 2024 年 7 月 16 日距离今天有多少天?提示:先获取当前时间。")
# 测试3:文本分析
run_agent("请告诉我 'Hello ReAct Agent' 这个字符串有多少个字符(不含空格)?")
3.5 输出示例
运行后,你会看到类似这样的输出:
============================================================
用户问题: 请计算 25 * 17 等于多少?
============================================================
> Entering new AgentExecutor chain...
Thought: 我需要使用计算器工具来计算25乘以17。
Action: calculator
Action Input: 25 * 17
Observation: 计算结果:425
Thought: 我已经得到了计算结果,可以给出最终答案了。
Final Answer: 25 * 17 等于 425。
> Finished chain.
最终回答: 25 * 17 等于 425。
这就是ReAct Agent的精髓:它把推理过程(Thought)和行动(Action)暴露出来了,让我们能清楚地看到"AI是怎么想的"。
四、进阶:带搜索能力的Agent
让Agent具备网络搜索能力,才能真正发挥ReAct的价值。
4.1 添加搜索工具
from langchain_community.tools import DuckDuckGoSearchRun
# 使用DuckDuckGo搜索(免费,无需API Key)
search = DuckDuckGoSearchRun()
@tool
def web_search(query: str) -> str:
"""
在互联网上搜索信息。
Args:
query: 搜索关键词
"""
try:
results = search.invoke(query)
# 截断过长的结果
if len(results) > 2000:
results = results[:2000] + "...(结果已截断)"
return results
except Exception as e:
return f"搜索失败:{str(e)}"
4.2 构建多工具Agent
# 扩展工具列表
advanced_tools = [
calculator,
get_text_length,
get_current_time,
web_search, # 新增搜索能力
]
# 创建新的Agent
advanced_agent = create_react_agent(
llm=llm,
tools=advanced_tools,
prompt=prompt,
)
advanced_executor = AgentExecutor(
agent=advanced_agent,
tools=advanced_tools,
verbose=True,
max_iterations=10, # 搜索需要更多步骤
handle_parsing_errors=True,
)
# 测试复杂问题
run_agent("2024年巴黎奥运会的开幕日期是哪天?中国代表团获得了多少枚金牌?")
4.3 完整的Agent执行流程
下面是一个完整的ReAct Agent执行轨迹示例,它展示了多轮思考-行动-观察的循环:
用户问题: 2024年巴黎奥运会的开幕日期是哪天?中国代表团获得了多少枚金牌?
> Entering new AgentExecutor chain...
Thought: 我需要先查询巴黎奥运会的开幕日期,然后再查询中国代表团的奖牌数。我应该使用搜索工具。
Action: web_search
Action Input: 2024年巴黎奥运会开幕日期
Observation: 2024年巴黎奥运会于2024年7月26日开幕,8月11日闭幕...
(搜索结果内容)
Thought: 现在我有了开幕日期,还需要查询中国代表团的奖牌信息。
Action: web_search
Action Input: 2024年巴黎奥运会中国代表团金牌数
Observation: 中国代表团在2024年巴黎奥运会上获得40枚金牌、27枚银牌、24枚铜牌...
(搜索结果内容)
Thought: 我已经收集到了所有需要的信息,可以给出完整答案了。
Final Answer: 2024年巴黎奥运会于2024年7月26日开幕,中国代表团共获得40枚金牌。
> Finished chain.
五、常见问题与最佳实践
5.1 防止无限循环
ReAct Agent可能会陷入"思考→行动→观察→再思考→再行动..."的死循环。解决办法:
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
max_iterations=5, # 限制最大迭代次数
early_stopping_method="generate", # 达到最大迭代时强制生成答案
)
5.2 工具描述要清晰
工具的描述(docstring)非常重要,LLM就是靠它来判断"什么时候该用哪个工具"。描述要包含:
- 工具的用途
- 输入参数的含义
- 输出格式
@tool
def fetch_weather(city: str) -> str:
"""
获取指定城市的实时天气信息。
使用场景:当用户询问天气情况时调用此工具。
Args:
city: 城市名称,如 "北京"、"上海"
"""
# 实现...
5.3 处理工具调用失败
工具可能因为各种原因失败(API超时、网络错误、参数错误等),Agent需要能优雅处理:
@tool
def risky_api_call(endpoint: str) -> str:
"""调用外部API,可能失败。"""
try:
response = requests.get(endpoint, timeout=5)
return response.text
except requests.Timeout:
return "错误:API请求超时,请稍后重试"
except Exception as e:
return f"错误:{str(e)}"
5.4 ReAct Agent vs Tool Calling Agent
LangChain支持两种主要的Agent模式:
| ReAct Agent | Tool Calling Agent | |
|---|---|---|
| 原理 | 通过Prompt模板引导LLM输出指定格式 | 利用LLM原生的Function Calling能力 |
| 兼容性 | 兼容几乎所有LLM | 仅支持支持Function Calling的模型 |
| 可解释性 | 思考过程完整可见 | 思考过程不直接暴露 |
| 稳定性 | 依赖LLM对格式的理解,有时会出错 | 更稳定,由模型原生支持 |
推荐:如果使用GPT-4、Claude等支持Function Calling的模型,优先使用Tool Calling Agent;如果使用开源模型或不支持Function Calling的模型,ReAct Agent是更好的选择。
六、总结
核心要点
-
ReAct的核心:思考(Reasoning)和行动(Acting)交替进行,形成"思考→行动→观察→再思考"的循环
-
三大组件:
- LLM:负责推理和决策的"大脑"
- 工具(Tools):执行具体操作的"手脚"
- 循环机制:维护历史上下文,迭代推进任务
-
关键优势:
- 能处理复杂、动态的多步任务
- 思考过程可见,可调试
- 遇到错误可以重新规划
完整代码清单
# react_agent_demo.py
import os
from datetime import datetime
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain.agents import AgentExecutor, create_react_agent
from langchain.prompts import PromptTemplate
load_dotenv()
# ---------- 1. 定义工具 ----------
@tool
def calculator(expression: str) -> str:
"""计算数学表达式。"""
try:
return f"结果:{eval(expression)}"
except Exception as e:
return f"错误:{e}"
@tool
def get_current_time() -> str:
"""获取当前时间。"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# ---------- 2. 创建ReAct提示模板 ----------
template = """用中文回答。你有以下工具可用:
{tools}
请按以下格式输出:
Question: 用户的问题
Thought: 思考下一步该做什么
Action: 要使用的工具名称 [{tool_names}]
Action Input: 工具的输入参数
Observation: 工具返回的结果
... (可以重复多次)
Thought: 我现在知道最终答案了
Final Answer: 最终答案
开始!
Question: {input}
Thought: {agent_scratchpad}"""
prompt = PromptTemplate.from_template(template)
# ---------- 3. 创建Agent ----------
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [calculator, get_current_time]
agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
max_iterations=5,
)
# ---------- 4. 运行 ----------
if __name__ == "__main__":
response = executor.invoke({
"input": "今天是几号?请计算 100 天后的日期是几月几号?"
})
print(response["output"])
💡 下一步:可以尝试为Agent添加更多工具,比如文件读写、数据库查询、发送邮件等,让Agent完成更复杂的自动化任务!
本文首发于 [你的技术博客名称],欢迎交流讨论! 😄

浙公网安备 33010602011771号