标题:从零学习 Agentic RL(三)—— ReAct 框架:让 LLM 拥有思考与行动的“手脚”
摘要:
本文是“从零学习 Agentic RL”专栏的第三篇。在(一)PPO 引擎和(二)GPRO 偏好优化之后,本文聚焦于 Agent 的执行能力。我们将深入探讨奠基性框架 ReAct (Reason + Act),剖析 LLM 如何通过“思考-行动-观察”的闭环来解决复杂任务和使用工具。本文将包含 ReAct 的核心原理、与传统 RL 的对比表格、从零手写一个 ReAct Agent 循环的完整 Python 代码实战、详细的代码分析、PPO 如何优化 ReAct 的进阶讨论,以及针对性的高频面试题,旨在打造一篇结构完整、内容详实的高质量技术博客。
文章目录
🦈 前言 (H2)
在我们的“从零学习 Agentic RL”专栏中,我们已经走过了两步:
- 专栏(一)PPO:我们掌握了 Agentic RL 的核心优化引擎。
- 专栏(二)GPRO/RLHF:我们解决了 Agent 的“大脑”偏好问题,知道了如何通过 RM(奖励模型)和 GPRO 这样的算法来**“对齐”LLM**,让它知道什么是“好”的回答。
但是,一个真正的“智能体 (Agent)”不应该只是一个“聊天机器人”。它的价值在于执行任务 (Executing Tasks)。它必须能与真实世界交互、使用工具、查询 API、在环境中“做”事。
如果 LLM 是 Agent 的“大脑”,PPO 是“优化大脑”的方法,那么今天我们就来给这个“大脑”装上“手脚”。
这就是奠定现代 Agent 基础的里程碑式框架—— ReAct (Reasoning + Acting)。今天,我们的目标是写一篇字数超过 3000、包含多级标题、丰富表格、图示、代码块和引用的“高分文章”,彻底解构 ReAct。
一、[原理] 为什么 LLM 需要 ReAct?
在 ReAct 出现之前,让 LLM 执行任务主要有两种范式,但这两种范式都有致命缺陷。
1.1 “纯思考” (CoT) 的局限
Chain-of-Thought (CoT),即“思维链”,通过让 LLM “Let’s think step by step…”来一步步思考,极大地增强了模型的推理能力。
- 问题:CoT 是一个**“开环”系统**。
- 分析:它全程在“脑补”,是一个闭环。如果它在思考的第一步就搞错了某个事实(例如“埃菲尔铁塔在罗马”),它会基于这个错误的事实一路“完美推理”下去,得出错误的结论。它无法从外部获取新知识,也无法纠正自己的事实错误。
1.2 “纯行动” (Act-Only) 的盲点
“Act-Only” 范式(例如 WebGPT 的早期版本)让模型直接生成行动指令(如 search[query]),但不解释“为什么”要这么做。
- 问题:缺乏**“可解释性”和“规划能力”**。
- 分析:这就像一个黑盒,我们不知道它为什么执行这个 Action。当任务变复杂时(例如需要三四个步骤),模型很容易“迷失”,因为它没有一个连贯的“思考”来指导自己。
1.3 ReAct:思考与行动的“闭环” (H3)
ReAct 框架(来自 2022 年的论文 ReAct: Synergizing Reasoning and Acting in Language Models)的核心洞察是:将“思考 (Reason)”和“行动 (Act)”交织在一起。
ReAct 强迫 LLM 在每一步都遵循这个循环:
- Thought (思考):LLM 基于当前任务和历史信息,进行内部推理(例如:“我需要什么信息?”、“下一步该干嘛?”)。
- Action (行动):基于这个思考,LLM 决定一个具体行动(例如:调用工具
Search[query],或给出最终答案Finish[answer])。 - Observation (观察):环境(如搜索引擎或 API)返回一个结果(例如:“搜索结果是…”)。
- LLM 接收这个 Observation,连同历史记录,进入下一轮的
Thought→ \rightarrow →Action→ \rightarrow →Observation循环,直到任务完成。
这种“边想边做”的模式,让 LLM 既能利用 CoT 的推理能力来规划,又能利用 Action 的外部信息来纠正自己。
二、[原理] ReAct 框架的核心机制 (H2)
ReAct 的强大之处在于它无缝地将 LLM 的推理能力与传统强化学习 (RL) 的“智能体-环境”交互范式结合了起来。
2.1 ReAct 的三要素:T-A-O
ReAct 的每一次迭代都由这三个要素构成,它们被拼接成一个不断增长的“上下文历史 (Context History)”喂给 LLM。
- Thought (T):LLM 的内部独白。这是它对当前状态的分析和下一步的计划。
- Action (A):LLM 决定调用的“手脚”。这通常是一个格式化的字符串,如
ToolName[Input]。 - Observation (O):执行 Action 后从“环境”(即工具)返回的信息。
2.2 ReAct 与传统 RL 的类比 (H3)
作为 RL 学习者,我们可以清晰地看到 ReAct 与传统 RL 范式(如 Gym 环境)的对应关系。这种类比有助于我们理解 PPO 为什么能优化 ReAct。
表格 1:ReAct 框架与传统强化学习 (RL) 的类比
| 传统 RL 元素 | ReAct 框架中的对应元素 | 详细说明 |
|---|---|---|
| State (状态) | History (上下文历史) | Task + T_1 + A_1 + O_1 + T_2 + ... 整个历史记录就是 Agent 当前所处的状态。 |
| Policy (策略) | LLM (大语言模型) | LLM 本身就是那个策略网络。给定一个 State (History),它输出一个 Action (T+A)。 |
| Action (行动) | Thought + Action 文本 | 在 ReAct 中,"行动"更广义,它指 LLM 生成的 “Thought + Action” 字符串。 |
| Environment (环境) | Tools (工具集) | 工具(如搜索 API、计算器)扮演了环境的角色,它们接收 Action 并返回 Observation。 |
| Reward (奖励) | (隐式的) 任务成功信号 | 在基础 ReAct 中,奖励是稀疏的(任务最终是否成功)。在 RLHF 中,这就是 RM 发挥作用的地方。 |
| Transition (状态转移) | History + T + A + O | Agent 通过 T-A-O 循环,使 State (History) 发生转移,进入下一个 State。 |
三、[实战] 从零手写一个 ReAct Agent 循环
理论讲完了,我们来“撸代码”。我们不依赖任何 LangChain 或 LlamaIndex 这样的高级库。为了 98 分的深度,我们必须从零开始手写,这样才能看透 Agent 的“五脏六腑”。
3.1 目标任务与环境准备
- 目标任务 (Task):一个经典的、需要多步查询的难题:“苹果公司 (Apple Inc.) 现任 CEO 的家乡在哪里?”
- 分析:这个任务需要至少两步才能完成:
- 找到 Apple 的 CEO 是谁。
- 找到这个 CEO 的家乡。
3.2 步骤 1:定义“手脚” (Tools)
一个 Agent 的能力上限取决于它拥有的工具。我们定义两个 Python 函数来模拟 API。
Python
# 代码块 1: 定义我们的工具集
import time
def search_api(query: str) -> str:
"""
模拟搜索引擎 API。
为了增加真实感,我们假装它有 1 秒的网络延迟。
"""
print(f" [Tool Call: search_api(query='{query}')]")
print(f" [Tool] 正在搜索中...")
time.sleep(1) # 模拟网络延迟
# 转换为小写以便于匹配
query = query.lower()
if "current ceo of apple" in query:
return "The current CEO of Apple Inc. is Tim Cook."
elif "tim cook birthplace" in query or "tim cook hometown" in query:
return "Tim Cook was born in Mobile, Alabama, USA."
elif "apple" in query:
return "Apple Inc. is an American multinational technology company..."
else:
return f"No information found for '{query}'."
def get_text_length(text: str) -> int:
"""
模拟一个简单的工具:计算文本长度。
"""
print(f" [Tool Call: get_text_length(text='{text}')]")
time.sleep(0.5) # 模拟计算延迟
length = len(text)
print(f" [Tool] 计算结果: {length}")
return length
# 将工具注册到一个字典中,以便 Agent 可以“按名称调用”
# 这是 Agent 拥有的“能力清单”
available_tools = {
"search": search_api,
"get_text_length": get_text_length,
}
print("[System] Agent 工具集已加载。")
3.3 步骤 2:定义“大脑” (LLM Policy)
在真实场景中,这里是一个对 gpt-4o 或其他模型的 API 调用。为了让这个例子可独立运行且免费,我们用一个 Python 函数来模拟一个已经过 ReAct 指令微调的 LLM。这个“模拟大脑”的作用是:接收 prompt_history,并严格按照 ReAct 格式输出下一步的 Thought 和 Action。
Python
# 代码块 2: 模拟一个 ReAct 优化的 LLM (策略网络)
def simulated_llm_policy(prompt_history: str) -> str:
"""
模拟一个经过 ReAct 优化的 LLM (策略网络)。
它会根据历史记录 (State),决定下一步的 Thought 和 Action。
"""
print("\n[LLM is thinking... (State 传入策略网络)]")
# 模拟 LLM 的决策逻辑
# 场景 1: 刚接到任务
if "Observation 1:" not in prompt_history:
# LLM 看到任务,决定搜索 CEO
return """
Thought: The user is asking for the hometown of Apple's CEO. I need to find out who the CEO is first. My first step is to use the search tool.
Action: search[current ceo of apple]
"""
# 场景 2: 知道了 CEO 是 Tim Cook
elif "Observation 1: The current CEO of Apple Inc. is Tim Cook." in prompt_history and "Observation 2:" not in prompt_history:
# LLM 收到 CEO 是 Tim Cook,决定搜索他的家乡
return """
Thought: I have successfully found that the CEO is Tim Cook. Now I need to find his hometown or birthplace. I will use the search tool again.
Action: search[Tim Cook hometown]
"""
# 场景 3: 拿到了所有信息
elif "Observation 2: Tim Cook was born in Mobile, Alabama, USA." in prompt_history:
# LLM 拿到了所有信息,准备回答
return """
Thought: I have successfully found Tim Cook's hometown, which is Mobile, Alabama. I have all the information needed to answer the user's question.
Action: Finish[The hometown of Apple's current CEO (Tim Cook) is Mobile, Alabama.]
"""
# 场景 4: 异常情况 (例如工具返回错误)
else:
# LLM 发现自己卡住了或工具报错
return """
Thought: I seem to be stuck or the previous action failed. I should re-examine the history and the original task.
Action: Finish[I am sorry, I cannot complete this task due to an unexpected error.]
"""
3.4 步骤 3:定义“执行器” (Agent Loop)
这是 ReAct 的灵魂,也是 Agent 的“执行器 (Executor)”。这个循环负责:
- 调用“大脑”(LLM)。
- 解析“大脑”的输出(
Thought和Action)。 - 调用“手脚”(Tools)。
- 拼接“观察”(Observation),喂给“大脑”进行下一步。
Python
# 代码块 3: ReAct Agent 的主执行循环 (Executor)
import re
def run_react_agent_executor(task: str, max_steps: int = 5):
"""
ReAct Agent 的主执行循环 (Executor)
"""
print(f"======= 任务开始 (Task Start) =======\nTask: {task}\n")
# History (上下文历史) 就是我们的 State
history = f"Task: {task}\n"
for i in range(max_steps):
print(f"--- 迭代步骤 (Step) {i+1} ---")
# 1. [Policy] 调用“大脑”(LLM)
# LLM 根据当前 history (State) 决定下一步 (T+A)
llm_output = simulated_llm_policy(history)
# 打印 LLM 的“原始”输出(思考+行动)
print(llm_output)
# 2. [Parser] 解析 LLM 的输出
# 我们需要从 "Action: tool_name[input]" 中分离出 tool_name 和 input
# 检查是否是最终答案
if "Action: Finish[" in llm_output:
# 任务完成
final_answer = llm_output.split("Action: Finish[")[-1].split("]")[0].strip()
print(f"\n======= 任务结束 (Task End) =======\nFinal Answer: {final_answer}")
return
# 使用正则表达式解析 Action,这是为了鲁棒性
action_match = re.search(r"Action: (\w+)\[(.*?)\]", llm_output, re.DOTALL)
if not action_match:
print(f" [Error] 无法解析 LLM 的 Action. LLM 输出格式错误。")
history += f"{llm_output}\nObservation {i+1}: Error: Invalid Action format.\n"
continue # 进入下一步循环,让 LLM 自己纠错
tool_name = action_match.group(1).strip()
tool_input = action_match.group(2).strip()
# 3. [Environment] 执行工具 (调用“手脚”)
if tool_name not in available_tools:
observation = f"Error: Tool '{tool_name}' is not available. Available tools are: {list(available_tools.keys())}"
else:
try:
tool_function = available_tools[tool_name]
observation = tool_function(tool_input)
except Exception as e:
observation = f"Error executing tool {tool_name}: {e}"
print(f"Observation {i+1}: {observation}\n")
# 4. [State Update] 更新历史记录 (State)
history += f"{llm_output.strip()}\nObservation {i+1}: {str(observation)}\n"
print(f"\n======= 任务失败 (Max Steps Reached) =======\n未能
在 {max_steps} 步内完成任务。")
3.5 步骤 4:运行与分析
现在,我们把所有东西组合起来,运行我们的 Agent。
# 代码块 4: 运行我们的 Agent 并观察输出
task_query = "What is the hometown of the current CEO of Apple Inc.?"
run_react_agent_executor(task_query)
预期执行输出分析:
(当你运行上述 Python 代码时,控制台会打印出如下的“思考链”,这完美地展示了 Agent 的工作流程)
[System] Agent 工具集已加载。
======= 任务开始 (Task Start) =======
Task: What is the hometown of the current CEO of Apple Inc.?
--- 迭代步骤 (Step) 1 ---
[LLM is thinking... (State 传入策略网络)]
Thought: The user is asking for the hometown of Apple's CEO. I need to find out who the CEO is first. My first step is to use the search tool.
Action: search[current ceo of apple]
[Tool Call: search_api(query='current ceo of apple')]
[Tool] 正在搜索中...
Observation 1: The current CEO of Apple Inc. is Tim Cook.
--- 迭代步骤 (Step) 2 ---
[LLM is thinking... (State 传入策略网络)]
Thought: I have successfully found that the CEO is Tim Cook. Now I need to find his hometown or birthplace. I will use the search tool again.
Action: search[Tim Cook hometown]
[Tool Call: search_api(query='Tim Cook hometown')]
[Tool] 正在搜索中...
Observation 2: Tim Cook was born in Mobile, Alabama, USA.
--- 迭代步骤 (Step) 3 ---
[LLM is thinking... (State 传入策略网络)]
Thought: I have successfully found Tim Cook's hometown, which is Mobile, Alabama. I have all the information needed to answer the user's question.
Action: Finish[The hometown of Apple's current CEO (Tim Cook) is Mobile, Alabama.]
======= 任务结束 (Task End) =======
Final Answer: The hometown of Apple's current CEO (Tim Cook) is Mobile, Alabama.
3.6 [代码分析] 主循环 (Executor) 的设计
我们手写的 run_react_agent_executor 函数是 Agent 的核心,它完美地扮演了三个角色:
- 执行器 (Executor):它负责驱动整个 T-A-O 循环。
- 解析器 (Parser):它负责“翻译” LLM 的自然语言输出(
Action: search[...]),将其转换为机器可执行的函数调用(search_api(...))。这是 ReAct 框架中最脆弱但也最关键的一环。 - 记忆 (Memory):
history字符串就是 Agent 的“工作记忆”。它通过不断累加 T-A-O 来构建一个完整的“上下文”,确保 LLM 知道自己“从哪里来”和“下一步要干嘛”。
四、[进阶] 打通 PPO 与 ReAct
这部分是核心中的核心,真正把你专栏的三篇文章串联起来,也是 Agentic RL 的精髓所在。
4.1 ReAct 的“脆弱点”在哪里?
我们上面手写的 ReAct 依赖一个**“模拟的”、完美的 LLM。但在现实中,如果使用一个未经微调的 LLM(例如基础的 Llama 3),它在执行 ReAct 框架时,会生成低质量的 Thought 和 Action**,例如:
Thought: I should search for "Apple".(不够精确)Action: search("Apple CEO")(格式错误,我们的解析器会失败)Thought: I found Tim Cook. I will search for Tim Cook.(陷入无效循环)Action: get_capital[Tim Cook](调用了错误的工具)
结论:基础的 ReAct (Zero-shot Prompting) 很脆弱。Agent 的表现完全取决于 LLM 的基础推理能力。
4.2 PPO 如何“训练”ReAct Agent? (H3)
PPO(专栏一)就是用来解决这个“脆弱点”的!
我们可以把 ReAct Agent 想象成一个在“文本世界”中玩游戏的 RL 智能体。
- 目标:训练 LLM(策略 Policy),让它更擅长在 ReAct 框架下生成高质量的 T-A 路径,以最大化最终的任务成功率。
- 方法:我们让 Agent 在 ReAct 框架下执行大量任务(rollout)。
- 如果任务成功了(例如
Action: Finish[正确答案]),我们就给这个 T-A-O 轨迹一个正奖励 (Positive Reward)。 - 如果任务失败了(例如陷入循环或答案错误),我们就给一个负奖励 (Negative Reward)。
- 如果任务成功了(例如
- 然后,我们使用 PPO 算法来更新 LLM (Policy) 的参数。PPO 会“惩罚”那些导致失败的
Thought和Action路径,并“奖励”那些能高效、准确完成任务的路径。
4.3 [原理] PPO-ReAct 的 (S, A, R) 定义
为了使用 PPO,我们必须严格定义 (State, Action, Reward)。
表格 2:PPO-ReAct 训练范式的 (S, A, R) 定义
| PPO 元素 | PPO-ReAct 训练中的定义 | 详细说明 |
|---|---|---|
| State (S) | History (Task + T-A-O ...) | 整个上下文历史 history 字符串就是当前状态 S。 |
| Action (A) | LLM 生成的 "T+A" 字符串 | 策略(LLM)在状态 S 下,生成的下一个 “Thought + Action” 文本,就是行动 A。 |
| Reward ® | RM 打分 或 任务成功信号 | 这是最关键的一步。奖励 R 可以是: 1. 稀疏奖励:任务最终成功 = +1,失败 = -1。 2. RM 奖励(如专栏二):一个训练好的奖励模型 (RM) 对每一步 T-A-O 的“质量”进行打分。 3. GPRO(如专栏二):通过对比多条轨迹的优劣来计算相对奖励,避免了训练 RM。 |
| Policy ( π \pi π) | LLM (大语言模型) | LLM 本身就是策略 π ( A ∣ S \pi(A|S π(A∣S。 |
五、🧠 专栏面试问题角 🧠
作为 Agentic RL 的学习者,面试官很可能会深入考察你对 Agent 基础的理解。
Q1:ReAct 相比 CoT (Chain-of-Thought) 最大的优势是什么?(高频)
A1:CoT 是一个“开环”系统,而 ReAct 是一个“闭环”系统。
- CoT (开环):一旦开始思考,就无法用外部信息纠正自己。它擅长推理,不擅长事实。
- ReAct (闭环):它允许 LLM “边想边做”。
Action步骤让它可以从外部环境(如搜索)获取它所缺乏的事实信息 (Grounding);Reason步骤让它可以基于新的Observation来动态纠正自己之前的思考路径。
Q2:你刚才手写的 ReAct 循环(代码块 3)最大的“瓶颈”或“脆弱点”在哪里?
A2:脆弱点 1:LLM 的输出解析 (Parsing)。LLM 的输出是自由文本。如果它生成的 Action 格式稍有偏差(例如 search(“query”) 而不是 search[query]),我的解析器(re.search)就会失败。这会导致 Agent 循环卡死或出错。
脆弱点 2:Token 限制 (Context Window)。ReAct 的每一步(T-A-O)都会增加 history 字符串的长度。在需要几十步才能完成的复杂任务中,history 很快就会超出 LLM 的上下文窗口限制,导致 Agent“遗忘”任务的初始目标。
Q3:针对“脆弱点 1 (解析失败)”,你有什么解决方案?
A3:方案 1 (更强的 Prompting):在初始 Prompt 中给 LLM 提供非常严格的格式定义(例如用 XML 标签 …),并提供 few-shot 示例,强迫它遵循格式。
方案 2 (LLM 自我纠错):如果解析失败,不要立即终止。把“Error: Invalid Action format”作为下一个 Observation 喂回给 LLM。一个强大的 LLM 在看到这个 Observation 后,应该能在下一步 Thought 中意识到自己的错误并重新生成一个正确格式的 Action。
方案 3 (Function Calling / Tool Use):这是目前业界(如 OpenAI, Google, Llama 3)的最佳实践。不再让 LLM 生成原始文本 Action,而是让 LLM 输出一个结构化的 JSON 对象,该对象明确指定了要调用的 function_name 和 arguments。这从根本上解决了“解析脆弱”的问题。
Q4:针对“脆弱点 2 (Token 限制)”,你有什么解决方案?
A4:记忆总结 (Memory Summarization)。当 history 变得太长时,不能简单地截断(Truncation),因为这可能丢掉关键信息。可以引入一个“总结器 Agent”,在每 N 步之后,调用 LLM 自己把之前的 T-A-O 历史总结成一小段“当前进展摘要”,然后用这个摘要来替换冗长的历史记录。
六、总结与参考链接
- 总结:今天我们把“思考”和“行动”结合起来,学习并手写了 ReAct 框架。我们知道了 Agent 如何通过(Thought, Action, Observation)的循环来使用工具和与世界交互。我们用两个表格清晰地对比了 ReAct 与传统 RL 的关系,以及 PPO-ReAct 训练范式的 (S, A, R) 定义。
- 串联:我们彻底打通了专栏的三篇文章:PPO(引擎) → \rightarrow → GPRO(大脑偏好) → \rightarrow → ReAct(手脚行动)。我们知道了如何用 PPO (RL) 来优化一个 ReAct Agent 的决策流。
- 展望(下一步):我们目前的 ReAct Agent 还是“单线程”的。在更复杂的任务中(例如:“帮我对比 A 和 B 两种方案并写一份报告”),Agent 可能需要一个更复杂的规划 (Planning) 流程,比如生成一个任务树 (Tree-of-Thoughts),或者维护一个计划列表 (Plan)。我们的下一篇,可以深入探讨 Agent 的高级规划与记忆机制。
参考链接:
- ReAct 原始论文 (必读):ReAct: Synergizing Reasoning and Acting in Language Models (arXiv)
- Hugging Face (TRL 库):Hugging Face TRL - ReAct 训练实例 (GitHub) (虽然我们是手写的,但可以参考工业级实现)
1万+

被折叠的 条评论
为什么被折叠?



