Agent 智能体开发实战指南(三):ReAct 框架深度解析
系列导读:这是《Agent 智能体开发实战指南》系列的第三篇,将深入讲解 ReAct 框架------Agent 智能体的核心思考与行动模式,通过实战案例理解「思考→行动→观察」的完整循环。
一、什么是 ReAct 框架?
1.1 核心概念
ReAct = Reasoning(推理) + Acting(行动)
ReAct 是让 Agent 像人类一样解决问题的关键框架:
人类解决问题的过程:
1. 思考:分析问题,制定策略
2. 行动:执行某个步骤
3. 观察:查看结果
4. 再思考:根据结果调整策略
5. 重复 2-4,直到问题解决
ReAct 让 Agent 不再"直接回答",而是"一步步解决"。
1.2 ReAct vs 直接回答
| 方式 | 示例 | 适用场景 |
|---|---|---|
| 直接回答 | 用户:"123×456 等于多少?" → LLM:"56088" | 简单问题、LLM 已知知识 |
| ReAct 模式 | 用户:"计算我的 BMI" → Agent:思考→获取体重→获取身高→计算→回答 | 复杂问题、需要外部数据 |
二、ReAct 循环详解
2.1 完整循环流程
┌─────────────────────────────────────────────────────────┐
│ ReAct 循环 │
│ │
│ ┌──────────┐ │
│ │ 思考 │ ← 分析当前状态,决定下一步 │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 行动 │ ← 调用工具执行具体操作 │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 观察 │ ← 接收工具返回结果 │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ 是 ┌─────────┐ │
│ │ 完成? │ ───────────→│ 生成答案 │ │
│ └────┬─────┘ └─────────┘ │
│ │ 否 │
│ └──────────────────→ │
│ 回到思考 │
└─────────────────────────────────────────────────────────┘
2.2 每个阶段的任务
| 阶段 | LLM 的任务 | 输出内容 |
|---|---|---|
| 思考 | 分析需求,评估已有信息,决定下一步 | 自然语言推理过程 |
| 行动 | 选择合适的工具,传入正确参数 | 工具调用请求 |
| 观察 | 解析工具返回,提取关键信息 | 内部状态更新 |
三、实战案例:BMI 计算器
3.1 场景设定
用户问:"计算我的 BMI"
问题拆解:
- BMI 公式:
BMI = 体重 (kg) / (身高 (m))² - 需要数据:体重、身高
- 数据来源:需要调用工具获取
3.2 工具定义
python
from langchain.agents import create_agent
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.tools import tool
@tool(description="获取体重,返回值是整数,单位千克")
def get_weight() -> int:
return 90
@tool(description="获取身高,返回值是整数,单位厘米")
def get_height() -> int:
return 172
agent = create_agent(
model=ChatTongyi(model="qwen3-max"),
tools=[get_weight, get_height],
system_prompt="""你是严格遵循 ReAct 框架的智能体,必须按「思考→行动→观察→再思考」的流程解决问题,
且**每轮仅能思考并调用 1 个工具**,禁止单次调用多个工具。
并告知我你的思考过程,工具的调用原因,按思考、行动、观察三个结构告知我""",
)
3.3 执行过程全记录
python
for chunk in agent.stream(
{"messages": [{"role": "user", "content": "计算我的 BMI"}]},
stream_mode="values"
):
latest_message = chunk['messages'][-1]
if latest_message.content:
print(latest_message.content)
try:
if latest_message.tool_calls:
print(f"工具调用:{[tc['name'] for tc in latest_message.tool_calls]}")
except AttributeError:
pass
3.4 完整输出
### 思考
要计算 BMI(身体质量指数),需要知道体重(千克)和身高(厘米)。公式为:
BMI = 体重(kg) / (身高(m))^2
由于目前既没有体重也没有身高的数据,我需要先获取其中一个值。
通常,先获取体重或身高都可以,但为了按步骤进行,我选择先获取体重。
### 行动
调用 `get_weight` 工具获取体重。
工具调用:['get_weight']
### 观察
获取到的体重是 **90 千克**。
### 思考
现在我已经有了体重(90 kg),接下来需要获取身高(厘米)才能计算 BMI。
因此,下一步应该调用 `get_height` 工具来获取身高数据。
### 行动
调用 `get_height` 工具获取身高。
工具调用:['get_height']
### 观察
获取到的身高是 **172 厘米**(即 1.72 米)。
### 思考
现在我已经有了计算 BMI 所需的所有数据:
- 体重 = 90 kg
- 身高 = 172 cm = 1.72 m
代入 BMI 公式:
BMI = 90 / (1.72 × 1.72) ≈ 30.39
根据世界卫生组织(WHO)的标准,BMI ≥ 30 属于 **肥胖** 范围。
因此,最终的 BMI 计算结果约为 **30.39**,属于肥胖范围。
3.5 关键观察点
- 思考过程透明化:每一步都有明确的思考说明
- 单工具调用:每次只调用一个工具,便于观察
- 状态累积:每次观察后,信息逐步完整
- 自然终止:信息足够后,直接生成答案
四、system_prompt 的设计艺术
4.1 基础版 vs 进阶版
基础版(不推荐):
python
system_prompt="你是一个助手,可以回答用户问题。"
问题:
- 没有指定思考方式
- 没有约束工具调用行为
- LLM 可能直接回答,不调用工具
进阶版(推荐):
python
system_prompt="""你是严格遵循 ReAct 框架的智能体,必须按「思考→行动→观察→再思考」的流程解决问题,
且**每轮仅能思考并调用 1 个工具**,禁止单次调用多个工具。
并告知我你的思考过程,工具的调用原因,按思考、行动、观察三个结构告知我。
可用工具:
- get_weight: 获取体重(千克)
- get_height: 获取身高(厘米)
回答格式:
### 思考
[你的分析]
### 行动
[你要调用的工具]
### 观察
[工具返回后,记录结果]
"""
4.2 system_prompt 核心要素
| 要素 | 作用 | 示例 |
|---|---|---|
| 角色定义 | 设定 Agent 身份 | "你是严格遵循 ReAct 框架的智能体" |
| 流程约束 | 规范思考方式 | "必须按思考→行动→观察的流程" |
| 工具说明 | 帮助 LLM 理解可用工具 | 列出工具名和用途 |
| 输出格式 | 统一响应结构 | "按思考、行动、观察三个结构告知" |
| 特殊约束 | 针对场景的限制 | "每轮仅能调用 1 个工具" |
五、多步推理场景
5.1 场景:旅行规划助手
用户问题:"我想去深圳玩 3 天,帮我规划一下"
需要的工具:
get_weather(city)- 查询天气get_hotels(city, budget)- 查询酒店get_attractions(city)- 查询景点get_transport(origin, dest)- 查询交通
ReAct 流程:
思考 1:需要先了解深圳的天气情况,以便推荐合适的活动
行动 1:调用 get_weather("深圳")
观察 1:深圳晴天,26°C,适合户外活动
思考 2:天气良好,接下来查询深圳的热门景点
行动 2:调用 get_attractions("深圳")
观察 2:获得景点列表:世界之窗、欢乐谷、大梅沙...
思考 3:需要知道用户的出发地才能查询交通
行动 3:调用 get_user_location()
观察 3:用户在北京
思考 4:查询北京到深圳的交通方式
行动 4:调用 get_transport("北京", "深圳")
观察 4:高铁 8 小时,飞机 3 小时...
思考 5:查询酒店信息
行动 5:调用 get_hotels("深圳", 500)
观察 5:获得酒店列表...
思考 6:信息收集完成,整合生成 3 天行程规划
→ 生成最终答案
5.2 代码实现
python
@tool(description="查询城市天气")
def get_weather(city: str) -> str:
pass
@tool(description="查询城市景点")
def get_attractions(city: str) -> str:
pass
@tool(description="获取用户所在城市")
def get_user_location() -> str:
pass
@tool(description="查询两地交通,传入出发地和目的地")
def get_transport(origin: str, dest: str) -> str:
pass
@tool(description="查询酒店,传入城市和预算")
def get_hotels(city: str, budget: int) -> str:
pass
agent = create_agent(
model=ChatTongyi(model="qwen3-max"),
tools=[get_weather, get_attractions, get_user_location, get_transport, get_hotels],
system_prompt="你是旅行规划助手,按 ReAct 框架逐步收集信息后生成行程规划。"
)
六、ReAct 的高级技巧
6.1 条件分支处理
场景:根据工具返回结果决定下一步行动
python
# 示例:根据天气决定推荐活动
### 思考
已获取天气信息:深圳今天下雨。
下雨天不适合户外活动,应该推荐室内景点。
### 行动
调用 get_indoor_attractions("深圳") # 注意:这里选择了不同的工具
实现方式:
- 在 system_prompt 中说明条件逻辑
- 训练 LLM 理解"如果...那么..."的推理
6.2 错误恢复
场景:工具调用失败时的处理
python
### 行动
调用 get_weather("深圳湾")
### 观察
错误:城市"深圳湾"不存在
### 思考
工具返回错误,可能是城市名称不准确。
尝试使用更标准的名称"深圳"重新查询。
### 行动
调用 get_weather("深圳")
system_prompt 建议:
python
system_prompt="""...
如果工具调用失败或返回错误:
1. 分析错误原因
2. 尝试调整参数重新调用
3. 如果多次失败,告知用户并寻求更多信息
..."""
6.3 提前终止
场景:信息已足够,无需继续调用工具
python
### 思考
用户只问了天气,已经获取到完整信息。
无需调用其他工具,直接回答即可。
七、调试与优化
7.1 打印完整执行轨迹
python
for chunk in agent.stream(input_dict, stream_mode="values"):
for msg in chunk['messages']:
msg_type = type(msg).__name__
if msg_type == "HumanMessage":
print(f"👤 用户:{msg.content}")
elif msg_type == "AIMessage":
print(f"🤖 Agent: {msg.content}")
if hasattr(msg, 'tool_calls') and msg.tool_calls:
print(f"🔧 工具调用:{[tc['name'] for tc in msg.tool_calls]}")
elif msg_type == "ToolMessage":
print(f"📦 工具返回:{msg.content}")
print("-" * 50)
7.2 常见问题与解决
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 跳过思考 | Agent 直接调用工具 | 在 prompt 中强调思考步骤 |
| 无限循环 | 反复调用同一工具 | 添加"避免重复调用"约束 |
| 工具选择错误 | 调用不相关的工具 | 优化工具 description |
| 参数错误 | 传入错误参数类型 | 在 description 中明确参数格式 |
7.3 性能优化
问题:ReAct 循环次数过多,响应慢
优化策略:
- 限制最大循环次数:
python
agent = create_agent(..., max_iterations=10)
- 并行调用独立工具:
python
# LangChain 支持并行调用无依赖的工具
# 如同时获取体重和身高
- 缓存工具结果:
python
# 对不变的数据(如用户信息)进行缓存
# 避免重复调用
八、本章小结
核心要点
- ReAct 循环:思考→行动→观察→再思考
- 透明化思考:让 Agent 输出推理过程,便于调试和信任
- system_prompt 设计:明确角色、流程、约束、格式
- 错误处理:工具失败时的恢复策略
- 提前终止:信息足够时直接回答
下章预告
下一篇我们将讲解 流式输出与状态管理,学习:
- stream_mode 的各种模式详解
- 如何实时展示 Agent 思考过程
- 状态快照与调试技巧
- 生产环境的流式处理
- Agent 智能体开发实战指南(一):从 LLM 到 Agent 的认知升级
- Agent 智能体开发实战指南(二):工具调用系统深度解析
- Agent 智能体开发实战指南(三):ReAct 框架深度解析(本文)
- Agent 智能体开发实战指南(四):流式输出与状态管理
- Agent 智能体开发实战指南(五):中间件系统与动态提示词
- Agent 智能体开发实战指南(六):RAG 与向量存储实战
- Agent 智能体开发实战指南(七):项目架构设计与工程化实践
- Agent 智能体开发实战指南(八):UI 集成与生产部署
本文是《Agent 智能体开发实战指南》系列的第三篇,下一篇将深入讲解流式输出与状态管理。