ReAct 原理简单——「想-做-看」循环。但从原理到生产,要趟过三道坎:怎么让 LLM 老老实实按 Thought/Action/Observation 格式输出?怎么把工具返回嵌进下一轮 Prompt?怎么对付越来越长的轨迹上下文?这三道坎趟不过,ReAct 就停在演示阶段。
| 坎 | 难点 | 后果 |
|---|---|---|
| Prompt 模板 | 怎么让 LLM 按格式输出 | 格式错乱、解析失败 |
| 轨迹生成 | 怎么把上一步 Observation 拼进下一轮 | 循环转不起来 |
| 上下文管理 | 轨迹越积越长怎么办 | 窗口爆炸、注意力稀释 |
本节逐一拆解。
ReAct 的核心是让 LLM 输出结构化的 Thought 与 Action,而不是自由文本。这需要精心设计的 Prompt 模板。
一份基本的 ReAct Prompt 包含四部分:
| 部分 | 作用 |
|---|---|
| 任务说明 | 告诉 LLM 「按 Thought/Action/Observation 格式输出」 |
| 工具清单 | 列出可用工具及调用方式 |
| 少样本示例 | 演示一两条完整轨迹(最关键!) |
| 当前任务 | 真实问题 + 已有的轨迹历史 |
少样本示例(few-shot examples)是 ReAct Prompt 的灵魂。它告诉 LLM「你应该这样想、这样调工具、这样收尾」。
[示例] Question: 科罗拉多造山运动延伸到的地区海拔最高点是什么? Thought 1: 我需要先查科罗拉多造山运动延伸到哪里。 Action 1: search["科罗拉多造山运动"] Observation 1: 科罗拉多造山运动延伸到高平原地区。 Thought 2: 高平原地区海拔最高点是什么? Action 2: search["高平原 海拔最高点"] Observation 2: 高平原最高点约为 2400 米。 Thought 3: 信息齐全,可以回答。 Final Answer: 约 2400 米。 [真实任务] Question: <用户问题> Thought 1: (LLM 模仿上面的格式开始)
💡 少样本示例的设计要点:示例要展示「完整的循环模式」——包括中途的纠错、何时停止、何时给 Final Answer。示例的质量直接决定 ReAct 的稳定性。一个常见错误是只给「直来直去」的示例,LLM 遇到需要纠错的场景就崩。
| 风格 | 写法 | 优点 |
|---|---|---|
| 自然语言描述 | 「search(query): 在网上搜索查询词」 | 通用、灵活 |
| 结构化 Schema | JSON Schema 描述工具名、参数、返回 | 严谨、可机器校验 |
早期 ReAct 论文用自然语言;现代框架(如 OpenAI Function Calling)用结构化 Schema。生产推荐结构化——它让 LLM 输出可被严格解析,避免「调了个不存在的工具」。
ReAct 的循环,本质是「把上一步的 Observation 拼回 Prompt,再让 LLM 输出下一步」。这个过程叫轨迹生成(Trajectory Generation)。
trajectory = [ { role: "user", content: "任务: ..." }, { role: "assistant", content: "Thought 1: ...\nAction 1: search[\"...\"]" }, { role: "tool", content: "Observation 1: ..." }, # 工具返回 { role: "assistant", content: "Thought 2: ...\nAction 2: ..." }, { role: "tool", content: "Observation 2: ..." }, ... ]
每一轮循环做四件事:
LLM 输出的 Thought + Action 是一段文本,需要解析出「调用什么工具、参数是什么」。
LLM 输出: "Thought 1: 我需要查苹果营收。 Action 1: search[\"Apple revenue 2024\"]" 解析后: tool_name = "search" args = {"query": "Apple revenue 2024"}
解析的两种方式:
| 方式 | 做法 | 优缺点 |
|---|---|---|
| 正则解析 | 用正则匹配 Action X: tool[args] |
简单,但脆弱 |
| 结构化输出 | 用 Function Calling 让 LLM 直接输出 JSON | 严谨,但需模型支持 |
⚠️ 解析失败是 ReAct 最常见的崩溃点。LLM 偶尔会输出「Thought 写完了但忘了写 Action」或「Action 格式不对」。生产 ReAct 必须有解析失败的兜底——通常是「把解析错误信息回喂给 LLM,让它重新输出」。这是第 2.4 节「错误回喂」原则在 ReAct 的具体应用。
ReAct 每轮循环都会在轨迹里追加 Thought + Action + Observation,上下文会单调增长。一个 10 步的 ReAct 任务,最终 Prompt 可能是初始的 10 倍长。
| 危害 | 表现 |
|---|---|
| 窗口爆炸 | 超过模型上下文窗口,直接报错 |
| 成本飙升 | Token 数与费用成正比 |
| 注意力稀释 | 上下文越长,LLM 越容易忽略早期的关键信息 |
| 延迟增加 | 长 Prompt 推理更慢 |
| 策略 | 做法 | 适用 |
|---|---|---|
| 滑动窗口 | 只保留最近 N 步,丢弃更早的 | 短期任务 |
| 摘要压缩 | 定期把旧轨迹压缩成摘要 | 中长期任务 |
| 结构化存档 | 关键事实抽出来单独存,轨迹只留索引 | 长程任务 |
| 外部记忆 | 旧轨迹转入向量库,按需检索 | 跨任务积累 |
最简单——只保留最近 N 步(如最近 5 步)的轨迹,更早的丢弃。
保留窗口 = 5 当前轨迹 = [Step1, Step2, ..., Step20] 实际送进 Prompt = [Step16, Step17, Step18, Step19, Step20]
⚠️ 滑动窗口的危险:早期信息里常有「任务目标」与「关键约束」——这些一旦丢失,Agent 会忘记自己在干什么。滑动窗口应当「保头保尾」——保留任务开头的 Goal 与最近 N 步,丢中间。
定期(如每 5 步)让 LLM 把旧轨迹压缩成一段摘要。
[Step 1-5] → 摘要: "已查询苹果 2023、2024 营收,分别为 383B、391B" [Step 6-10] → 摘要: "已计算增长率 2.0%,正在准备最终回答" 当前 = [摘要1, 摘要2, Step11, Step12, ...]
把轨迹里的「关键事实」抽出来单独存(如存在 Memory 的「事实区」),轨迹本身只留指针。
[事实区] - 苹果 2024 营收: $391B (来自 Step 2) - 苹果 2023 营收: $383B (来自 Step 4) - 增长率: 2.0% (来自 Step 6) [轨迹区] (精简版) - Step 1-2: 查 2024 营收 - Step 3-4: 查 2023 营收 - Step 5-6: 计算增长率
旧轨迹转入向量数据库(第 5 章详述),需要时按相关性检索回来。
💡 生产实践的常见组合:滑动窗口(保头保尾)+ 定期摘要 + 关键事实外存。这三层组合能在绝大多数场景下平衡「保留关键信息」与「控制上下文长度」。第 5 章会进一步讨论记忆的写入与检索策略。
ReAct 循环不能无限转。停止条件分软停止与硬停止两类:
| 类型 | 触发 | 例子 |
|---|---|---|
| 软停止 | LLM 自主判断 | 「信息齐全,输出 Final Answer」 |
| 硬停止 | 工程兜底 | 达到最大步数、超时、Token 耗尽 |
软停止依赖 LLM 自己判断「我该给 Final Answer 了」。这靠两件事保障:
无论 LLM 多聪明,都必须有硬停止兜底:
| 条件 | 默认值 |
|---|---|
| 最大步数 | 10-25 步 |
| 最大时长 | 60-300 秒 |
| Token 上限 | 任务预算 |
| 重复检测 | 连续 3 步相似 |
⚠️ 硬停止不是可选项。生产 ReAct 必须同时设置步数、时间、Token 三道上限,缺一不可。没有硬停止的 ReAct,迟早会在某个边界任务上死循环烧光预算。
把上述各部分串起来,一份生产级 ReAct 的完整流水线如下:
六个步骤:拼 Prompt → 调 LLM → 解析 → 执行 → 得观察 → 上下文管理。每一步都有它的失败模式与兜底——这是 ReAct 从「能演示」到「能生产」的全部工程内容。
| 反模式 | 表现 | 后果 | 正确做法 |
|---|---|---|---|
| 无少样本示例 | 只给格式说明 | LLM 不按格式输出 | 至少 1-2 条完整示例 |
| 正则解析无兜底 | 解析失败直接崩 | 一次异常整轮废 | 错误回喂重试 |
| 全量轨迹不压缩 | 每轮拼全部历史 | 窗口爆炸+贵 | 滑动窗口+摘要 |
| 滑动窗口丢目标 | 只留最近 N 步 | Agent 忘了任务 | 保头保尾 |
| 无硬停止 | 不设上限 | 死循环烧预算 | 步数+时间+Token 三道闸 |
| 示例只有 happy path | 示例都一帆风顺 | 遇错就崩 | 示例含纠错场景 |
下一节《4.3 ReAct 的变体与演进》将讨论 ReAct 的升级路线——Reflexion 引入反思记忆、Self-Ask 自问自答、ReWOO 分离规划与执行、LATS 树搜索式 Agent。