[Hello Agents Chapter 4] 智能体经典范式构建 Part 2:手写 ReAct 智能体
摘要:本文详细介绍了如何从零构建一个ReAct智能体,通过Prompt工程与正则表达式解析实现“思考-行动-观察”的闭环逻辑。文章涵盖系统提示词设计、核心循环实现、输出解析和工具执行等关键环节,并讨论了ReAct范式的优缺点及调试技巧。该智能体通过动态提示词模板和严格输出格式控制,实现工具调用与问题求解的自动化流程。 关键词:ReAct智能体、Prompt工程、正则解析、Agent循环、调试技巧
摘要: 本篇紧接上文,将利用已封装的 LLM 客户端和工具箱,通过精细的 Prompt 工程与正则表达式解析,从零组装一个完整的 ReAct 智能体。我们将深入代码核心,实现“思考-行动-观察”的闭环逻辑,并探讨该范式的优缺点及调试技巧。
关键词: ReAct Agent, Prompt Engineering, Regex Parsing, Agent Loop, Debugging
本文是基于Datawhale的hello-agent开源项目做的一些笔记,内容仅供参考,原PDF以及代码可以去github仓库获取https://datawhalechina.github.io/hello-agents
在 Part 1 中,我们完成了开发环境搭建,封装了 HelloAgentsLLM 客户端,并实现了一个具备智能解析功能的 Google Search 工具及其调度器 ToolExecutor。现在,万事俱备,只欠核心逻辑。
4.2.3 ReAct 智能体的编码实现
我们将通过一个 ReActAgent 类来封装核心逻辑。为了便于理解,我们将实现过程拆分为:Prompt 设计、核心循环、输出解析、工具执行与观测整合。
1. 系统提示词设计 (System Prompt)
提示词是 ReAct 机制的基石。我们需要设计一个模板,动态插入可用工具、用户问题及历史交互记录,并强制 LLM 遵循特定的输出格式(Thought/Action)。
# ReAct 提示词模板
REACT_PROMPT_TEMPLATE = """
请注意,你是一个有能力调用外部工具的智能助手。
可用工具如下:
{tools}
请严格按照以下格式进行回应:
Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。
Action: 你决定采取的行动,必须是以下格式之一:
{tool_name} [{tool_input}]: 调用一个可用工具。
Finish [{final_answer}]: 当你认为已经获得最终答案时。
- 当你收集到足够的信息,能够回答用户的最终问题时,你必须在 Action: 字段后使用 Finish[...] 来输出最终答案。
现在,请开始解决以下问题:
Question: {question}
History:
{history}
"""
这个模板定义了交互规范:
- 角色定义:明确其具备使用工具的能力。
- 工具清单 ({tools}):由
ToolExecutor动态生成。 - 格式规约:这是最关键的部分。
Thought:强制模型进行“内心独白”。Action:规定了ToolName [Input]的调用格式和Finish [Answer]的结束指令。
💡 深度解析 (Deep Dive):
原书使用的这种基于文本格式(Text-based)的 Prompt 在早期 Agent 开发中非常普遍。但在现代工程(如 OpenAI GPT-4 Turbo)中,我们通常会结合 Function Calling (Tool Use) API,将工具定义为 JSON Schema 传入,从而获得更稳定、更结构化的输出,减少正则解析失败的概率。
2. 核心循环的实现 (The Core Loop)
ReActAgent 的核心是一个 while 循环,它不断执行“格式化 Prompt -> 调用 LLM -> 解析 -> 执行 -> 更新历史”的流程。
import re
from typing import List, Dict, Any
# 假设已导入之前定义的类
# from llm_client import HelloAgentsLLM
# from tools import ToolExecutor
class ReActAgent:
def __init__(self, llm_client: HelloAgentsLLM, tool_executor: ToolExecutor, max_steps: int = 5):
self.llm_client = llm_client
self.tool_executor = tool_executor
self.max_steps = max_steps
self.history = [] # 存储 (Action, Observation) 历史
def run(self, question: str):
"""运行 ReAct 智能体来回答一个问题。"""
self.history = [] # 每次运行时重置历史记录
current_step = 0
print(f"--- 开始处理问题: {question} ---")
while current_step < self.max_steps:
current_step += 1
print(f"\n--- 第 {current_step} 步 ---")
# 1. 格式化提示词
tools_desc = self.tool_executor.get_available_tools()
history_str = "\n".join(self.history)
prompt = REACT_PROMPT_TEMPLATE.format(
tools=tools_desc,
question=question,
history=history_str,
tool_name="Search", tool_input="query", final_answer="answer" # 填充模板说明中的占位符
)
# 2. 调用 LLM 进行思考
messages = [{"role": "user", "content": prompt}]
response_text = self.llm_client.think(messages=messages)
if not response_text:
print("错误: LLM 未能返回有效响应。")
break
# ...(后续的解析、执行、整合步骤见下文)
# 为了代码连贯性,我们将逻辑拆解讲解,最后组合
# 3. 解析 LLM 输出
thought, action = self._parse_output(response_text)
if thought:
print(f"思考: {thought}")
if not action:
print("警告: 未能解析出有效的 Action,流程终止。")
break
# 4. 执行 Action
if action.startswith("Finish"):
# 提取最终答案
final_answer_match = re.match(r"Finish\[(.*)\]", action)
final_answer = final_answer_match.group(1) if final_answer_match else action
print(f"\n--- 最终答案 ---\n{final_answer}")
return final_answer
# 解析工具调用
tool_name, tool_input = self._parse_action(action)
if not tool_name or not tool_input:
print(f"错误: 无法解析动作 '{action}'")
continue # 或 break,视策略而定
print(f"行动: {tool_name} [{tool_input}]")
# 5. 调用工具获取观察结果
tool_function = self.tool_executor.get_tool(tool_name)
if not tool_function:
observation = f"错误: 未找到名为 '{tool_name}' 的工具。"
else:
observation = tool_function(tool_input)
print(f"观察: {observation}")
# 6. 将本轮 Action 和 Observation 添加到历史
self.history.append(f"Action: {action}")
self.history.append(f"Observation: {observation}")
print("已达到最大步数,流程终止。")
return None
🛠️ 实战映射 (Implementation):
max_steps是一个极其重要的安全阀。在生产环境中,Agent 很容易因为陷入死循环(Loop)而耗尽 Token 额度。LangChain 的AgentExecutor中也有类似的max_iterations参数。
3. 输出解析器的实现 (Output Parser)
LLM 返回的是非结构化文本,我们需要用正则表达式提取关键信息。
# (这些方法是 ReActAgent 类的一部分)
def _parse_output(self, text: str):
"""解析 LLM 的输出,提取 Thought 和 Action。"""
# 非贪婪匹配 Thought,贪婪匹配 Action
thought_match = re.search(r"Thought: (.*?)Action:", text, re.DOTALL)
# 如果没有明确的 Thought 标记,尝试获取 Action 之前的所有内容
if not thought_match:
thought_match = re.search(r"Thought: (.*)", text, re.DOTALL)
action_match = re.search(r"Action: (.*)", text, re.DOTALL)
thought = thought_match.group(1).strip() if thought_match else None
# 如果 action_match 包含换行符,只取第一行,或者根据具体情况调整正则
action = action_match.group(1).strip() if action_match else None
# 简单的清理
if thought and "Action:" in thought:
thought = thought.split("Action:")[0].strip()
return thought, action
def _parse_action(self, action_text: str):
"""解析 Action 字符串,提取工具名称和输入。例如 Search[Query]"""
match = re.match(r"(\w+)\[(.*)\]", action_text)
if match:
return match.group(1), match.group(2)
return None, None
(注:原书中的正则较简单 r"Thought: (.*)",但在实际多行输出中可能需要 re.DOTALL 或更复杂的逻辑来防止匹配错误,此处尽量保持原书逻辑并微调以确保可运行)
4. 运行实例与分析
将所有组件组合后,我们可以运行一次真实的查询。
输入问题:“华为最新的手机是哪一款?它的主要卖点是什么?”
运行轨迹记录 (Log):
- 第 1 步:
- Thought: 我需要查找华为最新发布的手机型号。这些信息可能在我的知识库之外,需要使用搜索。
- Action:
Search [华为最新手机型号及主要卖点] - Observation: (SerpApi 返回结果)
[1] HUAWEI Pura 70 系列... [2] Mate 60 Pro...
- 第 2 步:
- Thought: 根据搜索结果,Pura 70 是最新发布的。Mate 60 也很新。我需要总结它们的卖点。
- Action:
Finish [根据搜索结果,华为最新的旗舰是 HUAWEI Pura 70 系列... 卖点是先锋影像...]
4.2.4 ReAct 的特点、局限性与调试技巧
亲手实现 ReAct 后,我们可以总结其工程特性。
1. 主要特点
-
高可解释性:通过
Thought链,我们可以清晰看到 Agent 为什么选择这个工具,这对于调试至关重要 。 -
动态规划:它是“走一步,看一步”。如果第一步搜索结果通过,它可以在第二步立即调整关键词重新搜索,而不是死板地执行预设计划 。
-
工具协同:LLM 负责运筹帷幄(Plan/Reason),工具负责解决具体问题(Data/Calc) 。
2. 固有局限性 (Limitations)
-
依赖模型能力:如果 LLM 逻辑推理弱,或者不遵循指令(Instruction Following),很容易在
Thought环节产生幻觉,或者输出无法解析的Action格式 。 -
执行效率:串行循环。每一步都需要一次 LLM 调用 + 网络请求。复杂任务可能导致高延迟和高 Token 消耗 。
-
提示词脆弱性:Prompt 中的标点符号、换行符差异都可能导致 Agent 行为异常 。
-
局部最优:由于缺乏全局规划,它可能陷入“原地打转”的死循环(Looping) 。
3. 调试技巧 (Debugging)
当你构建的 Agent 不工作时,请按以下清单检查 :
- 检查完整 Prompt:打印出最终发给 LLM 的字符串,检查历史记录 (
History) 拼接是否正确。 - 分析原始输出 (Raw Output):解析失败时,查看 LLM 到底输出了什么。往往是因为 LLM 加了“Note:”或者没换行。
- 验证工具输入:确保 Agent 生成的
tool_input符合函数要求(例如 JSON 格式是否正确)。 - Few-shot Prompting:如果模型总是格式错误,在 Prompt 中加入 1-2 个成功的
Thought -> Action -> Observation示例。
更多推荐



所有评论(0)