我从去年开始把 ReAct 从“提示词技巧”里拿出来,单独当成一套执行框架来用,结果发现整个项目组对 Agent 的信任度上了一个台阶。
如果你只是把 ReAct 当成“先推理再行动”的模板,那确实低估它了。它真正的价值在于——把 LLM 的思考轨迹变成可审计、可追溯、可复核的执行证据。这不再是提示词层面的优化,而是工程层面的基础设施。
这篇文章我会从为什么 ReAct 不等于提示词技巧讲起,再到如何设计一套完整的轨迹审计方案,最后聊几个我实测踩过的坑。适合正在做 Agent 落地、需要应对监管审查或内部合规要求的开发者。
1. 重新理解 ReAct:从提示词到执行框架
1.1 主流误解:ReAct 只是“思考-行动”的提示词模板
很多人第一次接触 ReAct,看到的例子都是这样的:
Thought: 我需要查询天气 Action: search_weather["北京"] Observation: 晴,25度 Thought: 天气良好,可以出行 Answer: 适合出行于是下意识把它理解为“一种让模型更好地回答问题的提示词格式”。这个理解不能说错,但它把 ReAct 的高度拉低了。提示词模板解决的是“单次对话中模型如何组织回答”,而 ReAct 实际上是一套让 AI 系统在复杂任务中持续决策、持续行动的运行机制。
我见过不少团队,把 ReAct 写进 system prompt 里,然后发现效果时好时坏。原因很简单——他们只在“提示词”层面用了 ReAct,却没有在“系统架构”层面为它配套执行环境。模型每次推理出来的 Thought 和 Action 没有被单独捕捉、存储、校验,而是直接融进了最终的 answer 里。这样一来,思考轨迹就变成了不可见、不可查的中间产物。
这和没有 ReAct 有什么区别?唯一的区别是回答格式更好看了,但系统依然是个黑盒。
1.2 ReAct 的本质:决策过程的结构化单元
完整的 ReAct 循环包含四个关键部分:Thought(推理依据)、Action(执行动作)、Observation(结果观察)、以及循环终止后的 Answer(最终回答)。这四个部分天然形成了一个结构化单元,每次循环都对应一次完整的“决策-执行-反馈”闭环。
我们把一次循环展开来看:
- Thought 记录的是模型为什么做出这个决定。这是推理的显式化。
- Action 记录的是模型选择的工具和输入参数。这是行动的规范化。
- Observation 记录的是工具返回的原始结果。这是反馈的实体化。
这三者在时间轴上严格串行,并且前后依赖。这就意味着,只要我们把每一次循环的原始数据完整保留下来,就能在事后还原这次决策的完整链路:它是基于什么信息推理的,调用了哪个工具,拿到了什么结果,然后如何修正下一步。
这不就是审计所需要的全部要素吗?
传统软件开发里,我们审计一个函数的行为,靠的是日志、参数、返回值。ReAct 每轮循环的思想和行动,本质上就是这个函数执行过程的“日志、参数、返回值”——只不过它们都由 LLM 动态生成,所以我们更容易忽视它们的可审计性。
1.3 为什么审计能力是 ReAct 的真正价值
过去两年我做过的 Agent 项目里,几乎每一个业务方都会问同一个问题:“它在执行这个任务的过程中,到底经过了哪些判断?如果结果错了,是哪个环节出的问题?”
如果系统只是“输入→输出”,这个问题几乎没法回答。而 ReAct 天然把中间过程拆成了可见的片段,这就是它区别于普通提示词技巧的最关键一点——它不只是“让模型更好地完成任务”,更是“让任务完成的过程可以被复查”。
举例来说,在一个客服工单自动分级系统里,Agent 收到用户投诉后,会先推理“这属于投诉还是咨询”,再查询订单状态、比对历史记录,最后给出分级判断。如果这个 Agent 用普通 Prompt 实现,系统只输出“判定为紧急投诉”,你根本不知道它依据了什么。但如果用 ReAct 框架实现,每一步都留下轨迹:
- 它看到了哪几封历史工单?
- 它基于什么关键词判断“紧急”?
- 它调用了什么接口验证用户身份?
- 其中有没有一步跑偏?
这些问题,全部可以从轨迹中还原。
所以我说,ReAct 的真实身份是“可审计执行框架”,而不是“提示词技巧”。当我们以审计为目标去设计 ReAct 系统的时候,才能真正发挥它的潜力。
2. 把思考轨迹变成执行证据:结构设计的关键
2.1 证据链的最小单元设计
既然要把轨迹当证据用,首先要定义“一个证据单元”包含什么。我见过很多项目直接在日志里塞一段 JSON,把 Thought、Action、Observation 串进去,看起来完整,真到了排查问题时候就会发现细节缺失严重。
我的建议是一个最小证据单元必须包含以下字段:
| 字段 | 用途 | 示例 |
|---|---|---|
| turn_id | 当前循环的序号 | 3 |
| timestamp | 发生时间(精确到毫秒) | 2025-01-01 10:00:00.123 |
| session_id | 所属会话/任务ID | task_xxx_001 |
| thought | 模型的原始推理文本 | 用户反馈语气强烈,判断为紧急投诉 |
| action_name | 匹配到的工具名 | check_order_status |
| action_input | 传给工具的具体参数 | {"order_id": "A10086"} |
| observation_raw | 工具返回的原始数据 | {"status": "已发货", "delay": "3天"} |
| observation_summary | 对原始结果的摘要 | 订单延迟3天,客户诉求合理 |
| token_usage | 本轮消耗的token数 | 1540 |
| error_flag | 本轮是否出现异常 | false |
这里特别要说一下 observation_raw 和 observation_summary 的区别。原始数据必须完整保留,因为它是事实依据。而 summary 是模型对事实的一种解读,属于推理的一部分。分清两者,审计的时候才能明白:是哪一步的事实读错了,还是基于正确事实的推理错了。如果不做区分,一旦结果出错,你很难定位是工具返回垃圾还是模型理解错误。
toke usage 这个字段很多系统会忽略。但对审计来说,它非常重要——它记录了模型消耗了多少算力,这在后置复核中能用来判断成本归属。
2.2 状态快照与动作留痕
除了记录每轮循环,还要把“系统在执行过程中的状态”一并记录。这里涉及到一个概念:可观测的状态快照。
Agent 执行过程中,不是所有状态都在工具返回值里。比如它的内部 memory 更新情况、它当前维护的待办列表、或者它已经完成的子任务集合,这些属于会话内部状态。审计时如果不记录这些,中间发生了什么就变成推断而不是实证——那就不够“证据”了。
实际操作中,我会在每个 turn 结束后,额外生成一次内部状态快照:
{ "turn_id": 3, "state_before": { "pending_subtasks": ["查询退款进度", "确认用户身份"], "memory_highlights": "用户上一次投诉未解决" }, "state_after": { "pending_subtasks": ["确认用户身份"], "memory_highlights": "订单延迟3天,用户不满" } }这个快照相当于给执行过程拍了张照片。有了它,我们才能在事后完整复盘:这个 Agent 在什么时候开始丢失上下文?它是什么时候把某个任务错误地判定为“已完成”的?如果没有状态快照,你只能茫然地翻聊天记录猜原因。
动作留痕则是把每次工具调用的 HTTP 参数、Headers、返回码,甚至调用耗时都记录下来。这一层很多开发者会认为“这是中间件该干的事情”,但实际项目中,把这一层和 ReAct 轨迹联合起来,审计证据就形成了一个完整闭环:
- Thought 层面说明了模型意图。
- 行为快照说明了模型状态。
- 动作留痕说明了系统实际发生了什么。
三者的交集,就是不可抵赖的执行事实。
2.3 可复现性:种子、版本与环境记录
审计不只是“事后看”,最关键的能力是“事后复现”。如果一条轨迹只能看不能重跑,那它的证据价值就打了折扣。为了做到这一点,我建议在轨迹记录中加入环境信息:
- 模型名称与版本号
- Prompt 模板的 hash 值
- 工具函数的版本号
- 温度等采样参数
- 随机种子(如果有)
- 依赖库版本列表
千万不要忽略 Prompt 版本。ReAct 系统的行为高度依赖 Prompt 内容,哪怕只是多加了半句话,整个决策路径都可能变化。如果审计时发现行为异常,而你能通过 hash 对比确认“当时跑的 Prompt 是哪一版”,就能快速定位是 Prompt 调整导致还是代码逻辑故障。
有一个非常典型的案例:某公司一次自动化审批 Agent 出现误判,保险起见,审计员复盘轨迹,发现模型的 thought 在没有依据的情况下主动认定“该用户符合白名单条件”。排查到最后,原因竟然是系统提示词里一个逗号的位置改变了模型对白名单判定标准的理解。如果没有 Prompt 版本记录,没人能解释这个 mystery。
所以在落地 ReAct 时,设计一套版本锁定机制,和设计业务逻辑同等重要。
3. 把证据落地:审计系统的实现路径
3.1 从轨迹到证据的存储层设计
轨迹要变成证据,核心是“不可篡改”和“可检索”。简单往数据库里插记录是不够的,审计要求我们保证记录本身没有被偷偷修改。
这里分享一个实用方案:轨迹双重存储。
- 第一层是热存储。用普通的 JSON 文档数据库(比如 MongoDB)保存所有轨迹,方便日常查询和开发调试。
- 第二层是冷存储。把原始轨迹序列化后,计算 hash,写入 WAL(Write-Ahead Log)文件,并同步到对象存储或本地只读磁盘。这样即使热库被篡改,冷存储的 hash 链也能对照发现问题。
哈希链的做法和区块链的思路类似:每个轨迹块的 hash 包含了上一个轨迹块的 hash:
block_i_hash = sha256(prev_hash + turn_i_data + timestamp)这样任何一环的数据被改动,后续所有 hash 都会失配。审计时只要从末端向前校验,就能定位被篡改的位置。
这个方案不需要引入复杂的区块链基础设施,一个简单的 Python 脚本就能实现。我之前用类似方案搞定过一个需要向审计方提供证明的项目需求,效果很稳。
可检索性方面,按 session_id 建索引,按时间范围建索引,这两个是最常见的检索路径。另外强烈建议为 action_input 和 observation_raw 建全文索引,因为大多数误判排查都需要搜“当时它给工具传了什么参数”。
3.2 轨迹索引与检索查询示例
网上很多文章只谈如何记录,不怎么讲怎么查。实际用起来,查询往往比记录更复杂。
我列一个最常用的排查场景:已知一个任务出错了,要找出 Agent 在哪一个 turn 开始行为异常。
SELECT turn_id, thought, action_name, action_input, observation_summary FROM trajectory WHERE session_id = 'task_xxx_001' ORDER BY turn_id ASC;这个查询很简单,但它揭示的事实量很大:看 thought 的变化,你就能发现模型是在第几轮开始产生认知偏移,是在第几轮把一个已经错误的分支当成了既定事实。
更进阶一点的查询是“轨迹交叉比对”:同一时间窗口内,多个会话出现相同错误动作。这类问题通常是工具本身返回了异常数据,或者 Prompt 调整引起的群体性偏移。这时候按 action_name 和 timestamp 聚合就能快速发现:
SELECT action_name, COUNT(*) FROM trajectory WHERE timestamp BETWEEN '2025-01-01 10:00:00' AND '2025-01-01 10:30:00' GROUP BY action_name ORDER BY COUNT(*) DESC;如果某个动作集中爆发,而且 action_input 里的参数高度相似,基本可以判断是上游数据批量异常,而不是模型随机抽风。
3.3 证据校验与异常检测
存储和检索只是手段,审计的最终目的是“发现异常”。这一步我分为规则校验和模型校验两类。
规则校验指的是确定性校验,不依赖 LLM,用代码直接判断轨迹是否合规:
- 是否出现工具名映射失败(Action 不存在的工具)
- 是否出现非法参数(有字符串传成了整数)
- Thought 是否过短或为空(模型跳过了推理)
- 循环是否出现死循环(超过最大轮数)
- Observation 是否为空或超时
这些是基本功,任何一个 ReAct 项目都应该实现。
模型校验则是利用第二个 LLM 实例来审计第一个 LLM 的轨迹,这算是当前兼顾性价比和可靠性的做法。审计 Prompt 大概是这样的:
你是一个审计员。给定以下 Agent 执行轨迹,检查每一步 Thought 是否能够逻辑推导出下一步 Action。如果出现推理与实际行为不一致,标记该步为可疑。 轨迹: [轨迹JSON]实测中,这种“用模型审模型”的方式确实能发现规则规则抓不到的问题。比如模型在 Thought 里写“用户诉求已解决”,但 ACTION 却调用了“发起退款”接口,这种逻辑断层,纯规则很难判定,因为参数完全合法,只有语义层面能看出来不合理。
但注意:“用模型审模型”不是银弹。首先它本身就有误判,其次审计 LLM 的推理也需要记录。我的建议是把它作为第二道过滤,用来挑选需要人工重点检查的轨迹,而不是完全替代人工。
4. 实操中的常见问题与排查心得
4.1 Action 溢出与审计断链
先说一个非常隐蔽的问题。ReAct 循环如果执行轮次太多,超出模型的 context 窗口,就会导致早期轨迹被截断。你最后拿到的轨迹不完整,审计时发现前几轮只有 Thought,没有 Observation,或者干脆丢失了。
这种情况不是模型“突然失忆”,而是系统没有做上下文裁剪策略。很多团队为了省事,把所有轨迹都放进 prompt 里传给下一轮,结果早段的证据链被挤出了窗口。
我的处理方案是压缩不截断:
- 对前序轮次的 observation_raw 做摘要,保留 summary。
- 截断前在摘要后面附上原数据的 hash,需要完整数据时按 hash 去存储层取。
- 保证进入 prompt 的内容不超窗口上限,同时保证存储层全军完整。
这样既不影响长任务执行,也不破坏审计数据的完整性。但注意:摘要生成本身也是 LLM 调用,它也会引入信息损失。为了防止摘要过程引入事实扭曲,一定要保留 raw 数据的存储地址。
4.2 幻觉性思考与证据不符
有一种情况很让人头疼:模型在 Thought 里编造了一个不存在的观察结果,然后基于这个幻觉继续推理。现象是轨迹很完整、逻辑连贯,但关键事实是错的。比如明明工具返回“未查询到订单”,Thought 却写了“订单存在,用户已购买”。
这种幻觉性思考往往是审计中最难发现的,因为你不能只靠检查格式来找问题。我总结出了两个比较有实战效果的检查法:
第一,Action 与 Observation 的一致性校验。调用了同一个接口,返回的字段是确定的。可以提前为每个工具定义 schema,观察结果必须符合这个 schema,如果 summary 里出现了 schema 中没有的字段名,就是可疑信号。
第二,跨轮信息一致性校验。让审计模型重点对比 Observation_summary 和下一轮 Thought 的引用语言,看看是否出现了“信息增强”——即 Thought 声称看到的事实,在 Observation 里根本不存在。这就是典型的幻觉出发点。
有些团队为了彻底杜绝幻觉影响决策,会要求模型在 Key 决策节点必须粘贴原始 Observation 的引用片段,而不是自行转述。这个做法增加了 token 消耗,但确实大幅提升了轨迹证据的可采信度,在金融、政务这些对合规要求高的场景非常值得。
4.3 权限与安全审计的联动
最后聊一个比较容易被忽略的:ReAct 轨迹审计和权限审计必须联动。
什么意思呢?当 Action 调用 API 时,它使用的是服务账号的权限。如果某个 Agent 在一次任务中意外调用了高权限接口,即使轨迹里的 Thought 是正确的、Action 是合规的,这次执行本身就构成了越权风险。
所以我在轨迹表里增加了一个字段:actual_permissions 和 required_permissions。
- required_permissions 是根据 Action 定义推测它应该需要的权限。
- actual_permissions 是执行时实际校验通过的身份权限。
审计时直接对比这两个值。如果 required 明显小于 actual,就说明 Agent 用了超出必要的权限做了一件小事——这不一定是谁写错了,也有可能是 ReAct 系统错误地选择了某个高权限工具。
这个设计把“AI 行为审计”接入到了传统的“身份与权限审计”里,两者结合后,整个证据链才真正闭合,既能回答“它做了正确的事吗”,也能回答“它有权这样做吗”。
我在实际项目中,还遇到过因为工具描述不清晰,模型把“只读接口”误认为“可写接口”的教训,原因就是工具文档里没有明确标注影响范围。后来在工具定义里强制增加权限标注字段,把这个字段作为 Action 选择模型时的判断依据,误用率下降非常明显。
4.4 常见三大坑速查表
| 坑点 | 表现 | 排查方向 | 预防方案 |
|---|---|---|---|
| 上下文裁剪导致轨迹断裂 | 早期轮次缺失 | 检查 context window 使用 | 压缩不截断,摘要替换 raw |
| Thought 事实幻觉 | 思考和实际观察不符 | 对比 Observation 与 Thought | 强制引用原始片段 |
| 工具权限滥用 | 低需求调用高权限 | 对比 required/actual permissions | 工具定义中标注权限级别 |
| Prompt 版本漂移 | 同样输入结果变化 | 反思 Prompt hash | 每次执行记录 Prompt 版本 |
| 状态快照缺失 | 无法定位上下文丢失 | 检查 state_before/after | 每轮生成快照 |
| token 消耗无法归属 | 成本无法分摊 | 检查 token_usage 字段 | 记录每轮 token 消耗 |
每次我接手一个 ReAct 系统的审计排查,基本上都是在这张表里对号入座。它不能覆盖所有情况,但能帮你快速定位 80% 的问题。
5. 总结性建议:从“能用”到“可证”
我个人在实际操作中的最大体会是,ReAct 的落地有一个顺序:先保证任务能跑通,再保证过程能还原,最后保证行为能证明。很多人顺序反了,一上来就想审计,但 Agent 本身的工具调用都是乱的,轨迹记下来也是一堆垃圾。
第一步,把工具定义写清楚。每个工具必须明确输入输出 schema、影响范围、权限级别,这是 ReAct 能做出正确 Action 的前提。
第二步,把轨迹 JSON 完整记录下来。Thought、Action、Observation 三个字段一个都不能省,同时记录原始返回数据,不要只记摘要。
第三步,建立版本锁定。包括 Prompt 版本、模型版本、工具版本。
第四步,实现哈希链冷存储。这一步能让你在任何时候证明“这条轨迹从未被篡改”。
第五步,做双层校验。规则校验打底,LLM 审计和人工复核作为补充。
最后再分享一个细节技巧:调试 ReAct 系统的时候,不要看最终 answer 来判断执行是否成功。一定要把某一条 session 的轨迹完整打印出来,一行一行看 Thought 的变化。很多隐蔽的劣化不是某一步突然爆掉的,而是一轮一轮推理偏差逐渐累积出来的。轨迹就是这个过程的 X 光片,你能清晰看到病灶从哪一帧开始出现。
ReAct 的价值,只有在你能全景回放过程的时候才会真正体现出来。它不是锦上添花的提示词,而是让 AI 从“不可解释的工具”迈向“可审计的执行者”的关键框架。用好了,你的 Agent 不仅能干活,还能为每一件干过的活负责。