五、React Agent最简实践 ——《动手学Agent应用开发》学习心得
五、React Agent最简实践 ——《动手学Agent应用开发》学习心得
==================================================================================
最近参加了Datawhale开源组织举办的组队学习。本篇的学习内容是基础原理-选修:三、Agent原理与最简实践
【教程地址】
https://gitlink.org.cn/datawhalechina/build_good_agents
==================================================================================
参考资料:
==================================================================================
通义千问3-30B-A3B-Instruct-2507 · 模型库 (modelscope.cn)
==================================================================================
实现最终效果:
1、从零实现最简 React Agent
1.1、注意事项
-
开发环境:自己的电脑配置比较低,使用魔搭社区的免费实例比较方便,就直接用了,不过研究了大半天怎么使用
-
注册魔搭或硅基流动,获得api_key(推荐魔搭,因为我用的魔搭的线上环境比较配套)
-
注册https://serper.dev/dashboard,获得api_key调用googel搜索API
-
使用 Qwen3-30B-A3B-Instruct-2507 的作为 Agent 模型
-
输出结果给大模型时,通常返回字符串,其他格式的结果大模型不好处理
1.2、实现步骤
1.2.1、 构造大模型
使用 Qwen3-30B-A3B-Instruct-2507 的作为我们的 Agent 模型。Qwen3-30B-A3B-Instruct-2507 模型的工具调用能力非常强大,能够处理复杂的任务。
注意:初学实现Agent最好使用Instruct模型,不要使用思考模型,因为思考模型调用工具不太准确
llm.py完整代码:
from typing import Dict, List, Tuple
from openai import OpenAI
class BaseModel:
def __init__(self, api_key: str = '') -> None:
self.api_key = api_key
def chat(self, prompt: str, history: List[Dict[str, str]], system_prompt: str = "") -> Tuple[str, List[Dict[str, str]]]:
"""
基础聊天接口
Args:
prompt: 用户输入
history: 对话历史
system_prompt: 系统提示
Returns:
(模型响应, 更新后的对话历史)
"""
pass
class Siliconflow(BaseModel):
def __init__(self, api_key: str):
self.api_key = api_key
self.client = OpenAI(api_key=self.api_key, base_url="https://api.siliconflow.cn/v1")
def chat(self, prompt: str, history: List[Dict[str, str]] = [], system_prompt: str = "") -> Tuple[str, List[Dict[str, str]]]:
"""
与 Siliconflow API 进行聊天
Args:
prompt: 用户输入
history: 对话历史
system_prompt: 系统提示
Returns:
(模型响应, 更新后的对话历史)
"""
# 构建消息列表
messages = [
{"role": "system", "content": system_prompt or "You are a helpful assistant."}
]
# 添加历史消息
if history:
messages.extend(history)
# 添加当前用户消息
messages.append({"role": "user", "content": prompt})
# 调用 API
response = self.client.chat.completions.create(
model="Qwen/Qwen3-30B-A3B-Instruct-2507",
messages=messages,
temperature=0.6,
max_tokens=2000,
)
model_response = response.choices[0].message.content
# 更新对话历史
updated_history = messages.copy()
updated_history.append({"role": "assistant", "content": model_response})
return model_response, updated_history
if __name__ == "__main__":
llm = Siliconflow(api_key="your api key")
prompt = "你好"
response, history = llm.chat(prompt)
print("Response:", response)
print("History:", history)
1.2.2、 构造工具
添加工具的描述信息,是为了在构造system_prompt的时候,让模型能够知道可以调用哪些工具,以及工具的描述信息和参数。
- 首先要在
tools中添加工具的描述信息 - 然后在
tools中添加工具的具体实现
使用Google搜索功能的话需要去
serper官网申请一下token: https://serper.dev/dashboard, 然后在tools.py文件中填写你的key,这个key每人可以免费申请一个,且有2500次的免费调用额度,足够做实验用
tools.py完整代码:
import json
import requests
from typing import Dict, List, Any
class ReactTools:
"""
React Agent 工具类
为 ReAct Agent 提供标准化的工具接口
"""
def __init__(self) -> None:
self.toolConfig = self._build_tool_config()
def _build_tool_config(self) -> List[Dict[str, Any]]:
"""构建工具配置信息"""
return [
{
'name_for_human': '谷歌搜索',
'name_for_model': 'google_search',
'description_for_model': '谷歌搜索是一个通用搜索引擎,可用于访问互联网、查询百科知识、了解时事新闻等。',
'parameters': [
{
'name': 'search_query',
'description': '搜索关键词或短语',
'required': True,
'schema': {'type': 'string'},
}
],
}
]
def google_search(self, search_query: str) -> str:
"""执行谷歌搜索
可在 https://serper.dev/dashboard 申请 api key
Args:
search_query: 搜索关键词
Returns:
格式化的搜索结果字符串
"""
url = "https://google.serper.dev/search"
payload = json.dumps({"q": search_query})
headers = {
'X-API-KEY': 'your serper api key',
'Content-Type': 'application/json'
}
try:
response = requests.request("POST", url, headers=headers, data=payload).json()
organic_results = response.get('organic', [])
# 格式化搜索结果
formatted_results = []
for idx, result in enumerate(organic_results[:5], 1):
title = result.get('title', '无标题')
snippet = result.get('snippet', '无描述')
link = result.get('link', '')
formatted_results.append(f"{idx}. **{title}**\n {snippet}\n 链接: {link}")
return "\n\n".join(formatted_results) if formatted_results else "未找到相关结果"
except Exception as e:
return f"搜索时出现错误: {str(e)}"
def get_available_tools(self) -> List[str]:
"""获取可用工具名称列表"""
return [tool['name_for_model'] for tool in self.toolConfig]
def get_tool_description(self, tool_name: str) -> str:
"""获取工具描述"""
for tool in self.toolConfig:
if tool['name_for_model'] == tool_name:
return tool['description_for_model']
return "未知工具"
if __name__ == "__main__":
tools = ReactTools()
result = tools.google_search("美国最近一次阅兵原因 2025")
print(result)
1.2.3、 构造 React Agent
1.2.3.1、 核心组件设计
为什么要这样设计?
React Agent 的设计采用了经典的分层架构模式:
- 接口层:对外暴露简单的
run()方法 - 协调层:管理思考-行动-观察的循环
- 解析层:从模型输出中提取结构化信息
- 执行层:调用具体工具完成任务
这种设计让代码结构清晰,易于维护和扩展。
1.2.3.2、 系统提示词构建
为什么系统提示如此重要?
系统提示是 React Agent 的"大脑",它直接决定了 Agent 的行为模式。一个好的系统提示应该包含:
- 时间信息:让 Agent 知道当前时间,避免过时信息
- 工具清单:明确告诉 Agent 有哪些工具可用
- 行为模式:详细的 ReAct 流程指导
- 输出格式:规范化的思考-行动-观察格式
构建思路:
我们使用 f-string 动态生成系统提示,这样可以:
- 自动包含当前时间
- 动态加载可用工具列表
- 保持提示的时效性和准确性
系统提示模板解析:
prompt = f"""现在时间是 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}。
你是一位智能助手,可以使用以下工具来回答问题:
{tool_descriptions}
请遵循以下 ReAct 模式:
思考:分析问题和需要使用的工具
行动:选择工具 [google_search] 中的一个
行动输入:提供工具的参数
观察:工具返回的结果
你可以重复以上循环,直到获得足够的信息来回答问题。
最终答案:基于所有信息给出最终答案
开始!"""
设计要点:
- 中文提示:更符合国内用户习惯
- 具体工具名:明确告诉模型可用工具
- 循环指导:说明可以多次使用工具
- 最终答案:明确结束条件
prompt = f"""现在时间是 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}。
你是一位智能助手,可以使用以下工具来回答问题:
{tool_descriptions}
请遵循以下 ReAct 模式:
思考:分析问题和需要使用的工具
行动:选择工具 [google_search] 中的一个
行动输入:提供工具的参数
观察:工具返回的结果
你可以重复以上循环,直到获得足够的信息来回答问题。
最终答案:基于所有信息给出最终答案
开始!"""
1.2.3.3、 行动解析机制
为什么解析这么复杂?
在实际应用中,大模型的输出格式往往不够规范,可能出现:
- 中英文混合的冒号
- JSON格式不规范
- 参数缺失或格式错误
- 多余的空格或换行
因此我们需要一个鲁棒的解析机制。
解析思路:
- 多层匹配:先用正则提取,再用JSON解析
- 容错设计:解析失败时提供降级方案
- 格式兼容:支持JSON字符串和纯文本参数
解析流程详解:
模型输出:
思考:用户询问特朗普生日,需要搜索
行动:google_search
行动输入:{"search_query": "特朗普生日"}
解析步骤:
1. 正则提取行动 → google_search
2. 正则提取参数 → {"search_query": "特朗普生日"}
3. JSON解析 → {'search_query': '特朗普生日'}
4. 返回结构化数据 → ('google_search', {'search_query': '特朗普生日'})
常见错误场景:
- ❌
行动输入:特朗普生日→ 自动转为{"search_query": "特朗普生日"} - ❌
行动输入:{"search_query":"特朗普生日"→ 补全JSON格式 - ❌
行动输入:"特朗普生日"→ 去除多余引号
1.2.3.4、 React主循环
什么是 ReAct 循环?
ReAct 循环是 Agent 的"心跳",它让 Agent 能够:
- 持续思考:基于新信息不断调整策略
- 工具调用:在需要时主动获取外部信息
- 结果整合:将工具结果与已有知识结合
循环的四个阶段:
- 思考阶段(Thought):模型分析问题,决定是否需要工具
- 行动阶段(Action):选择合适的工具并提供参数
- 观察阶段(Observation):执行工具并获取结果
- 整合阶段(Integration):将新信息整合到上下文中
为什么需要 max_iterations?
- 防止无限循环:避免模型陷入死循环
- 控制成本:限制 API 调用次数
- 用户体验:避免过长的响应时间
状态管理的重要性:
每次循环都需要:
- 保持上下文:将观察结果加入对话历史
- 更新输入:为下一轮循环准备新的提示
- 历史记录:确保模型知道之前做了什么
agent.py完整代码:
import json5
import re
import time
from llm import Siliconflow
from tool import ReactTools
class ReactAgent:
def __init__(self, api_key: str = '') -> None:
self.api_key = api_key
self.tools = ReactTools()
self.model = Siliconflow(api_key=self.api_key)
self.system_prompt = self._build_system_prompt()
def _build_system_prompt(self) -> str:
"""构建系统提示,使用 ReAct 模式"""
tool_descriptions = []
for tool in self.tools.toolConfig:
tool_descriptions.append(
f"{tool['name_for_model']}: {tool['description_for_model']}"
f" 参数: {json5.dumps(tool['parameters'], ensure_ascii=False)}"
)
tool_names = [tool['name_for_model'] for tool in self.tools.toolConfig]
prompt = f"""现在时间是 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}。你是一位智能助手,可以使用以下工具来回答问题:
{chr(10).join(tool_descriptions)}
请遵循以下 ReAct 模式:
思考:分析问题和需要使用的工具
行动:选择工具 [{', '.join(tool_names)}] 中的一个
行动输入:提供工具的参数
观察:工具返回的结果
你可以重复以上循环,直到获得足够的信息来回答问题。
最终答案:基于所有信息给出最终答案
开始!"""
return prompt
def _parse_action(self, text: str, verbose: bool = False) -> tuple[str, dict]:
"""从文本中解析行动和行动输入"""
# 更灵活的正则表达式模式
action_pattern = r"行动[::]\s*(\w+)"
action_input_pattern = r"行动输入[::]\s*({.*?}|\{.*?\}|[^\n]*)"
action_match = re.search(action_pattern, text, re.IGNORECASE)
action_input_match = re.search(action_input_pattern, text, re.DOTALL)
action = action_match.group(1).strip() if action_match else ""
action_input_str = action_input_match.group(1).strip() if action_input_match else ""
# 清理和解析JSON
action_input_dict = {}
if action_input_str:
try:
# 尝试解析为JSON对象
action_input_str = action_input_str.strip()
if action_input_str.startswith('{') and action_input_str.endswith('}'):
action_input_dict = json5.loads(action_input_str)
else:
# 如果不是JSON格式,尝试解析为简单字符串参数
action_input_dict = {"search_query": action_input_str.strip('"\'')}
except Exception as e:
if verbose:
print(f"[ReAct Agent] 解析参数失败,使用字符串作为搜索查询: {e}")
action_input_dict = {"search_query": action_input_str.strip('"\'')}
return action, action_input_dict
def _execute_action(self, action: str, action_input: dict) -> str:
"""执行指定的行动"""
if action == "google_search":
search_query = action_input.get("search_query", "")
if search_query:
results = self.tools.google_search(search_query)
return f"观察:搜索完成,结果如下:\n{results}"
else:
return "观察:缺少搜索查询参数"
return f"观察:未知行动 '{action}'"
def _format_response(self, response_text: str) -> str:
"""格式化最终响应"""
if "最终答案:" in response_text:
return response_text.split("最终答案:")[-1].strip()
return response_text
def run(self, query: str, max_iterations: int = 3, verbose: bool = True) -> str:
"""运行 ReAct Agent
Args:
query: 用户查询
max_iterations: 最大迭代次数
verbose: 是否显示中间执行过程
"""
conversation_history = []
current_text = f"问题:{query}"
# 绿色ANSI颜色代码
GREEN = '\033[92m'
RESET = '\033[0m'
if verbose:
print(f"{GREEN}[ReAct Agent] 开始处理问题: {query}{RESET}")
for iteration in range(max_iterations):
if verbose:
print(f"{GREEN}[ReAct Agent] 第 {iteration + 1} 次思考...{RESET}")
# 获取模型响应
response, history = self.model.chat(
current_text,
conversation_history,
self.system_prompt
)
if verbose:
print(f"{GREEN}[ReAct Agent] 模型响应:\n{response}{RESET}")
# 解析行动
action, action_input = self._parse_action(response, verbose=verbose)
if not action or action == "最终答案":
final_answer = self._format_response(response)
if verbose:
print(f"{GREEN}[ReAct Agent] 无需进一步行动,返回最终答案{RESET}")
return final_answer
if verbose:
print(f"{GREEN}[ReAct Agent] 执行行动: {action} | 参数: {action_input}{RESET}")
# 执行行动
observation = self._execute_action(action, action_input)
if verbose:
print(f"{GREEN}[ReAct Agent] 观察结果:\n{observation}{RESET}")
# 更新当前文本以继续对话
current_text = f"{response}\n观察结果:{observation}\n"
conversation_history = history
# 达到最大迭代次数,返回当前响应
if verbose:
print(f"{GREEN}[ReAct Agent] 达到最大迭代次数,返回当前响应{RESET}")
return self._format_response(response)
if __name__ == '__main__':
agent = ReactAgent(api_key="your api key")
response = agent.run("中国最近一次阅兵的原因有哪些?", max_iterations=3, verbose=True)
print("最终答案:", response)
1.3、调试技巧
查看详细日志:设置 verbose=True 查看完整流程
减少迭代次数:测试时使用 max_iterations=1 快速验证
检查API响应:确保网络连接和API key有效
错误排查:观察绿色输出中的每一步执行状态

浙公网安备 33010602011771号