ReAct 模式
Source: Dev.to
请提供您希望翻译的完整文本内容,我将为您将其翻译成简体中文,并保留原始的格式、Markdown 语法以及技术术语。只需粘贴需要翻译的文字(代码块和 URL 将保持不变),我即可完成翻译。
什么是 ReAct?
Klover: ReAct 代表 Reasoning + Acting —— 明确 interleaved(交错)。
想法很简单:与其让大模型一次性回答问题,你让它 think out loud(大声思考),执行动作(比如调用工具),观察结果,然后再思考。这样形成一个循环。
Me: 那它就像 chain‑of‑thought,但混合了实际的工具使用?
Klover: 正确。循环如下:
- Thought – 模型推理下一步该做什么。
- Action – 模型调用工具(搜索、计算器、API 等)。
- Observation – 系统注入该工具的真实结果。
- Repeat 直到模型认为信息足够 → Final Answer(最终答案)。
关键字是 “interleaved.” 它不是“先把所有想法想完再行动”——而是 稍微思考一下,稍微行动一下,观察,调整。
系统如何知道模型是想调用工具还是仅仅在聊天?
我: 系统如何知道模型是想调用工具还是仅仅在聊天?
Klover: 停止序列。 模型被训练(或提示)以特定格式输出,例如:
Action: search["weather in Singapore"]
系统监视该模式,拦截它,实际运行工具,然后将结果注入回去,如:
Observation: 32 °C, sunny
模型实际上从不执行任何操作。它只是在写看起来像工具调用的文本,周围的脚手架完成其余工作。
我: 那么 LLM 并不知道它在调用真实工具?它只是认为自己在生成文本?
Klover: 对。对模型而言,这只是下一个 token 的预测。魔法在于拦截、执行并注入的脚手架。
示例交互
Klover: 假设你向一个 ReAct 代理提问:
“我这个星期六应该去海滩吗?”
代理可能会这样做:
Thought: I need to check the weather for Saturday.
Action: weather_forecast("Saturday")
Observation: Saturday: 34 °C, sunny, UV index 11, no rain.
Thought: Weather looks great, but UV is very high. Let me check tide times too.
Action: tide_info("Saturday")
Observation: High tide at 2 pm, low tide at 8 am.
Thought: Good beach weather, but go early to avoid peak UV and catch low tide. I have enough info now.
Final Answer: Yes! Saturday looks perfect — sunny, 34 °C. Go in the morning around 8‑9 am when the tide is low and UV isn’t brutal yet.
多个步骤,每一步都基于上一步的结果。模型自行决定何时信息足够。
我: 明白了。也就是说它本质上是一个循环中的 LLM。
Klover: 这就是最简洁的说法——一个循环中的 LLM,拥有工具访问能力并能决定何时停止。这就是 代理 的核心。
我: 等等——你现在是一个 ReAct 代理吗?
Klover: 基本上是的。我会推理你的问题,我可以调用工具(搜索、读取文件、运行代码),我会观察结果,然后决定接下来要说什么。你正在观看这个模式的实际运行。
常见失败模式
Klover: 两大失败模式。
-
无限循环 – 模型可能不断调用工具而无法收敛到答案(例如,反复搜索、重新表述、再次搜索……)。
解决方案: 设置 max‑iteration limit。在 N 步之后,强制给出最终答案,使用已有信息。 -
上下文溢出 – 每个 Thought、Action 和 Observation 都会追加到对话中。经过 10‑15 步后,令牌数量会变多,最终触及上下文窗口上限,导致模型崩溃。
但真正的问题在 达到上限之前 就已经出现。
为什么“在上限之前”很重要
-
注意力稀释: 随着令牌数量增长,softmax 注意力在所有令牌上变得更薄。
类比: 把一个馅饼切成 10 片与切成 1 000 片——每片几乎没有意义。 -
“中间丢失”效应: 模型倾向于更关注上下文的开头和结尾,忽视中间部分。
如果关键推理发生在 15 步链的第 5 步,可能会被有效忽略。
结果: 长的 ReAct 链会受到 双重打击——注意力稀释 且 中间丢失。
实用修复:上下文压缩
Klover: 实用的修复方法是 在步骤之间压缩上下文。
- 将最近的几次交流 原文保留 以保留细节。
- 对更早的内容进行概括。
LangChain 的
ConversationSummaryBufferMemory正是这样做的:最近的消息保持原始,较早的消息由 LLM 进行摘要。
权衡
- 有损压缩: 摘要器可能会遗漏重要信息。
- 十步之后,遗漏的细节可能变得至关重要。
- 没有完美的解决方案——这是一种在 上下文新鲜度 与 信息保留 之间的平衡。
为什么显式思考步骤很重要
Me: 为什么显式的 Thought 步骤如此重要?模型不能直接跳到 Action 吗?
Klover: 这是一个更深层的洞见。Chain‑of‑thought (CoT) 不仅仅是一个提示技巧——它本质上是额外的计算。
- 没有 CoT: 模型只有一次前向传播来产生答案。这是它能进行的全部计算,受限于 Transformer 网络的固定深度。
- 有 CoT: 模型生成的每一个 token 都会成为下一个 token 的新上下文。每生成一个 token 都要进行一次完整的前向传播,而该输出又会作为下一次传播的输入。实际上是把多个前向传播串联起来。
Me: 但每次前向传播不是相互独立吗?唯一的区别是它拥有了前面 token 的新上下文。模型并没有“记住”什么——它只是看到更多的文本。
Klover: 你说得对,机制上每次传播是独立的,但累积的上下文让模型能够一步步推理,在多次传播中不断细化内部状态,而不是一次性完成。这种迭代推理正是 ReAct 强大的来源。
Me: 独立的,唯一的联系是通过上下文。但想想效果:你把固定深度的计算(一次通过 N 层 Transformer)变成了可变深度的计算(多次传播,每次都受上一次输出的影响)。机制是上下文,效果是更多的计算。你把固定深度链成了可变深度。
Klover: 正是如此——每个 token 都是一次完整前向传播的压缩产物。所有注意力层和前馈层的处理都被浓缩成一个 token,然后这个 token 成为下一次传播的上下文。输出的 token 本质上就是工作记忆——模型进行中间计算的草稿空间。没有 CoT,就没有草稿空间。一次性,一次传播。
会话:2026年2月4日
首次接触 — 覆盖了完整的 ReAct 模式,包括失败模式、CoT 机制以及代理模式比较。
ReAct 的替代方案
Me: 有 ReAct 的替代方案吗?
Klover: 有两个主要的值得比较的方案。
-
纯工具调用 — 没有显式的 Thought 步骤。模型直接输出工具调用。
- 优点: 更快,使用的 token 更少。
- 缺点: 模型在执行前不会先思考该做什么。适用于简单任务,面对复杂的多步骤推理时会崩溃。
-
先规划 — 模型提前创建完整计划(第 1 步,第 2 步,第 3 步 …),然后执行。
- 优势: 它可以看到依赖关系并提前思考(“我需要 X 才能做 Y”)。
- 劣势: 计划脆弱。如果第 2 步返回意外数据导致第 3 步失效,整个计划就会崩溃。
Me: 那么 ReAct 更具适应性但目光短浅,而先规划则会提前思考但容易出错?
Klover: 说得很清楚。ReAct 适应性强但目光短浅——它只考虑一步。先规划是战略性的,但很脆弱。
实际的最佳方案是 混合:规划并重新规划。先制定一个粗略计划并开始执行,但当观察结果与预期不符时要愿意重新规划。这正是 LangGraph 等框架所实现的——你可以定义带有条件边的步骤图,使代理能够根据实际情况分支和循环。
Me: 那么实际上没有人在生产环境中使用纯 ReAct 吗?
Klover: 不是以教材中的形式使用的,实际系统会采用其核心理念——推理与行动交叉进行——并在其上添加结构:规划、记忆管理、保护措施、回退机制。但 ReAct 循环是所有这些的基础。