什么是 ReAct 循环
单 Agent 的核心组成,就是 ReAct,可想它很重要,然而并不难懂,我们来看看怎么个事吧!
最近在啃一些关于 AI Agent 的开源项目,有一个基于 Java 写的,类 CC 的 Agent CLI ,在学习的过程中发现,不管绕到哪,最后都会绕回同一个词——ReAct。
这词你要没听过,第一眼八成以为跟 React 前端框架有点关系。不是,一点关系都没有。接下来我就用通俗些的描述把它讲明白,讲完你就会明白:AI 能“自己干活”,靠的就是这个东西。
Chat 和 Agent,差在哪
我们平时跟大模型聊天,是你发一句、它回一句,完事。你问“1+1 等于几”,它回“2”,这一轮就结束了,它不会自己去验证一遍。
Agent 不一样。你让它“帮我看看项目里有哪些 TODO 没处理”,它不是直接给个答案,而是:
先想想 → 决定去读文件 → 读完了看看结果 → 再想想 → 决定再去搜一下 → …… 直到它觉得摸得差不多了,才把结论整理给你。
感受一下这个区别:Chat 是一次性的,一问一答;Agent 是个循环,它自己在那儿转,转到满意为止。这个循环,就叫 ReAct。
ReAct 三个字母,就是三个动作
ReAct 拆开是 Reasoning(推理)+ Acting(行动)。落到循环里,是三个动作反复转:
想(Thought)→ 做(Action)→ 看(Observation)
举个简单的例子,你让 AI 烧壶水,它不会凭空变出一壶开水,它是这么干的:
想:得先接水 → 做:拧开水龙头 → 看:壶满了 → 再想:该插电了 → 再做:插上电 → …… 直到看到水开了,才跟你说“烧好了”。
看出来没,AI 在这里就是个只会想、不会亲手干活的脑子,工具是它的手脚,ReAct 循环就是把“想”和“做”串起来的那根线。想一步,做一步,看一眼,再想下一步。
那它怎么知道该停了?
这是我觉得最反直觉的一点。
我一开始以为,得有个啥监控机制盯着它,看它干完就喊停。其实没有。停不停,是 AI 自己决定的。
每次“想”完,AI 的输出就两种可能:
- 带了“工具调用”(我要去读个文件)→ 程序照做,把结果喂回去,循环接着转;
- 纯文本(“项目里一共 3 个 TODO,分别在……”)→ 程序一看没带工具调用了,就知道它干完了,把文本给你,循环结束。
说白了,“开口跟你汇报”这个动作,本身就是“我停了”的信号。人也一样,你不会一边说“水烧好了”,一边还拧着水龙头。
AI 是怎么下命令的?
顺着上面说,AI 想完要“下命令”调工具,它总不能像跟你聊天一样来一句“帮我读一下那个文件吧”——程序听不懂人话。
它输出的是一段规规矩矩的 JSON,就俩字段:工具名 + 参数。长这样:
{
"name": "read_file",
"arguments": { "path": "src/main/java/com/paicli/agent/Agent.java" }
}
程序拿到这段,才知道“哦,去调 read_file,路径是这个”。
那问题来了:AI 怎么知道手上有哪些工具能调?它天生不知道。这些工具清单是程序喂给它的——而且每次调用都喂一遍。为啥是每次?因为 AI 是无状态的,它记不住上一轮给过它什么,所以每轮都得重新把“菜单”摆一遍:这工具叫什么、能干什么、要什么参数。
工具干完的结果,为什么不能扔
这是我最想说的一点,也是我琢磨最久才转过来的。
工具干完活了,结果必须存回对话历史,不能执行完就扔。为什么?还是因为那个“无状态”。
AI 每次被调用,都像个失忆的演员——它根本不记得上一幕自己演了啥、工具返回了啥。那怎么办?程序只能每次开演前,把整个对话历史从头念给它听一遍。
所以工具结果要是不存回去,下一轮的 AI 就是个“下了命令、却没收到回音”的脑子,它只能瞎猜“到底执行成功没”。这循环就废了。
万一它转不停怎么办
还有个现实问题:AI 要是特别轴,一直觉得自己没干完,就是不肯输出纯文本,一路调工具调下去,那 token 和钱不就烧没了?
程序也不是干瞪眼,一般会设几道闸兜底:
- 最大步数:转到第 50 步还不收手,强制打断;
- Token 预算:上下文塞太满就压缩,压不动就强制收尾;
- 超时:单步卡死了,秒表到了就叫停;
- 手动取消:你不想等了,按个
/cancel。
注意,这四步都是“兜底”的,不是主开关。正常情况,循环还是靠 AI 自己说停。
落到代码,就一个 while(true)
扯了这么多,你可能会问:这循环代码里长啥样?
我啃的这个 AgentCLI 源代码时,它就写在 Agent.java 的一个 while (true) 循环里。主干就那么几行(我精简过,去掉了噪音):
while (true) {
// ① 拿“菜单”:取工具清单
var tools = toolRegistry.getToolDefinitions();
// ② 调 AI:把「对话历史 + 工具清单」一起传过去
var response = llmClient.chat(conversationHistory, tools);
// ③ 这轮带工具调用吗?
if (response.hasToolCalls()) {
// 带 → 执行工具,结果存回对话历史,continue 回顶部再“想”
var results = executeToolCalls(response.toolCalls());
for (var r : results) conversationHistory.add(Message.tool(r.id(), r.result()));
continue;
}
// ④ 不带 → 纯文本,直接返回,循环结束
return response.content();
}
看到没,前面聊的一整套,浓缩下来就是这个死循环:拿菜单 → 调 AI → 有工具调用就执行、存结果、continue 回去再想;没有就 return。就这两条路,没有第三条。
收尾
如果这篇你只记一句,就记这句:
Agent 不是更聪明的 Chat,而是把“想 → 做 → 看”这个循环跑起来的 Chat。ReAct,就是这个循环的名字。
搞懂这个,后面再看“工具调用”“上下文压缩”“多 Agent 协作”,就都有地基了——它们全是这个循环长出来的枝。