Day13:迈向智能交互:从 ReAct Agent 开发到优化的全历程
Day13:迈向智能交互:从 ReAct Agent 开发到优化的全历程
一、引言
在当今人工智能应用的浪潮中,开发智能 Agent 成为解锁大语言模型潜力的关键。本周我们将深入学习如何开发一个完整的 ReAct Agent,并在后续对其进行复盘、调试与优化。以通义千问大模型为依托,逐步构建一个功能强大、交互友好的智能 Agent。
二、第一个完整 ReAct Agent 开发
核心任务:整合知识开发完整 ReAct Agent
-
工具选择与整合:我们需要整合多种工具,使 Agent 能够根据用户问题自主选择合适工具。例如,我们选择计算器、搜索和文件读取作为初始的三个工具。对于计算器工具,用于处理数值计算问题;搜索工具,可针对用户信息查询需求;文件读取工具,则能满足读取本地文件内容的要求。
-
工具函数定义:
- 计算器工具:
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
搜索工具:假设使用一个模拟的搜索 API,实际需替换为真实 API。
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
文件读取工具:
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
工具选择逻辑:借助通义千问大模型的推理能力,判断用户问题所需工具。通过向通义千问发送用户问题和工具描述,模型决定调用哪个工具。
def choose_tool(user_input, api_key):
tools = [
{
"name": "calculate",
"parameters": {
"type": "object",
"properties": {
"num1": {"type": "number"},
"num2": {"type": "number"},
"operation": {"type": "string"}
},
"required": ["num1", "num2", "operation"]
},
"description": "用于执行简单的数学运算"
},
{
"name": "search",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"api_key": {"type": "string"}
},
"required": ["query", "api_key"]
},
"description": "用于搜索信息"
},
{
"name": "read_file",
"parameters": {
"type": "object",
"properties": {
"file_path": {"type": "string"}
},
"required": ["file_path"]
},
"description": "用于读取本地文件内容"
}
]
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
结果解析与回答生成:不同工具返回结果格式不同,需要针对性解析。例如,计算器返回简单数值,搜索可能返回 JSON 格式数据,文件读取返回文本内容。解析后,构建提示让通义千问生成自然语言回答。
def parse_calculator_result(result):
if result is not None:
return f"计算结果为{result}"
return ""
def parse_search_result(result):
if result:
# 假设搜索结果结构,实际需根据真实API调整
return f"搜索结果: {result.get('results', [])}"
return ""
def parse_read_file_result(result):
if result:
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 "无法生成回答"
完整 ReAct Agent 代码:
import requests
import json
import os
from pydantic import BaseModel, validator
# 计算器工具
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
# 搜索工具
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
# 文件读取工具
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
# 模拟通义千问API调用(实际需替换为真实API调用)
def call_qwen_api(api_key, prompt):
url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
data = {
"model": "qwen-plus",
"messages": [
{"role": "user", "content": prompt}
],
"stream": False
}
try:
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"调用通义千问API失败: {e}")
return None
def choose_tool(user_input, api_key):
tools = [
{
"name": "calculate",
"parameters": {
"type": "object",
"properties": {
"num1": {"type": "number"},
"num2": {"type": "number"},
"operation": {"type": "string"}
},
"required": ["num1", "num2", "operation"]
},
"description": "用于执行简单的数学运算"
},
{
"name": "search",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"api_key": {"type": "string"}
},
"required": ["query", "api_key"]
},
"description": "用于搜索信息"
},
{
"name": "read_file",
"parameters": {
"type": "object",
"properties": {
"file_path": {"type": "string"}
},
"required": ["file_path"]
},
"description": "用于读取本地文件内容"
}
]
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
def parse_calculator_result(result):
if result is not None:
return f"计算结果为{result}"
return ""
def parse_search_result(result):
if result:
return f"搜索结果: {result.get('results', [])}"
return ""
def parse_read_file_result(result):
if result:
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 "无法生成回答"
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':
params = SearchParams(**tool_params)
result = search(params)
parsed_result = parse_search_result(result)
elif tool_name =='read_file':
params = ReadFileParams(**tool_params)
result = read_file(params)
parsed_result = parse_read_file_result(result)
else:
return "无法确定要使用的工具"
return generate_answer(parsed_result, api_key)
补充任务:优化交互体验
-
用户输入提示:在程序开始时,提示用户输入问题,使交互更友好。
-
执行步骤打印:在 Agent 运行过程中,打印选择的工具、传入的参数以及中间结果,方便追溯问题。
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}")
if tool_name == 'calculate':
params = CalculatorParams(**tool_params)
result = calculate(params)
print(f"计算结果: {result}")
parsed_result = parse_calculator_result(result)
elif tool_name =='search':
params = SearchParams(**tool_params)
result = search(params)
print(f"搜索结果: {result}")
parsed_result = parse_search_result(result)
elif tool_name =='read_file':
params = ReadFileParams(**tool_params)
result = read_file(params)
print(f"文件读取结果: {result}")
parsed_result = parse_read_file_result(result)
else:
print("无法确定要使用的工具")
exit()
answer = generate_answer(parsed_result, api_key)
print(f"生成的回答: {answer}")
三、 Agent 调试优化
核心任务:复盘本周学习内容
-
Function Call:回顾如何使用 Function Call 让大模型调用外部工具,包括工具函数定义、参数校验以及与大模型的交互流程。
-
ReAct 范式:总结 ReAct 范式的 “推理→动作→观察→再推理” 流程,以及它如何与 Function Call 结合,使 Agent 能够根据用户问题合理选择并调用工具。
-
Agent 开发:梳理开发完整 ReAct Agent 的过程,从工具选择、工具函数实现、结果解析到回答生成的各个环节,分析每个环节的作用和实现方法。
补充任务:调试优化 Agent
-
修复已知 bug:
-
工具选择错误:检查工具选择逻辑,确保通义千问返回的工具选择与用户问题匹配。可能需要调整工具描述,使模型更准确理解工具适用场景。
-
结果解析失败:针对不同工具返回结果,仔细检查解析函数。例如,若搜索 API 返回结果结构变化,相应调整
parse_search_result函数。
-
-
增加新工具(文件写入):
-
工具函数定义:
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 -
工具描述添加:在工具选择逻辑中,添加文件写入工具的描述。
tools = [ { "name": "calculate", "parameters": { "type": "object", "properties": { "num1": {"type": "number"}, "num2": {"type": "number"}, "operation": {"type": "string"} }, "required": ["num1", "num2", "operation"] }, "description": "用于执行简单的数学运算" }, { "name": "search", "parameters": { "type": "object", "properties": { "query": {"type": "string"}, "api_key": {"type": "string"} }, "required": ["query", "api_key"] }, "description": "用于搜索信息" }, { "name": "read_file", "parameters": { "type": "object", "properties": { "file_path": {"type": "string"} }, "required": ["file_path"] }, "description": "用于读取本地文件内容" }, { "name": "write_file", "parameters": { "type": "object", "properties": { "file_path": {"type": "string"}, "content": {"type": "string"} }, "required": ["file_path", "content"] }, "description": "用于将内容写入本地文件" } ] -
结果解析与回答生成调整:添加文件写入工具的结果解析函数,并在
react_agent函数中处理文件写入工具的调用和结果解析。def parse_write_file_result(result): if result: return result return "文件写入失败" 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': params = SearchParams(**tool_params) result = search(params) parsed_result = parse_search_result(result) elif tool_name =='read_file': params = ReadFileParams(**tool_params) result = read_file(params) parsed_result = parse_read_file_result(result) elif tool_name == 'write_file': params = WriteFileParams(**tool_params) result = write_file(params) parsed_result = parse_write_file_result(result) else: return "无法确定要使用的工具" return generate_answer(parsed_result, api_key) -
优化后的完整代码:
-
import requests
import json
import os
from pydantic import BaseModel, validator
# 计算器工具
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
# 搜索工具
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
# 文件读取工具
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
# 文件写入工具
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
# 模拟通义千问API调用(实际需替换为真实API调用)
def call_qwen_api(api_key, prompt):
url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
data = {
"model": "qwen-plus",
"messages": [
{"role": "user", "content": prompt}
],
"stream": False
}
try:
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"调用通义千问API失败: {e}")
return None
def choose_tool(user_input, api_key):
tools = [
{
"name": "calculate",
"parameters": {
"type": "object",
"properties": {
"num1": {"type": "number"},
"num2": {"type": "number"},
"operation": {"type": "string"}
},
"required": ["num1", "num2", "operation"]
},
"description": "用于执行简单的数学运算"
},
{
"name": "search",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"api_key": {"type": "string"}
},
"required": ["query", "api_key"]
},
"description": "用于搜索信息"
},
{
"name": "read_file",
"parameters": {
"type": "object",
"properties": {
"file_path": {"type": "string"}
},
"required": ["file_path"]
},
"description": "用于读取本地文件内容"
},
{
"name": "write_file",
"parameters": {
"type": "object",
"properties": {
"file_path": {"type": "string"},
"content": {"type": "string"}
},
"required": ["file_path", "content"]
},
"description": "用于将内容写入本地文件"
}
]
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
def parse_calculator_result(result):
if result is not None:
return f"计算结果为{result}"
return ""
def parse_search_result(result):
if result:
return f"搜索结果: {result.get('results', [])}"
return ""
def parse_read_file_result(result):
if result:
return f"文件内容: {result}"
return ""
def parse_write_file_result(result):
if result:
return 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 "无法生成回答"
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':
params = SearchParams(**tool_params)
result = search(params)
parsed_result = parse_search_result(result)
elif tool_name =='read_file':
params = ReadFileParams(**tool_params)
result = read_file(params)
parsed_result = parse_read_file_result(result)
elif tool_name == 'write_file':
params = WriteFileParams(**tool_params)
result = write_file(params)
parsed_result = parse_write_file_result(result)
else:
return "无法确定要使用的工具"
return generate_answer(parsed_result, api_key)
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}")
if tool_name == 'calculate':
params = CalculatorParams(**tool_params)
result = calculate(params)
print(f"计算结果: {result}")
parsed_result = parse_calculator_result(result)
elif tool_name =='search':
params = SearchParams(**tool_params)
result = search(params)
print(f"搜索结果: {result}")
parsed_result = parse_search_result(result)
elif tool_name =='read_file':
params = ReadFileParams(**tool_params)
result = read_file(params)
print(f"文件读取结果: {result}")
parsed_result = parse_read_file_result(result)
elif tool_name == 'write_file':
params = WriteFileParams(**tool_params)
result = write_file(params)
print(f"文件写入结果: {result}")
parsed_result = parse_write_file_result(result)
else:
print("无法确定要使用的工具")
exit()
answer = generate_answer(parsed_result, api_key)
print(f"生成的回答: {answer}")
总结
通过本周的学习,我们成功开发并优化了一个基于通义千问大模型的 ReAct Agent。从最初整合 Function Call、ReAct 范式等知识开发出支持多种工具的 Agent,到对其进行调试优化,包括修复工具选择和结果解析的问题,并增加新的文件写入工具,逐步提升了 Agent 的功能和稳定性。
在实际应用中,可根据具体需求进一步扩展工具种类,优化工具选择逻辑以及结果解析方式,以更好地满足用户多样化的需求。同时,记得将本周知识点笔记以及优化后的 Agent 代码整理保存,方便后续参考和改进。如果在学习过程中遇到问题,可回顾本周所学内容,或者查阅相关文档进行解决。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)