ReAct 已死?原生 Function Calling 与 Agent 循环的真相
范式没变,变的是信封。聊聊 ReAct 和 function calling 到底是什么关系,以及 Claude Code 这类真实产品是怎么把它们揉在一起的。
两个高频词,一场误会
过去几年,LLM Agent 从论文概念变成了生产标配。随之而来的是两个高频词:ReAct 和 Function Calling(tool use)。圈子里流行一种说法——"ReAct 已经过时了,现在都该用原生 function calling",仿佛两者是同一赛道的两个选手,一个被另一个淘汰了。
这个说法对了一半。ReAct 和 function calling 根本不在同一个维度上:
- ReAct 是范式(paradigm)——回答"agent 该怎么思考和行动"。
- Function calling 是机制(mechanism)——回答"工具调用该怎么传输和执行"。
两者不是替代关系,而是实现关系:现代 agent 产品,恰恰是用原生 function calling 这套机制,落地了 ReAct 这套范式。这篇文章不讲空话,我们用真实产品(Claude Code、客服退款 agent、SWE-bench 编码 agent)拆开来看。
01 先分清:范式 vs 机制
ReAct:推理与行动的交响乐
ReAct 出自 2022 年姚顺宇等人的论文 ReAct: Synergizing Reasoning and Acting in Language Models(arXiv:2210.03629)。核心思想一句话:让模型交错输出"推理轨迹"和"具体动作",再用外部环境的"观察结果"驱动下一步。推理帮助模型制定、跟踪、修正行动计划;行动让模型与现实世界交互、收集信息。
典型轨迹长这样:
Thought 1: 需要先查刘慈欣的出生地,确认来源再回答。
Action 1: Search[刘慈欣 出生地]
Observation 1: 刘慈欣(1963年6月23日—),出生于北京。
Thought 2: 观察已给出明确答案,直接回答。
Action 2: Finish[北京]
注意:动作是自由文本。harness 用正则或模板从文本流里抠出动作名和参数,执行后把观察结果拼回对话,让模型继续。
ReAct 的价值:不需要模型专门训练工具调用能力(2022 年也没有);推理过程以文本形式暴露,天然可解释;范式本身很轻,一个 prompt 就能跑起来。
ReAct 的代价:文本解析脆弱(格式一漂移就崩);动作非结构化;很难做并行工具调用;长轨迹里文本和动作混在一起,机器难以精确处理。
原生 Function Calling:结构化的工具协议
Function calling 是模型厂商(OpenAI 的 tool_calls、Anthropic 的 tool_use、Google 的 functionCall)训练出来的结构化输出能力。模型不再用文本写 Action: Search[x],而是直接吐出一个结构化的工具调用块:
{
"type": "tool_use",
"id": "toolu_01ABC",
"name": "read_file",
"input": { "path": "./src/auth.ts", "offset": 1 }
}
harness 侧的 agent loop 长这样(Anthropic Messages API 风格):
messages = [{"role": "user", "content": prompt}]
while True:
resp = client.messages.create(
model="claude-sonnet-4-5",
tools=TOOLS, # 每个工具一个 JSON Schema 定义
messages=messages,
)
tool_uses = [b for b in resp.content if b.type == "tool_use"]
# 没有工具调用 → 循环结束(stop_reason == "end_turn")
if not tool_uses:
break
# 执行工具(可并行),结果回填为 tool_result
results = [run_tool(b.name, b.input) for b in tool_uses]
messages.append({"role": "assistant", "content": resp.content})
messages.append({
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": b.id, "content": r}
for b, r in zip(tool_uses, results)
],
})
看出来了吗——这个循环和 ReAct 在结构上一模一样:模型推理 → 产生动作 → 执行 → 观察结果回填 → 再推理。区别只在传输层:动作是结构化块而不是文本,解析换成了 schema 校验,还支持并行调用多个工具。
打个比方
ReAct 是全剧剧本:角色该想什么、说什么、做什么。
Function calling 是舞台调度:台词怎么递、道具怎么传、场记怎么记。
同一部戏,既可以在老式剧场演(演员念完台词,舞台监督靠耳朵听、靠笔记录),也可以用现代调度系统演(无线耳麦、cue 单、结构化场记)。戏没变,变的是舞台设施。
02 真实场景一:Claude Code——生产级编码 Agent 的活标本
Claude Code(Anthropic 的终端编码 agent)是观察这个问题的绝佳样本,因为它把两端都占了:官方文档明确说它走的是原生 tool use,但它的设计处处透着 ReAct 的骨架。
官方文档 How Claude Code works 把它的循环描述为三阶段:gather context(收集上下文)→ take action(采取行动)→ verify results(验证结果),并说:
The agentic loop is powered by two components: models that reason and tools that act. Claude Code serves as the agentic harness around Claude.
再看它修 bug 的真实轨迹(来自 Agent SDK 文档的示例,"Fix the failing tests in auth.ts"):
Turn 1 Bash npm test → 3 个用例失败
Turn 2 Read auth.ts / auth.test.ts → 拿到文件内容
Turn 3 Edit + Bash 修改 auth.ts + 重跑测试 → 全部通过
Turn 4 纯文本回复 "Fixed the auth bug, all three tests pass now."
这就是一条标准的 ReAct 轨迹:思考(看测试结果)→ 行动(读文件)→ 观察(文件内容)→ 行动(修改+验证)→ 观察(测试通过)→ 收尾。模型会链式跑几十个 tool call 并不断纠偏——正是 ReAct 论文里"用推理更新行动计划、处理异常"的工程化放大版。
而它的"实现细节"暴露在会话记录里:Claude Code 把每轮交互写成 JSONL 存在 ~/.claude/projects/ 下,text / thinking / tool_use / tool_result 块交错排列。这份日志,就是 ReAct 论文承诺的"可解释轨迹"的结构化形态——只是从"人类可读文本"升级成了"机器可读、可回放、可 diff 的日志"。
调试靠的也不是魔法:结构化工具记录(精确到工具名和 JSON 参数)+ thinking 块(部分可见的推理)+ checkpoint 快照回放。三件套缺一不可——其中前两件恰恰对应 ReAct 的"动作"和"推理"两个组件。
03 真实场景二:客服退款 Agent——function calling 的主场
换个场景:一个电商客服 agent,用户说"我上周买的耳机要退货"。
在原生 function calling 下,模型会并行发出三个工具调用:
[
{ "type": "tool_use", "name": "get_order", "input": {"order_id": "A-8821"} },
{ "type": "tool_use", "name": "get_customer", "input": {"customer_id": "U-117"} },
{ "type": "tool_use", "name": "fraud_check", "input": {"order_id": "A-8821"} }
]
三个调用并行执行、一次回填,agent 拿到订单状态、用户身份、风控结果后决定走退款流程。这个场景里 function calling 的优势是碾压级的:
- 参数精确、可审计:每次调用是
order_id、customer_id这样的明确字段,出问题能精确回放"这次用错了参数",而不是在文本里找Search[A-8821]这种可能被截断、被转义污染的字符串。 - 并行:退款决策依赖三个独立查询,ReAct 文本格式只能串行,一个回合一个动作,延迟直接翻三倍。
- 错误边界清晰:参数不合法在进工具前就被 schema 校验拦下,错误归属(模型吐错参数 vs 工具内部报错)一眼可辨。
如果这个 agent 用 ReAct 文本格式硬写,你会收获:正则解析失败、JSON 字符串转义地狱(退款备注里带个引号就崩)、动作串行导致的超时、以及审计时"这段 Action 文本到底对应哪个请求"的猜谜游戏。
04 真实场景三:SWE-bench 编码 Agent——工具设计比 Prompt 更重要
Anthropic 在 Building Effective Agents 里给了个非常直白的定义:
Agents are typically just LLMs using tools based on environmental feedback in a loop.
并强调:"gaining 'ground truth' from the environment at each step (such as tool call results or code execution)" 是 agent 运转的关键。
这句话拆开就是 ReAct 的公式:环境反馈(观察)→ 驱动下一步动作。SWE-bench 编码 agent 的日常循环是:跑测试 → 读报错 → 定位文件 → 修改 → 重跑测试 → 再改……测试输出就是它的 Observation,工具的返回值就是它的 ground truth。
更有意思的是他们的另一个观察:构建 SWE-bench agent 时,"我们花在优化工具上的时间比优化 prompt 还多"——比如把工具参数从相对路径改成绝对路径,模型用错率立刻归零。这跟 ReAct 思想是一脉相承的:工具就是行动空间,行动空间设计得越清晰,模型的推理-行动循环就越可靠。ReAct 论文里模型只能在 Search/Lookup/Finish 三个动作里选,Claude Code 把这个行动空间扩展成几十个经过精雕细琢的工具——范式没变,动作集变大了、变专业了。
05 真实场景四:ReAct 还没死——它是最务实的兜底
说了这么多 function calling 的好话,必须补一句公道话:ReAct 在今天依然大量存在,而且它是很多场景下唯一务实的选项。
- 自托管 / 开源模型没有 function calling 训练:很多本地部署的 7B/13B 模型没有经过 tool calling 微调,你没法用原生机制。ReAct 文本格式是纯 prompt 技巧,任何模型都能跑——这也是开源生态里 ReAct 至今活跃的原因。
- 交互式决策环境:ALFWorld、WebShop 这类 benchmark 的接口本身就是文本动作(
pick up 1 apple from 1 countertop之类),ReAct 格式天然契合,直到今天论文里还在用它。 - 教学与快速原型:手写一个 ReAct loop 只要几十行代码,不用任何 SDK,是理解"agent 到底在循环什么"的最佳入门路径。LangChain 的 ReAct agent 至今还是无数教程的起点。
- 反面教材警告:AutoGPT 当年让模型在文本里"写 JSON 动作",解析层崩了无数回——这就是在缺结构化机制的情况下硬做结构化传输的代价。ReAct 用纯文本没问题,但别伪装成结构。
一句话:有原生支持用原生,没有就用 ReAct 文本兜底,但别用文本硬扮结构化。
06 一张表看懂:什么时候选谁
| 场景 | 建议 |
| 模型原生支持 tool calling(Claude / GPT / Gemini 等) | 用 function calling |
| 自托管开源模型,无 tool calling 训练 | ReAct 文本格式兜底 |
| 环境接口是纯文本(游戏环境、老系统、终端脚本) | ReAct |
| 需要并行工具调用(客服查询 + 风控 + 库存) | function calling |
| 需要精确审计 / 回放(生产、金融、合规) | function calling + 全量日志 |
| 快速原型、教学、理解 agent 原理 | 先从 ReAct 跑通,再迁移 |
07 误区
误区一:"function calling 的执行路径确定、可预测。"
错。function calling 只让单次调用的记录确定(工具名 + 参数可复现),但执行路径——模型下一步调哪个工具——依然是 LLM 采样决定的,跟 ReAct 一样非确定。同一个 prompt 跑两遍,工具序列可能完全不同。结构化的是信封,信的内容照样随机。
误区二:"ReAct 更容易产生长轨迹和循环,所以难调试。"
轨迹长度和循环是 agent loop 的属性,不是 ReAct 文本格式的属性。Claude Code 一个任务链几十个 tool call,比经典 ReAct demo 长得多,照样该循环循环。"循环"是迭代式 agent 的固有特征,换机制一条都不会少。
误区三:"function calling 一定更好调试。"
调用记录更精确是真的,但"模型为什么选这个工具"这个深水区,function calling 反而可能更难看——因为 thinking 块默认常被隐藏或摘要化。ReAct 的文本推理至少是明牌。真正决定可调试性的是可观测性设计:结构化日志 + 推理可见 + 可回放,三者缺一不可。
08 写在最后
回到开头的争论:ReAct 过时了吗?
没有。它只是换了一身更合身的西装。
ReAct 贡献的"推理-行动-观察"闭环,至今仍是所有自主 agent 的底层骨架;function calling 贡献的是让这个骨架在生产环境里跑得稳的结构化传输层。Claude Code 们真正的工程秘密,也从来不是"用了哪种机制",而是三件事:循环怎么设计(何时收手、如何纠偏)、工具怎么设计(行动空间是否清晰)、可观测性怎么设计(出了事能不能回放)。
机制会继续进化——今天的 tool_use,明天可能是 tool orchestration、tool composition。但只要 agent 还需要"先想、再做、再看结果、再想"地完成任务,ReAct 的骨架就不会过时。
参考
- ReAct 论文:[2210.03629] ReAct: Synergizing Reasoning and Acting in Language Models
- Claude Code Docs – How Claude Code works:How Claude Code works - Claude Code Docs
- Claude Agent SDK – How the agent loop works:How the agent loop works - Claude Code Docs
- Anthropic – Building Effective Agents:Building Effective AI Agents \ Anthropic
- Anthropic Tool Use API:https://platform.claude.com/docs/en/build-with-claude/tool-use
- OpenAI Function Calling:https://platform.openai.com/docs/guides/function-calling
更多推荐



所有评论(0)