现在几乎所有"AI Agent"框架——不管是 Claude Code、各种 Agent 编排工具,还是自己写的一个几十行的工具调用脚本——底层跑的都是同一个骨架:模型不是一次性把答案吐出来,而是反复经历"想一步、做一步、看结果、再想下一步",直到任务完成。这个骨架有个具体的名字,叫 ReAct(Reasoning + Acting),来自 Yao 等人 2022 年的论文《ReAct: Synergizing Reasoning and Acting in Language Models》。这篇聊聊这个循环具体是怎么回事、现代 LLM API 是怎么实现它的,以及实践中最容易踩的几个坑。
一、为什么需要"循环",而不是一次问答#
最朴素的 LLM 调用是一问一答:把问题丢进去,模型直接给答案。这在纯知识性问答上够用,但遇到需要"先查证、再回答"的任务就会露怯——模型只能基于训练时记住的东西编答案,没法真的去查一下现在几点、这个 API 现在返回什么、这段代码跑起来到底报什么错。
思维链(Chain-of-Thought,CoT)改善了一部分问题:让模型把中间推理过程写出来,而不是直接蹦答案,输出质量确实会好不少。但 CoT 的推理始终是"脑内"的——全程不接触真实世界,模型的中间假设没有任何机会被外部事实纠正,一旦某一步的假设错了,后面全盘跟着错,而且模型自己不会发现。
ReAct 的核心改动很简单:在推理步骤之间插入真实世界的动作。模型每"想"一步,可以选择调用一个外部工具(搜索、查数据库、执行代码……),工具的真实返回结果会被塞回上下文,成为模型下一步推理的依据。推理不再是封闭的自言自语,而是和外部世界交替进行的循环——这也是论文标题里 “Synergizing” 的意思:推理指导行动该做什么,行动的结果又反过来修正推理。
二、循环的三个阶段#
ReAct 的一轮循环由三部分组成,通常缩写成 Thought → Action → Observation:
- Thought(思考):模型用自然语言表达当前的推理——我现在知道什么、还缺什么信息、下一步该做什么。
- Action(行动):基于这一步的思考,模型发出一次具体的工具调用(带上参数),或者判断信息已经够了,直接给出最终答案。
- Observation(观察):工具执行的真实结果被追加进上下文,成为下一轮 Thought 的输入。
这三步循环往复,直到模型在某一轮 Thought 之后判断"不需要再行动了",输出 Final Answer,循环终止。整个过程可以画成一个简单的状态机:
问题 → Thought → Action → Observation
↑______________|
(重复 N 轮,直到给出 Final Answer)拿一个具体例子过一遍:问"某个 GitHub 仓库最新一次 release 修了什么 bug"。第一轮 Thought 可能是"我需要先找到这个仓库的 release 列表",Action 调用一个查 GitHub API 的工具,Observation 拿到 JSON 格式的 release 数据;第二轮 Thought 变成"最新那条 release 的 body 里提到了三个 fix,但其中一个 issue 链接需要展开看细节",Action 再调用一次工具去抓那个 issue 的内容,Observation 拿到详情;第三轮 Thought 判断信息已经够回答问题了,直接输出 Final Answer。全程模型没有凭空编造任何一个 bug 描述——每个结论都能追溯到某一轮真实的 Observation。
三、现代 LLM API 里,这个循环长什么样#
ReAct 论文提出时用的是纯文本 prompt 拼接(在 prompt 里手写 “Thought:"、“Action:"、“Observation:” 这几个字段,靠模型模仿格式续写)。现在的 LLM API 把这套模式变成了结构化的原生能力,不再需要靠字符串解析——以 Claude 的 Messages API 为例:
- 模型如果决定要"行动”,响应里会带
stop_reason: "tool_use",内容里包含一个或多个结构化的tool_use块(对应 Action),而不是靠文本里的 “Action: xxx” 这行字。 - 调用方执行完对应的工具后,把结果包装成
tool_result块,塞进下一条消息(对应 Observation)发回去。 - 模型收到 Observation 后继续推理,如果还需要行动,再次返回
tool_use;如果信息够了,返回stop_reason: "end_turn",循环结束。
这个"发请求 → 判断 stop_reason → 执行工具 → 把结果拼回消息 → 再发请求"的过程,手写一个 while 循环就是最直接的实现:
messages = [{"role": "user", "content": question}]
while True:
response = client.messages.create(
model="claude-opus-5", tools=tools, messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
break # 模型给出了 Final Answer
tool_results = [execute(block) for block in response.content
if block.type == "tool_use"]
messages.append({"role": "user", "content": tool_results})值得注意的一个细节:如果一轮响应里模型同时发出了多个 tool_use 块(并行调用多个工具),这些工具的执行结果必须在同一条消息里一次性返回,不能拆成多条消息分批发回去——拆开发会让模型逐渐"学会"不再并行调用,退化成每次只发一个 Action。
这个手写循环之外,SDK 通常也会提供一层封装(比如工具调用循环的辅助函数),把这段 while 循环和工具分发都接管掉,调用方只需要定义好每个工具函数本身。两者本质上是同一个循环,区别只是这个循环是自己控制还是交给库控制。
四、ReAct 和其他几种 Agent 模式的关系#
| 模式 | 推理方式 | 特点 |
|---|---|---|
| Chain-of-Thought | 一次性把推理过程写完,不接触外部世界 | 简单,但假设无法被中途纠正 |
| ReAct | 思考和行动逐步交替,走一步看一步 | 每步都能被真实观察修正,但容易短视,缺乏全局规划 |
| Plan-and-Execute | 先生成一个多步骤的整体计划,再逐条执行 | 全局规划更连贯,但计划一旦定死,中途发现前提错了调整成本高 |
| Reflexion | 在 ReAct 基础上加一层"事后自我反思”,把反思结果存进记忆,指导下一次尝试 | 适合可以重试的任务(比如反复跑测试直到通过),单次循环内不解决短视问题 |
实践中这几种模式并不互斥——很多实际系统是 Plan-and-Execute 定好大方向,具体到每一步子任务时用 ReAct 循环去摸索着完成,本质是"高层用计划兜底全局连贯性,底层用 ReAct 应对执行中的不确定性"。
五、实践中容易踩的坑#
没有终止条件,循环收不住。 模型偶尔会陷入"一直觉得信息不够、不停调用工具"的状态,尤其是工具返回的信息本身有噪音或者和问题不完全对口的时候。生产环境里必须给循环设一个硬上限(最大轮数,或者按 token 数设置一个预算),到达上限后强制中断并让模型基于已有信息给出一个答案,而不是无限跑下去。
上下文被 Observation 挤满。 每一轮 Observation 都会被完整塞进上下文,工具返回的原始数据(比如一整页搜索结果、一份完整的日志)如果不做裁剪,几轮循环下来上下文就被无关细节占满,模型的注意力被稀释,早期给的约束反而被后面的噪音盖过去。这和多智能体协作里"上下文隔离"要解决的是同一类问题——只是这里的隔离手段通常是对工具返回结果做摘要或截断,而不是拆出子智能体。
工具选错或编造不存在的参数。 循环轮数越多,模型偏离最初任务、调用一个看似相关但其实文不对题的工具的概率就越高。给工具定义严格的输入 schema、让 API 校验参数格式,能挡掉一部分"格式对但语义错"的调用,但没法根治"选错工具"本身——这更多要靠工具描述写得足够清晰、工具粒度不要太碎。
把失败的 Action 直接丢弃。 工具调用失败时(比如 API 报错、执行超时),正确做法是把错误信息也作为一次 Observation 返回给模型,让它在下一轮 Thought 里感知到"这条路走不通",而不是静默跳过——静默跳过会让模型继续基于一个从未发生过的假设推理下去。
六、总结#
ReAct 的贡献不是发明了"调用工具"这件事本身,而是把推理和行动交织在一起,让每一步的思考都能被外部世界的真实反馈及时修正,而不是一口气把整个计划憋出来之后才发现前提错了。这个 Thought → Action → Observation 的循环现在已经是几乎所有 Agent 系统的最小公倍数——不管上层套了多复杂的规划、记忆或多智能体调度逻辑,拆到最底层,负责"和外部世界打交道"的那部分,跑的基本还是这个循环。理解了这个骨架,再去看具体框架的实现细节(工具怎么定义、循环怎么终止、上下文怎么裁剪),会发现大多是在这个基本模式上做工程化的取舍,而不是另起炉灶的新东西。
通过邮件回复


