ReAct模式结合了反射模式和工具使用模式,这使其成为当前AI Agent使用的最强大的模式之一。AI Agent既可以自我思考,自我纠错,还可以使用工具与世界交互。
python
import json
import re
from typing import Dict, List, Any, Callable, Optional
import openai
from datetime import datetime
class ReActAgent:
"""
ReAct (Reasoning + Acting) 模式AI Agent
支持调用阿里云通义千问大模型,可扩展工具调用
"""
def __init__(
self,
api_key: str,
model: str = "qwen-max",
base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1",
max_iterations: int = 10,
system_prompt: Optional[str] = None
):
"""
初始化ReAct Agent
Args:
api_key: 阿里云API Key
model: 模型名称 (qwen-max, qwen-plus, qwen-turbo等)
base_url: API基础URL
max_iterations: 最大迭代次数
system_prompt: 系统提示词
"""
self.client = openai.OpenAI(
api_key=api_key,
base_url=base_url
)
self.model = model
self.max_iterations = max_iterations
self.tools: Dict[str, Callable] = {}
# 默认系统提示词
self.system_prompt = system_prompt or """你是一个遵循ReAct模式的AI助手,通过思考、行动、观察的循环来解决问题。
可用工具:
{tool_descriptions}
请按以下格式回复:
思考:分析当前情况,决定下一步行动
行动:工具名称(参数),参数必须是JSON格式
观察:工具执行结果(由系统提供)
当任务完成时,输出:
思考:任务已完成
最终答案:你的最终回答
注意:
1. 每次只执行一个行动
2. 工具参数必须是有效的JSON格式
3. 根据观察结果继续思考,直到任务完成
"""
def register_tool(self, name: str, func: Callable, description: str):
"""
注册工具
Args:
name: 工具名称
func: 工具函数
description: 工具描述
"""
self.tools[name] = {
"func": func,
"description": description
}
def _get_tool_descriptions(self) -> str:
"""获取工具描述文本"""
if not self.tools:
return "暂无可用工具"
descriptions = []
for name, info in self.tools.items():
descriptions.append(f"- {name}: {info['description']}")
return "\n".join(descriptions)
def _build_prompt(self, user_input: str, history: List[Dict] = None) -> List[Dict]:
"""
构建提示词
Args:
user_input: 用户输入
history: 历史对话记录
"""
messages = []
# 系统提示词
system = self.system_prompt.format(
tool_descriptions=self._get_tool_descriptions()
)
messages.append({"role": "system", "content": system})
# 添加历史对话
if history:
messages.extend(history)
# 添加用户输入
messages.append({"role": "user", "content": user_input})
return messages
def _parse_action(self, text: str) -> Optional[Dict[str, Any]]:
"""
解析模型输出的行动指令
Args:
text: 模型输出文本
Returns:
解析后的行动指令,包含tool_name和parameters
"""
# 匹配行动格式:工具名({"参数": "值"})
pattern = r'行动:(\w+)\((.*?)\)'
match = re.search(pattern, text, re.DOTALL)
if match:
tool_name = match.group(1)
params_str = match.group(2).strip()
try:
# 尝试解析JSON参数
parameters = json.loads(params_str) if params_str else {}
return {
"tool_name": tool_name,
"parameters": parameters
}
except json.JSONDecodeError:
# 如果不是JSON格式,作为字符串参数处理
return {
"tool_name": tool_name,
"parameters": {"input": params_str}
}
return None
def _extract_thought(self, text: str) -> Optional[str]:
"""提取思考内容"""
pattern = r'思考:(.*?)(?=行动:|最终答案:|$)'
match = re.search(pattern, text, re.DOTALL)
return match.group(1).strip() if match else None
def _extract_final_answer(self, text: str) -> Optional[str]:
"""提取最终答案"""
pattern = r'最终答案:(.*?)$'
match = re.search(pattern, text, re.DOTALL)
return match.group(1).strip() if match else None
def _execute_tool(self, tool_name: str, parameters: Dict) -> str:
"""
执行工具
Args:
tool_name: 工具名称
parameters: 工具参数
Returns:
工具执行结果
"""
if tool_name not in self.tools:
return f"错误:未知工具 '{tool_name}'"
try:
tool_func = self.tools[tool_name]["func"]
result = tool_func(**parameters)
return str(result)
except Exception as e:
return f"工具执行失败:{str(e)}"
def _call_llm(self, messages: List[Dict]) -> str:
"""
调用大模型
Args:
messages: 消息列表
Returns:
模型回复内容
"""
try:
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=0.7,
max_tokens=2000
)
return response.choices[0].message.content
except Exception as e:
raise Exception(f"调用大模型失败:{str(e)}")
def run(self, user_input: str, verbose: bool = True) -> str:
"""
运行ReAct Agent
Args:
user_input: 用户输入
verbose: 是否打印执行过程
Returns:
Agent最终回答
"""
# 初始化
messages = self._build_prompt(user_input)
iteration = 0
full_response = ""
if verbose:
print(f"\n{'=' * 50}")
print(f"用户: {user_input}")
print(f"{'=' * 50}\n")
# ReAct循环
while iteration < self.max_iterations:
iteration += 1
# 调用大模型
response = self._call_llm(messages)
if verbose:
print(f"\n--- 第 {iteration} 次迭代 ---")
print(response)
# 添加到消息历史
messages.append({"role": "assistant", "content": response})
full_response = response
# 检查是否包含最终答案
final_answer = self._extract_final_answer(response)
if final_answer:
if verbose:
print(f"\n✅ 任务完成!")
return final_answer
# 解析行动指令
action = self._parse_action(response)
if not action:
# 没有行动指令,可能思考不完整,继续下一次迭代
continue
# 执行工具
tool_result = self._execute_tool(
action["tool_name"],
action["parameters"]
)
if verbose:
print(f"\n🔧 执行工具: {action['tool_name']}")
print(f"📊 观察结果: {tool_result}")
# 将观察结果添加到消息历史
observation_msg = f"观察:{tool_result}"
messages.append({"role": "user", "content": observation_msg})
# 达到最大迭代次数
return f"达到最大迭代次数({self.max_iterations}),任务未完成。\n最后状态:{full_response}"
def run_stream(self, user_input: str):
"""
流式运行(可选)
"""
# 可以扩展实现流式输出
pass
# ============= 示例工具函数 =============
def calculator(expression: str) -> float:
"""计算器工具:计算数学表达式"""
try:
# 安全计算,只允许基本的数学运算
allowed_names = {
k: v for k, v in math.__dict__.items()
if not k.startswith("__")
}
allowed_names.update({"abs": abs, "round": round})
result = eval(expression, {"__builtins__": {}}, allowed_names)
return f"计算结果:{expression} = {result}"
except Exception as e:
return f"计算错误:{str(e)}"
def get_current_time(format: str = "%Y-%m-%d %H:%M:%S") -> str:
"""获取当前时间"""
return datetime.now().strftime(format)
def search_weather(city: str) -> str:
"""模拟天气查询(实际可接入真实天气API)"""
# 这里用模拟数据演示
weather_data = {
"北京": "晴,25°C,空气质量良",
"上海": "多云,28°C,空气质量优",
"广州": "阵雨,30°C,空气质量优",
"深圳": "雷阵雨,29°C,空气质量良"
}
return weather_data.get(city, f"未找到{city}的天气信息")
def get_stock_price(code: str) -> str:
"""模拟股票查询"""
# 模拟数据
stocks = {
"000001": "平安银行 12.50元",
"600036": "招商银行 35.80元",
"9988": "阿里巴巴 110.50港元"
}
return stocks.get(code, f"未找到代码{code}的股票信息")
# ============= 使用示例 =============
def main():
"""主函数示例"""
# 1. 配置阿里云API Key(从环境变量获取或直接填写)
import os
# 方式1:从环境变量获取(推荐)
API_KEY = os.getenv("DASHSCOPE_API_KEY")
# 方式2:直接填写(仅用于测试,注意安全)
# API_KEY = "sk-你的阿里云API-KEY"
if not API_KEY:
print("请设置环境变量 DASHSCOPE_API_KEY")
print("或在代码中直接填写API_KEY")
return
# 2. 创建Agent
agent = ReActAgent(
api_key=API_KEY,
model="qwen-max", # 可选:qwen-max, qwen-plus, qwen-turbo
max_iterations=5
)
# 3. 注册工具
agent.register_tool(
"calculator",
calculator,
"计算数学表达式,参数expression为数学表达式字符串,如:'2 + 3 * 4'"
)
agent.register_tool(
"get_time",
get_current_time,
"获取当前时间,参数format为可选的时间格式"
)
agent.register_tool(
"weather",
search_weather,
"查询天气,参数city为城市名称"
)
agent.register_tool(
"stock",
get_stock_price,
"查询股票价格,参数code为股票代码"
)
# 4. 测试不同场景
print("\n" + "=" * 60)
print("ReAct Agent 示例 - 调用阿里大模型")
print("=" * 60)
# 示例1:简单计算
print("\n【示例1】计算问题")
result = agent.run("计算 (25 + 37) * 2 等于多少?")
print(f"\n最终答案:{result}")
# 示例2:多步任务
print("\n【示例2】多步任务:查询北京天气,然后计算温度加上10度是多少?")
result = agent.run("帮我查一下北京的天气,然后把温度数值加上10度")
print(f"\n最终答案:{result}")
# 示例3:混合查询
print("\n【示例3】混合查询:现在几点了?查询平安银行股价")
result = agent.run("告诉我当前时间,然后查一下平安银行的股票价格")
print(f"\n最终答案:{result}")
# ============= 简版ReAct实现(自定义提示词) =============
def simple_react_example():
"""
简版ReAct实现 - 展示核心原理
使用自定义提示词让大模型遵循ReAct模式
"""
API_KEY = os.getenv("DASHSCOPE_API_KEY")
if not API_KEY:
print("请设置API_KEY")
return
client = openai.OpenAI(
api_key=API_KEY,
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
# ReAct提示词模板
react_prompt = """请通过思考、行动、观察的循环来解决用户问题。
可用工具:
- calculator(expression): 计算数学表达式
- get_weather(city): 查询城市天气
请按以下格式输出:
思考:[分析当前情况]
行动:[工具名称]([参数JSON])
观察:[等待系统返回]
当任务完成时,输出:
最终答案:[答案]
用户问题:{question}
开始:"""
def call_llm(messages):
response = client.chat.completions.create(
model="qwen-max",
messages=messages,
temperature=0.7
)
return response.choices[0].message.content
# 模拟工具执行
def execute_tool(action_text):
if "calculator" in action_text:
# 简化解析
return "计算结果:42"
elif "get_weather" in action_text:
return "北京:晴,25°C"
return "未知工具"
# 模拟ReAct循环
question = "北京天气多少度?这个温度乘以2是多少?"
messages = [{"role": "user", "content": react_prompt.format(question=question)}]
print(f"用户:{question}\n")
for i in range(3): # 最多3轮
print(f"\n--- 第{i + 1}轮 ---")
response = call_llm(messages)
print(f"模型:{response}")
if "最终答案" in response:
print("\n✅ 任务完成")
break
if "行动" in response:
# 模拟执行工具
observation = execute_tool(response)
print(f"观察:{observation}")
# 添加观察结果到对话
messages.append({"role": "assistant", "content": response})
messages.append({"role": "user", "content": f"观察:{observation}"})
if __name__ == "__main__":
# 需要安装openai库:pip install openai
# 运行完整示例
main()
# 或者运行简版示例
# simple_react_example()