ReAct的论文思想剖析及demo实现
本文提出ReAct框架,通过思考-行动-观察的循环机制,将大语言模型的推理能力与外部工具调用能力有机结合。文章详细阐述了ReAct的形式化定义、提示工程实现及与基线方法的对比,同时指出其面临上下文窗口爆炸、工具调用脆弱性等局限性。最后介绍了ReAct与其他范式(如Reflexion、Toolformer)的联系,并提供了优化后的Python实现方案,包括改进的解析器和智能工具执行器。该框架为构建兼
1. 研究背景和基础
在本文提出以前,LLMs的推理能力和行动能力主要被作为独立的主题进行研究。
1. 只思考不行动
在ReAct之前,Google的 “思维链”(Chain-of-Thought, CoT)工作主要关注如何激发模型内部的逻辑推导能力,例如“让我想想,先算A,再算B”。这种推理是相对封闭的。
思维链(CoT)的局限性:
○ 静态黑盒(Static Black Box):CoT一旦开始生成,就完全依赖模型内部的参数知识。如 果在推理的第一步出现错误,后续的推理无论逻辑多么严密,都会在错误的基础上越走越远。这种现象被称为错误传播(Error Propagation)。
○ 缺乏接地(Ungrounded):由于无法访问外部世界,CoT无法验证自己的假设,也无法更 新过时的知识。
2. 只行动不思考
WebGPT这样的工作则关注如何将自然语言转化为搜索指令或机械臂动作,这属于行动生成。
行动生成(Act-Only)的局限性:
○ 缺乏高层规划:这类方法通常直接将观察(Observation)映射到动作(Action)。在简单的任务中(如“把苹果放在桌子上”)这很有效,但在需要多步推理的复杂任务中(如“找到所有红色的物体并将它们按大小排列”),模型很容易迷失方向,因为它没有显式的机制来维护“我已经找到了哪些物体”这样的高层状态。
○ 数据依赖性:Act-Only方法通常需要大量的演示数据进行模仿学习或强化学习,泛化能力较差。
2.React的初步认知
基于两种以往范式(CoT和WebGPT),本文作者提出了ReAct的定义式特征——交错(Interleaved)。填补这两者之间的空白,通过“以思促行”和“以行促思”的循环,构建一个 既具备逻辑深度又具备现实交互能力的智能体
1.以思促行:推理不仅仅是为了得出最终答案,更是为了规划下一步的行动。例如,在发现“搜索结果为空”这一异常情况(Observation)时,推理部分需要生成“我应 该尝试缩短关键词”这样的思维,从而指导下一次搜索行动。这是对行动计划的动态维护。
2.以行促思:行动不仅仅是为了改变环境,更是为了获取信息来修正内部的认知。通过与外部知识库交互,模型可以将最新的、准确的事实引入到推理上下文中,从而来减少幻觉。
3.React方法论详解
通过简单而优雅的 Thought-Action-Observation 循环,ReAct解决了大模型落地应用中的核心矛 盾:推理的封闭性与现实的开放性之间的矛盾。
3.1形式化定义与动作空间扩展
在传统的强化学习或交互式决策任务中,智能体在时间步
接收来自环境的观察
,并根据当前策略
输出一个动作
。这个动作
会直接作用于环境,导致环境状态的改变并产生新的观察
。
ReAct对此进行了扩展,定义了增强的动作空间
其中:
是原有的外部动作空间,如“搜索”、“点击”、“移动”。
是语言空间,即模型生成的自然语言思维轨迹(Thought )。
关键区别:
动作 会触发外部环境的反馈。思维
不影响外部环境,它只改变智能体的内部状态,即更新当前的上下文
。
这种形式化定义深刻地揭示了ReAct的本质:思考也是一种行动。这与认知科学中的“认知操作”概念相呼应。通过将思考纳入动作空间,ReAct使得智能体可以像选择“向左走”一样,自主选择“思考下一步该做什么”。
3.2 提示工程
在实际操作层面,ReAct主要依赖于上下文学习(In-context Learning),即通过在Prompt中提供 少量的示例(Few-shot Exemplars)来教会LLM这种新的交互模式。这避免了昂贵的模型微调,使得ReAct可以即插即用于各种冻结参数的大模型。
思维类型举例:
1.分解目标:“这个任务要求我找到A和B,然后比较它们的大小。所以我第一步应该先搜索A。”
2.提取关键信息:“搜索结果说A出生于1844年。我需要把这个年份记下来。”
3.常识推理与假设:“既然搜索‘Apple Remote’没有直接结果,通过常识判断,它可能属于 ‘Apple TV’的配件,我应该尝试搜索‘Apple TV’。”
4.自我纠错:“刚才的搜索结果没有包含我想要的信息,说明我的关键词可能太具体了,我应该泛化关键词再试一次。”
3.3 与基线方法的对比架构
为了更清晰地展示ReAct的机制,我们可以通过下表对比几种主要的Prompting范式:
|
特性 |
标准提示 (Standard) | 思维链 (CoT) | 行动生成 (Act-Only) | ReAct (协同范式) |
|---|---|---|---|---|
| 核心机制 | 直接预测答案 | 内部推理 -> 答案 | 观察 -> 动作 | 观察 -> 推理<->动作 |
| 外部交互 | 无 | 无 | 有 (如Web搜索) | 有 (高度集成的交互) |
| 推理可见性 | 黑盒 | 内部可见 (Thought) | 不可见 (隐式) | 显式可见 (Thought & Act) |
| 错误修正能力 | 极低 | 低 (难以自我发现) | 中 (依赖环境反馈) | 高 (推理与环境互证) |
| 主要缺陷 | 准确率低 | 事实幻觉、错误传播 | 缺乏规划、迷失目标 | 上下文消耗大、 依赖工具 |
4. 局限性
根据2024-2025年的开发者反馈 ,ReAct在生产环境中主要面临以下问题:
1. 上下文窗口爆炸(Context Window Explosion):ReAct的轨迹极其冗长。如果搜索工具返 回了大量无关文本,Prompt的长度会迅速突破LLM的上下文限制,导致“迷失在中间”(Lost in the Middle)现象,推理能力急剧下降,且API调用成本高昂。
2. 工具调用的脆弱性:模型对工具参数的格式非常敏感。如果Prompt中的示例与实际任务稍有偏差,模型可能会生成错误的Action Input(如JSON格式错误),导致程序崩溃。
3. 循环死锁:在缺乏外部新信息的情况下,ReAct容易陷入自我验证的死循环,坚信自己的错误推理是正确的。
5. 与其他范式的联系
1. Reflexion
ReAct虽然强大,但它主要关注当下的推理。如果Agent犯了错,它很难从长期的历史中吸取教 训。针对这一痛点,Reflexion框架应运而生。
机制:Reflexion在ReAct的基础上增加了一个“反思”步骤。当任务失败时,Agent会回顾整个轨迹,生成一段“自我批评”,并将其存储在长期记忆中。
协同:在下一次尝试时,ReAct Agent会先读取这些反思记录(如“上次我在搜索X时失败了, 因为关键词太长,这次我应该尝试简短的关键词”),从而避免重蹈覆辙。
关系:可以认为Reflexion是具备了“情景记忆”和“元认知”能力的进阶版ReAct 。
2. Toolformer
通过微调让模型学会调用API,这是一种隐式的ReAct。随着OpenAI推出 Function Calling(函数调用)功能,显式的ReAct Prompting逐渐与隐式的微调融合。现在的 GPT-4o在被告知有工具可用时,会自动进入一种类似ReAct的思维模式,而无需繁琐的 Prompt工程。
3. Tree of Thoughts (ToT)
ReAct通常是线性的(贪婪解码)。对于需要深度探索的数学或逻辑难题,ToT允许模型生成多个思维分支并进行回溯。在2025年的复杂 Agent设计中,往往会采用混合架构:用ToT进行顶层规划,用ReAct执行具体的子任务。
6. ReAct智能体的实现
如果有想实现一个ReAct智能体的demo,可以参考:datawhale的三种智能体经典范式构建
不过其中的提示词可以改为(增加了结果的few_shot案例):
REACT_PROMPT_TEMPLATE = """
请注意,你是一个有能力调用外部工具的智能助手。
可用工具如下:
{tools}
请严格按照以下格式进行回应:
Thought: 你的思考过程,用于分析问题、拆解任务和规划下一步行动。
Action: 你决定采取的行动,必须是以下格式之一:
- `{{tool_name}}[{{tool_input}}]`: 调用一个可用工具。
- `Finish[你的完整答案内容]`: 当你收集到足够信息可以回答用户问题时,将完整的答案内容写在方括号内。
重要提示:
- 使用 Finish 时,必须在方括号内写出完整的、详细的答案内容,而不是写"最终答案"这样的占位符。
- 例如:Finish[华为最新手机是Mate 70 Air,主要卖点包括:1. 6.6mm超薄设计 2. 7英寸大屏...]
现在,请开始解决以下问题:
Question: {question}
History: {history}
"""
对于参考链接中的实现,我优化了工具选择失败处理机制和输出解析器,以下是我优化后的ReActAgent类,基类和main函数参考上方链接。
class ReActAgent:
def __init__(self, llm_client, tool_executor, max_steps: int = 20):
self.llm_client = llm_client
self.tool_executor = tool_executor
self.max_steps = max_steps
self.history = []
def run(self, question: str):
"""主循环"""
self.history = []
current_step = 0
# 获取工具描述
tools_desc = self.tool_executor.getAvailableTools()
while current_step < self.max_steps:
current_step += 1
print(f"--- Step {current_step} ---")
prompt = REACT_PROMPT_TEMPLATE.format(
tools=tools_desc,
question=question,
history="\n".join(self.history)
)
# 2. LLM 思考
messages = [{"role": "user", "content": prompt}]
response = self.llm_client.think(messages=messages)
if not response:
print("⚠️ LLM Empty Response")
break
# 3. 统一解析
thought, action_name, action_input = self._parse_llm_output(response)
if thought:
print(f"🧠 Thought: {thought}")
# 4. 终止条件处理
if action_name == "Finish":
print(f"🎉 Final Answer: {action_input}")
return action_input
# 5. 执行工具
self.history.append(f"Thought: {thought}" if thought else "")
self.history.append(f"Action: {action_name}[{action_input}]")
if not action_name:
observation = "System Error: Invalid format. Please strictly follow 'Action: ToolName[Input]'."
print(f"⚠️ {observation}")
else:
observation = self._execute_tool_smartly(action_name, action_input)
print(f"👀 Observation: {observation}")
self.history.append(f"Observation: {observation}")
return "Max steps reached."
def _parse_llm_output(self, text: str) -> Tuple[str, str, str]:
"""
优化后的解析器:使用单次正则匹配,兼顾健壮性。
支持:
1. Thought: ... Action: Tool[Args]
2. Action: Tool[Args] (直接行动)
3. 容忍大小写和松散空格
"""
if not text:
return "", "", ""
# [`]* -> 允许工具名前面有 0 个或多个反引号
# ([a-zA-Z0-9_]+) -> 捕获工具名
# [`]* -> 允许工具名后面有 0 个或多个反引号
pattern = r"(?i)(?:Thought:\s*(.*?)\s*)?Action:\s*[`]*([a-zA-Z0-9_]+)[`]*\s*\[(.*)\]"
match = re.search(pattern, text, re.DOTALL)
if match:
thought = match.group(1).strip() if match.group(1) else ""
tool_name = match.group(2).strip()
tool_input = match.group(3).strip()
# 清理末尾可能的 markdown 代码块符号 (比如 `calculate[1+1]`)
tool_input = tool_input.rstrip("`").strip()
return thought, tool_name, tool_input
# 降级策略:处理 Finish
if "Finish" in text:
parts = text.split("Finish", 1)
answer = parts[1].strip().strip("[]").strip()
return parts[0].strip(), "Finish", answer
return text.strip(), "", ""
def _execute_tool_smartly(self, tool_name: str, tool_input: str) -> str:
"""
智能工具执行器:集成了 模糊匹配、参数校验 和 错误引导。
"""
# 使用 getTool 方法获取真正的函数对象
tool_func = self.tool_executor.getTool(tool_name)
# 1. 精确匹配
if tool_func:
try:
# 执行工具
return tool_func(tool_input)
except Exception as e:
return f"Tool Execution Error: {str(e)}. Please check your input format."
# 获取所有可用工具名称用于模糊匹配
available_tool_names = list(self.tool_executor.tools.keys())
# 2. 模糊匹配 (自动纠错工具名)
matches = difflib.get_close_matches(tool_name, available_tool_names, n=1, cutoff=0.6)
if matches:
suggested_tool = matches[0]
return f"Error: Tool '{tool_name}' not found. Did you mean '{suggested_tool}'?"
# 3. 启发式错误检测
if tool_name.lower() == "search" and re.search(r"[\+\-\*\/]", tool_input):
return f"Error: Tool '{tool_name}' not found. Note: For math calculations, please use the 'calculate' tool."
# 4. 兜底错误
tool_list_str = ", ".join(available_tool_names)
return f"Error: Tool '{tool_name}' not found. Available tools: {tool_list_str}."
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)