1. 从零构建ReAct智能代理:基于通义千问的完整开发指南
最近在开发一个基于大语言模型的智能代理系统时,我发现ReAct范式(推理-动作循环)能够显著提升代理的决策能力。本文将分享我使用通义千问大模型开发完整ReAct Agent的实战经验,涵盖从基础工具集成到高级优化的全过程。
这个项目源于我在开发智能客服系统时遇到的实际需求——需要让AI不仅能理解用户问题,还能自主调用外部工具完成计算、查询等具体任务。通过结合ReAct范式和Function Calling功能,我们最终实现了一个能够自主选择并执行工具的智能代理。
2. ReAct Agent核心架构设计
2.1 ReAct范式基础原理
ReAct(Reasoning and Acting)是一种将推理(Reasoning)和行动(Acting)结合的范式,其核心是让AI系统能够:
- 根据当前状态进行推理
- 决定需要执行的动作
- 观察动作结果
- 基于新状态继续推理
这种循环机制使得Agent能够像人类一样"思考后再行动",而不是简单地预测下一个token。在我们的实现中,通义千问大模型负责推理部分,而Python函数则作为可执行的动作。
2.2 工具系统设计要点
一个实用的ReAct Agent需要精心设计的工具系统。我们选择了三种基础但功能明确的工具作为起点:
- 计算器:处理数学运算
- 搜索工具:获取外部信息
- 文件读取:访问本地内容
每种工具都需要明确定义:
- 输入参数及验证规则
- 执行逻辑
- 输出格式
这种模块化设计使得后续添加新工具变得非常简单,只需遵循相同的接口规范即可。
3. 核心工具实现详解
3.1 计算器工具实现
计算器是最基础的工具,但实现时仍需注意多个细节:
python复制class CalculatorParams(BaseModel):
num1: float
num2: float
operation: str
@validator('operation')
def valid_operation(cls, v):
valid_ops = ['+', '-', '*', '/']
if v not in valid_ops:
raise ValueError('不支持的运算操作')
return v
def calculate(params: CalculatorParams):
if params.operation == '+':
return params.num1 + params.num2
elif params.operation == '-':
return params.num1 - params.num2
elif params.operation == '*':
return params.num1 * params.num2
elif params.operation == '/':
if params.num2 == 0:
print("除数不能为零")
return None
return params.num1 / params.num2
关键设计考虑:
- 使用Pydantic进行参数验证,确保输入合法性
- 明确支持的操作类型,避免无效操作
- 特殊处理除零错误,防止程序崩溃
3.2 搜索工具实现
搜索工具需要连接外部API,这里我们使用了一个模拟实现:
python复制class SearchParams(BaseModel):
query: str
api_key: str
@validator('query')
def query_not_empty(cls, v):
if not v.strip():
raise ValueError('查询内容不能为空')
return v
@validator('api_key')
def api_key_not_empty(cls, v):
if not v.strip():
raise ValueError('api_key不能为空')
return v
def search(params: SearchParams):
base_url = "https://api.example.com/search"
params_dict = {
"key": params.api_key,
"q": params.query
}
try:
response = requests.get(base_url, params=params_dict)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"搜索失败: {e}")
return None
注意事项:
- API密钥和查询内容都不能为空
- 使用try-except处理网络请求异常
- 返回标准化的JSON格式便于后续解析
3.3 文件操作工具实现
文件读写是本地操作的重要能力,实现时需特别注意安全性:
python复制class ReadFileParams(BaseModel):
file_path: str
@validator('file_path')
def file_path_exists(cls, v):
if not os.path.exists(v):
raise ValueError('文件路径不存在')
return v
def read_file(params: ReadFileParams):
try:
with open(params.file_path, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
print(f"读取文件失败: {e}")
return None
安全考虑:
- 验证文件路径是否存在
- 明确指定文件编码(UTF-8)
- 使用上下文管理器确保文件正确关闭
4. 工具选择与执行流程
4.1 工具选择逻辑实现
核心挑战是如何让大模型根据用户问题选择合适的工具。我们通过以下方式实现:
python复制def choose_tool(user_input, api_key):
tools = [
{
"name": "calculate",
"description": "用于执行简单的数学运算",
"parameters": {
"type": "object",
"properties": {
"num1": {"type": "number"},
"num2": {"type": "number"},
"operation": {"type": "string"}
},
"required": ["num1", "num2", "operation"]
}
},
# 其他工具定义...
]
prompt = {
"user_input": user_input,
"functions": tools
}
response = call_qwen_api(api_key, json.dumps(prompt))
if "function_call" in response:
function_call = response["function_call"]
return function_call["name"], function_call["parameters"]
return None, None
关键点:
- 为每个工具提供清晰的描述,帮助模型理解适用场景
- 明确定义参数结构,确保模型生成合规的参数
- 处理API返回时检查function_call字段是否存在
4.2 结果解析与回答生成
不同工具返回的结果格式各异,需要统一解析后生成自然语言回答:
python复制def parse_calculator_result(result):
if result is not None:
return f"计算结果为{result}"
return "计算失败"
def generate_answer(parsed_result, api_key):
prompt = f"根据以下信息生成回答: {parsed_result}"
response = call_qwen_api(api_key, prompt)
if response:
return response["choices"][0]["message"]["content"]
return "无法生成回答"
设计考虑:
- 每种工具都有专门的解析函数处理其特定结果格式
- 解析后的中间结果再次调用大模型生成自然语言回答
- 所有环节都有错误处理,避免单点故障导致系统崩溃
5. 完整Agent实现与优化
5.1 主循环实现
将各个组件整合成完整的ReAct Agent:
python复制def react_agent(user_input, api_key):
tool_name, tool_params = choose_tool(user_input, api_key)
if tool_name == 'calculate':
params = CalculatorParams(**tool_params)
result = calculate(params)
parsed_result = parse_calculator_result(result)
elif tool_name == 'search':
# 搜索工具处理...
else:
return "无法确定要使用的工具"
return generate_answer(parsed_result, api_key)
5.2 交互体验优化
为方便调试和使用,我们增加了详细的执行日志:
python复制if __name__ == "__main__":
api_key = "your_api_key"
user_input = input("请输入你的问题: ")
print(f"用户输入: {user_input}")
tool_name, tool_params = choose_tool(user_input, api_key)
print(f"选择的工具: {tool_name}")
print(f"工具参数: {tool_params}")
# 执行工具并打印中间结果...
answer = generate_answer(parsed_result, api_key)
print(f"生成的回答: {answer}")
5.3 扩展文件写入功能
随着需求增长,我们新增了文件写入工具:
python复制class WriteFileParams(BaseModel):
file_path: str
content: str
@validator('file_path')
def file_path_valid(cls, v):
if not v.strip():
raise ValueError('文件路径不能为空')
return v
def write_file(params: WriteFileParams):
try:
with open(params.file_path, 'w', encoding='utf-8') as f:
f.write(params.content)
return "文件写入成功"
except Exception as e:
print(f"文件写入失败: {e}")
return None
新增工具时需要:
- 更新工具选择逻辑中的工具列表
- 添加对应的结果解析函数
- 在主循环中增加处理分支
6. 常见问题与调试技巧
6.1 工具选择不准确
问题现象:大模型经常选择错误的工具
解决方案:
- 优化工具描述,使其更准确地反映功能
- 在prompt中加入示例问题
- 对边界情况进行特殊处理
6.2 参数解析失败
问题现象:大模型生成的参数不符合预期格式
解决方案:
- 加强参数验证逻辑
- 提供更详细的参数说明
- 实现参数后处理逻辑
6.3 性能优化建议
- 对频繁使用的工具结果进行缓存
- 实现工具并行执行能力
- 优化大模型prompt减少token消耗
7. 项目总结与展望
通过这个项目,我们实现了一个功能完整的ReAct Agent,它能够:
- 理解用户自然语言输入
- 自主选择合适工具
- 执行具体操作
- 生成自然语言回答
在实际应用中,这种架构可以扩展到更多场景:
- 电商客服:查询订单、退换货处理
- 数据分析:自动运行查询并解释结果
- 智能家居:控制设备并反馈状态
未来可能的改进方向包括:
- 增加工具使用记忆,避免重复操作
- 实现工具组合使用(多步推理)
- 加入用户反馈学习机制
