4.2 ReAct 的工程实现:Prompt 模板、轨迹生成与上下文管理


4.2 ReAct 的工程实现:Prompt 模板、轨迹生成与上下文管理

ReAct 原理简单——「想-做-看」循环。但从原理到生产,要趟过三道坎:怎么让 LLM 老老实实按 Thought/Action/Observation 格式输出?怎么把工具返回嵌进下一轮 Prompt?怎么对付越来越长的轨迹上下文?这三道坎趟不过,ReAct 就停在演示阶段。

4.2.1 工程实现的三道坎

难点 后果
Prompt 模板 怎么让 LLM 按格式输出 格式错乱、解析失败
轨迹生成 怎么把上一步 Observation 拼进下一轮 循环转不起来
上下文管理 轨迹越积越长怎么办 窗口爆炸、注意力稀释

本节逐一拆解。

4.2.2 第一道坎:Prompt 模板

ReAct 的核心是让 LLM 输出结构化的 Thought 与 Action,而不是自由文本。这需要精心设计的 Prompt 模板。

ReAct Prompt 的基本结构

一份基本的 ReAct Prompt 包含四部分:

部分 作用
任务说明 告诉 LLM 「按 Thought/Action/Observation 格式输出」
工具清单 列出可用工具及调用方式
少样本示例 演示一两条完整轨迹(最关键!)
当前任务 真实问题 + 已有的轨迹历史

少样本示例:ReAct 的「教练」

少样本示例(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 输出可被严格解析,避免「调了个不存在的工具」。

4.2.3 第二道坎:轨迹生成

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 输出提取 Action

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 的具体应用。

4.2.4 第三道坎:上下文管理

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, ...]
  • 优点:保留语义、压缩比高。
  • 缺点:摘要本身花一次 LLM 调用,且可能丢细节。

策略三:结构化存档

把轨迹里的「关键事实」抽出来单独存(如存在 Memory 的「事实区」),轨迹本身只留指针。

[事实区] - 苹果 2024 营收: $391B (来自 Step 2) - 苹果 2023 营收: $383B (来自 Step 4) - 增长率: 2.0% (来自 Step 6) [轨迹区] (精简版) - Step 1-2: 查 2024 营收 - Step 3-4: 查 2023 营收 - Step 5-6: 计算增长率
  • 优点:关键信息长期保留、可检索。
  • 缺点:抽取规则需设计。

策略四:外部记忆(接 RAG)

旧轨迹转入向量数据库(第 5 章详述),需要时按相关性检索回来。

  • 优点:近乎无限容量、跨任务积累。
  • 缺点:检索质量决定一切、实现最复杂。

💡 生产实践的常见组合:滑动窗口(保头保尾)+ 定期摘要 + 关键事实外存。这三层组合能在绝大多数场景下平衡「保留关键信息」与「控制上下文长度」。第 5 章会进一步讨论记忆的写入与检索策略。

4.2.5 停止条件与终止判定

ReAct 循环不能无限转。停止条件分软停止硬停止两类:

类型 触发 例子
软停止 LLM 自主判断 「信息齐全,输出 Final Answer」
硬停止 工程兜底 达到最大步数、超时、Token 耗尽

软停止:让 LLM 学会「停下」

软停止依赖 LLM 自己判断「我该给 Final Answer 了」。这靠两件事保障:

  1. 少样本示例里演示「停止时机」:示例要包含「Thought 判断信息齐全后直接 Final Answer」的模式。
  2. Prompt 里明确停止规则:如「当你认为信息足够回答问题时,直接输出 Final Answer,不要再调工具」。

硬停止:工程兜底

无论 LLM 多聪明,都必须有硬停止兜底:

条件 默认值
最大步数 10-25 步
最大时长 60-300 秒
Token 上限 任务预算
重复检测 连续 3 步相似

⚠️ 硬停止不是可选项。生产 ReAct 必须同时设置步数、时间、Token 三道上限,缺一不可。没有硬停止的 ReAct,迟早会在某个边界任务上死循环烧光预算。

4.2.6 ReAct 的完整工程流水线

把上述各部分串起来,一份生产级 ReAct 的完整流水线如下:

六个步骤:拼 Prompt → 调 LLM → 解析 → 执行 → 得观察 → 上下文管理。每一步都有它的失败模式与兜底——这是 ReAct 从「能演示」到「能生产」的全部工程内容。

4.2.7 工程实现的反模式

反模式 表现 后果 正确做法
无少样本示例 只给格式说明 LLM 不按格式输出 至少 1-2 条完整示例
正则解析无兜底 解析失败直接崩 一次异常整轮废 错误回喂重试
全量轨迹不压缩 每轮拼全部历史 窗口爆炸+贵 滑动窗口+摘要
滑动窗口丢目标 只留最近 N 步 Agent 忘了任务 保头保尾
无硬停止 不设上限 死循环烧预算 步数+时间+Token 三道闸
示例只有 happy path 示例都一帆风顺 遇错就崩 示例含纠错场景

本节小结

  • ReAct 工程实现有三道坎:Prompt 模板(让 LLM 按格式输出)、轨迹生成(把 Observation 拼回 Prompt)、上下文管理(对付越来越长的轨迹)。
  • Prompt 模板四部分:任务说明、工具清单、少样本示例、当前任务。少样本示例是 ReAct Prompt 的灵魂——它示范完整的循环模式(含纠错、停止时机)。
  • 轨迹生成每轮做四件事:拼 Prompt → 调 LLM → 解析输出 → 执行 Action 得到 Observation。解析失败是最常见崩溃点,必须有「错误回喂重试」兜底。
  • 上下文膨胀是 ReAct 的核心工程难题。四种压缩策略:滑动窗口(保头保尾)、摘要压缩、结构化存档、外部记忆(RAG)。生产常用组合是「滑动+摘要+外存」。
  • 停止条件分软停止(LLM 自主判断)与硬停止(步数/时间/Token 三道上限)。硬停止是生产强制标配
  • 完整 ReAct 流水线六步:拼 Prompt → 调 LLM → 解析 → 执行 → 观察 → 上下文管理。每步都有失败模式与兜底。

下一节《4.3 ReAct 的变体与演进》将讨论 ReAct 的升级路线——Reflexion 引入反思记忆、Self-Ask 自问自答、ReWOO 分离规划与执行、LATS 树搜索式 Agent。


作者与出处
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 会发光的石头的小龙虾 转发
评论区 (0)
U