为什么 ReAct 不够

May 12, 2025 · 阅读需 7 分钟

ReAct 是一个很重要的起点。我第一次看到这个范式时,感觉它把 Agent 这件事讲得很清楚:模型不是只回答问题,它可以一边想,一边做。

它的循环很简单:

先想一步,调用工具,看结果,再想下一步。这个结构非常适合做原型。搜资料、查 API、读文件、生成一段代码,都能用这个循环跑起来。

但做得久一点就会发现,ReAct 解决的是“怎么让模型动起来”,不是“怎么让系统可靠地跑下去”。

Context 不是 Memory

ReAct 最容易踩的坑,是把 context 当 memory。

用户说过什么、工具返回过什么、模型推理过什么,全都继续塞进 prompt。短任务里没问题,甚至很方便。但任务一长,上下文就开始变成垃圾场:旧日志、失败尝试、过期假设、临时 workaround 都混在一起。

模型并不总能分清哪些是事实,哪些只是某一步的猜测。

比如一个代码 Agent,真正需要记住的是项目约定、用户偏好、已经排除过的路径、当前目标和未完成事项。它不需要把每一次命令输出都背下来。ReAct 本身没有提供这种筛选机制,只是不断把历史往后滚。

所以 ReAct 可以做工作区,但不能替代记忆系统。

线性循环容易走偏

ReAct 的循环是线性的:想一步,做一步,看结果,再想一步。

这个方式很自然,但复杂任务不一定是一条线。真实工程里经常需要并行探索多个假设,需要回到之前的分支,需要把一个大任务拆给多个子任务,也需要在关键点停下来让人确认。

线性循环的问题是,早期判断一旦偏了,后面每一步都可能在错误前提上继续补丁。

模型第一步理解错需求,后面就会越来越努力地完成一个错误任务。工具返回一个模糊结果,模型可能顺着最像答案的方向走下去。单步看都合理,整体看已经偏航。

这不是 prompt 写得不够好,而是控制结构太弱。复杂任务需要计划、分支、检查点和调度。ReAct 更像最小执行单元,不是完整任务系统。

错误不能都交给模型猜

真正区分 demo 和工程系统的,往往不是成功路径,而是失败路径。

工具调用失败怎么办?文件没找到怎么办?测试失败后怎么判断是代码错、测试错,还是环境错?用户中途改需求,当前已经做过的事情怎么收敛?

ReAct 通常把这些都交给模型临场发挥。模型看到错误,再生成下一步。

简单场景里够用。生产环境里不够。因为失败恢复需要结构化信息:当前处于哪个阶段,已经试过什么,失败是否可重试,重试有没有副作用,什么时候需要人工确认。

没有这些机制,Agent 很容易陷入两种状态:要么反复尝试同一个无效动作,要么为了绕过错误做出更危险的动作。

好的 Runtime 应该认真处理失败,而不是把错误文本扔回 prompt 里让模型猜。

安全边界不应该只靠模型自觉

ReAct 还有一个常被低估的问题:它鼓励模型不断行动。

一旦模型能行动,工具权限、数据访问、命令执行、副作用控制都会变得重要。一个只会聊天的模型说错话,代价通常有限;一个能执行命令、改数据库、发请求的模型,如果边界不清楚,风险就完全不同。

单靠一句“不要做危险操作”是不够的。

安全需要系统层面的约束:哪些工具可用,哪些参数要校验,哪些操作必须人工确认,哪些目录可以写,哪些网络请求被禁止,哪些 secret 永远不能进入模型上下文。

模型可以参与判断,但不应该成为最后一道防线。最后一道防线应该是系统。

ReAct 是起点,不是终点

我不觉得 ReAct 错了。相反,它是非常好的起点。它说明了 Agent 的基本形态:推理和行动必须交替发生。

但 ReAct 不够。

它更像 Agent 的内核循环,不是完整操作系统。要让 Agent 从 demo 走向真实工程,还需要状态管理、记忆系统、任务调度、权限模型、错误恢复、观测日志和人类确认。

这些东西听起来没有“一个 prompt 搞定一切”性感,但它们才决定 Agent 能不能长期工作。

未来有价值的 Agent 系统,可能不是谁的 ReAct prompt 写得更漂亮,而是谁能把模型放进一个可靠的运行环境里:知道该记什么、该忘什么;什么时候继续,什么时候停;失败后怎么恢复;哪些事情根本不该做。

ReAct 让模型开始行动。

Runtime 才让行动变得可靠。