[!WARNING] 可以在本文看完后,去旧文档看看以前的评论,也值得学习[架构设计:ReAct设计模式核心原理](/oncall/智能 OnCall Agent 项目/旧文档备份/架构设计:ReAct设计模式核心原理/)

ReAct 设计模式:让 AI 学会「边想边做」

大家好,今天咱们来聊一个 AI Agent 领域非常核心的概念——ReAct

如果你正在学习 AI Agent、对话机器人,或者想搞明白 ChatGPT 是怎么调用工具的,那这篇文章你一定要看完。我会从最基础的地方讲起,一步步带你搞懂 ReAct 到底是什么、怎么实现的、以及它为什么这么重要。

📷 [图片 token=H1vQbmEzVoEeRix7lbJcplvvnjs(未能下载,见飞书原文)]

一、ReAct 到底是什么?

📷 [图片 token=Kki3bkgrIoMy4kxISBWctzC1nwc(未能下载,见飞书原文)]

先说结论:ReAct = Reasoning(推理)+ Acting(行动)

一句话概括:让 AI 像人一样,边想边做,边做边调整。

你可能会想:AI 不是直接就能回答问题吗,为什么还需要「边想边做」?

好,我举个例子你就明白了。

一个 AI 回答不了的问题

假设你问 AI:

“地球和火星的质量加起来是多少?”

这个问题看起来简单,但 AI 其实很为难——它的训练数据里未必记住了精确的行星质量数值,就算记住了也可能不准确。更关键的是,它不会做精确计算,大语言模型本质上是在"预测下一个词",不是在算数学题。

那怎么办?

如果是你,你会怎么做?你大概率会这样:

  1. 想一想:我需要地球的质量和火星的质量,然后把它们加起来

  2. 查一查:打开搜索引擎,先查地球质量 → 得到 5.972 × 10²⁴ kg

  3. 再查一下:继续查火星质量 → 得到 6.42 × 10²³ kg

  4. 算一算:掏出计算器,5.972e24 + 6.42e23 = 6.614e24

  5. 回答:加起来大概是 6.614 × 10²⁴ kg

看到没?你作为一个人类,在解决这个问题的时候,经历了一个 “想→做→看结果→再想→再做” 的循环过程。

ReAct 就是让 AI 模仿你这个过程。

用做菜来类比

还可以换个更生活化的例子。想象你第一次做红烧肉:

  • 思考:“我得先知道红烧肉怎么做,需要什么调料、什么火候……”

  • 行动:打开手机,刷抖音搜"红烧肉教程"

  • 观察:教程说要炒糖色,用冰糖最好

  • 再思考:“可是家里只有白糖啊,白糖能代替吗?我再搜搜看”

  • 再行动:继续搜索"白糖代替冰糖炒糖色"

  • 再观察:搜索结果说可以,但颜色会稍浅

  • 结论:好的,那我就用白糖来做

这个"想→做→看→再想"的循环,就是 ReAct 的精髓。

正式定义

用稍微正式一点的话来说,ReAct 是一个思考→行动→观察→再思考的闭环机制:

步骤做什么举个例子
Thought(思考)分析问题,决定下一步该做什么“我需要先查地球的质量”
Action(行动)调用外部工具获取信息调用「查星球质量」工具,输入"地球"
Observation(观察)查看工具返回的结果工具返回:5.972 × 10²⁴ kg
循环 / 结束信息够了就回答,不够就继续循环“还缺火星的质量,继续查”

这个循环会一直进行,直到 AI 认为"信息够了,我可以回答用户了",才会跳出循环给出最终答案。

二、最早的 ReAct 是怎么实现的?(古法手搓版)

了解了 ReAct 的思想,你可能会好奇:代码层面到底是怎么做到的?

最早期的 ReAct 实现,说实话,有点"原始"——靠的是 Prompt 工程 + 字符串解析

我给你拆解一下。

📷 [图片 token=UgBMb8Q0koLIsYx6cxIcn2MQnXe(未能下载,见飞书原文)]

第一步:用 Prompt 告诉 AI “你该怎么输出”

核心思路是:在 System Prompt 里严格规定 AI 的输出格式,让它必须按照 Thought → Action → PAUSE 的套路来回复。

System Prompt 大概长这样:

你是一个 ReAct 代理,遵循 思考→行动→暂停→观察 的循环来解决问题。

工作流程:
1. Thought:描述你的推理过程
2. Action:执行一个动作(格式:Action: 工具名: 输入)
3. PAUSE:停下来,等待工具返回结果
4. Observation:分析工具返回的结果

可用工具:
- calculation:数学计算(如 "5*7/4")
- planet_mass:查询行星质量(如 "Mars")

看到了吧?我们是在用自然语言"约束"AI的行为——你必须按这个格式输出,不能乱来。

第二步:代码解析 AI 的输出,调用对应工具

AI 收到这个 Prompt 后,它会乖乖按格式输出,比如:

Thought: 我需要地球和火星的质量,先查地球的。
Action: planet_mass: Earth
PAUSE

然后我们的代码要做的事情就是:用正则表达式去解析这段文本,把 Action: planet_mass: Earth 拆出来,知道要调用 planet_mass 这个函数,参数是 Earth

调用完函数拿到结果后,我们把结果包装成 Observation 塞回给 AI:

Observation: Earth has a mass of 5.972 × 10^24 kg

然后 AI 继续思考、继续行动……如此循环,直到它不再输出 Action,而是直接给出 Answer,循环就结束了。

完整执行日志

咱们来看一个完整的例子,问题是:“地球和火星的质量加起来是多少?”

Question: What is the mass of Earth plus Mars?

-------- step 1 --------
Thought: 我需要地球和火星的质量,先查地球的。
Action: planet_mass: Earth
PAUSE

→ [代码解析出工具名 planet_mass,参数 Earth,执行后返回结果]
Observation: Earth has a mass of 5.972 × 10^24 kg

-------- step 2 --------
Thought: 有了地球质量,还缺火星的,继续查。
Action: planet_mass: Mars
PAUSE

→ [代码解析出工具名 planet_mass,参数 Mars,执行后返回结果]
Observation: Mars has a mass of 6.4171 × 10^23 kg

-------- step 3 --------
Thought: 两个质量都有了,需要计算它们的和。
Action: calculation: 5.972e24 + 6.4171e23
PAUSE

→ [代码解析出工具名 calculation,参数为算式,执行后返回结果]
Observation: 5.972e24 + 6.4171e23 = 6.614e24

-------- step 4 --------
Answer: 地球和火星的质量加起来约为 6.614 × 10²⁴ kg。

4 次循环,3 次工具调用,最终给出答案。整个过程 AI 一直在"想→做→看→再想"。

用伪代码梳理流程

def query(question):
    messages = [system_prompt, question]  # 初始化对话

    for i in range(max_turns):            # 最多循环 N 轮
        result = call_llm(messages)       # 1. 把 prompt 发给 AI

        if "Action:" in result:           # 2. AI 输出里有 Action 吗?
            tool, input = parse(result)   #    有 → 正则解析出工具名和参数
            observation = run_tool(tool, input)  # 3. 执行工具,拿到结果
            messages.append(f"Observation: {observation}")  # 4. 结果塞回去
        else:
            return result                 # 5. 没有 Action → 最终答案,结束

核心就这么几行代码,本质上就是一个 while 循环 + 字符串解析。

古法 ReAct 的问题

这种方式虽然能跑通,但有个很大的硬伤:一切都靠字符串

工具的定义是字符串写在 Prompt 里的,AI 的调用格式也是字符串,我们解析也是靠正则匹配字符串。

这就带来几个问题:

  1. 格式脆弱:AI 有时候不完全按格式输出,比如多了个空格、换了个冒号,正则就匹配不上了

  2. 复杂参数搞不定:如果工具的输入是一个嵌套的 JSON 对象(比如 Map 套 Map),用字符串来描述和解析就是噩梦

  3. 错误处理全靠自己:AI 输出了一个不存在的工具名怎么办?格式错了怎么办?全得手动写异常处理

  4. 开发成本高:每个项目都要自己设计格式、写解析逻辑、处理各种边界情况

说白了,古法 ReAct 就像是在用胶水和绳子把零件绑在一起——能用,但不够稳,也不够优雅。

三、现代 ReAct 是怎么实现的?(Function Call 版)

大模型厂商也意识到了古法 ReAct 的痛点,于是推出了一个重要特性:Function Call(函数调用)

简单来说,Function Call 就是把"古法 ReAct 的手工活"标准化了——用 JSON 来统一工具的定义和调用格式

📷 [图片 token=HSemb6XrVo8VjwxVSygcwMXpnvb(未能下载,见飞书原文)]

什么是 Function Call?

你可以把 Function Call 理解为大模型厂商制定的一套"协议":

  • 工具怎么描述:用标准的 JSON 格式来定义工具的名字、功能、参数类型

  • AI 怎么调用工具:AI 不再输出自然语言字符串,而是直接返回结构化的 JSON

  • 结果怎么回传:工具的返回结果也按照规定的格式传回给 AI

举个例子,以前古法 ReAct 里,工具描述是这样的(写在 Prompt 文本里):

可用工具:
- calculation:数学计算(如 "5*7/4")
- planet_mass:查询行星质量(如 "Mars")

现在用 Function Call,工具描述变成了标准的 JSON:

{
  "name": "planet_mass",
  "description": "查询太阳系行星的质量",
  "parameters": {
    "type": "object",
    "properties": {
      "planet": {
        "type": "string",
        "description": "行星名称,如 Earth、Mars"
      }
    },
    "required": ["planet"]
  }
}

AI 要调用工具时,也不再输出 Action: planet_mass: Earth 这样的字符串了,而是直接返回:

{
  "tool_calls": [{
    "function": {
      "name": "planet_mass",
      "arguments": "{\"planet\": \"Earth\"}"
    }
  }]
}

看到区别了吗?从"人约定的文本格式"变成了"机器可以直接解析的 JSON 结构"

现代 ReAct 的执行流程

📷 [图片 token=AAIubwuZ8omrxuxBQ4BcMbNdnjg(未能下载,见飞书原文)]

用 Function Call 之后,ReAct 的循环变得简洁多了:

1. 把工具的 JSON 描述 + 用户问题 一起发给大模型
   (不需要在 Prompt 里写 "Action:xxx:xxx" 这种格式说明了)

2. 大模型自己判断要不要调用工具
   → 需要调用 → 返回标准 JSON 格式的 tool_calls
   → 不需要   → 直接返回文本答案

3. 我们解析 JSON,执行对应的工具函数,拿到结果

4. 把工具结果按规定格式加到对话里,开始下一轮循环

5. 直到大模型不再返回 tool_calls → 循环结束,输出最终答案

你会发现,核心循环的思路和古法 ReAct 完全一样——还是"想→做→看→再想"。变的只是交互的格式,从"手搓字符串"变成了"标准化 JSON"。

为什么 Function Call 这么好用?

这里多解释一下。可能有同学会问:不就是换了个格式吗,有那么大区别吗?

区别非常大,我举个类比:

你和同事沟通工作,之前你们约定用微信语音留言说任务,比如"帮我查下上周的销售数据,按地区分组,要 Excel 格式"。对方得听完语音,自己理解、自己整理,中间但凡听错一个字就可能搞岔了。

后来公司上了一个项目管理工具,你只需要填一个表单:任务类型=数据查询,数据范围=上周,分组方式=地区,输出格式=Excel。结构清晰,不会误解。

Function Call 就相当于那个项目管理工具的表单——把原本靠"人说人听"的沟通,变成了结构化的、机器可理解的标准协议。

而且因为格式标准化了,大模型厂商可以针对性地训练 AI 模型,让它更擅长生成正确的 JSON 调用格式。如果 AI 偶尔生成了格式错误的 JSON,服务端还能自动检测并重试,不需要你在代码里写一堆错误处理逻辑。

四、古法 vs 现代,到底差在哪?

说了这么多,咱们来做一个清晰的对比:

对比维度古法 ReAct现代 ReAct
工具调用格式自然语言字符串(如 Action: planet_mass: Mars标准化 JSON 结构(通过 Function Call 规范)
工具描述方式依赖用户自定义 Prompt 中的自然语言说明工具信息标准化(如 JSON 对象定义工具名、参数、功能)
解析方式正则表达式解析字符串(易出错,依赖格式严格匹配)结构化 JSON 解析(大模型/框架原生支持,可靠性高)
复杂数据处理困难(如嵌套结构的输入输出,字符串解析易混乱)可靠(JSON 天然支持复杂参数类型,如嵌套对象)
错误处理需手动实现(如未知 Action 抛出异常)框架自动支持(如工具调用格式错误时,大模型/框架自动重试)
依赖技术纯 Prompt 工程 + 字符串处理大模型 Function Call 功能(厂商官方支持)
格式规范来源用户自定义 Prompt 中的格式约束大模型厂商定义的标准化协议
实现复杂度高(需手动处理字符串生成、解析、异常捕获)低(框架封装了格式处理、工具调用逻辑)

一句话总结:核心差异就是从"人工字符串约定"升级到了"机器可理解的结构化协议"。

思想没变(都是 ReAct 循环),但实现方式的可靠性和开发效率,提升了一个量级。

五、ReAct 的好处是什么?

最后我们来聊聊,ReAct 模式到底给 AI 带来了什么好处。

📷 [图片 token=HE52b4Xhcolo59xE4Teczhppneb(未能下载,见飞书原文)]

1. 让 AI 具备了"使用工具"的能力

大语言模型最大的短板是什么?它的知识是有截止日期的,它也不擅长精确计算。

但有了 ReAct,AI 就可以像人一样去调用外部工具——查数据库、调 API、搜索引擎、计算器……这就大大扩展了 AI 的能力边界。

AI 不再是一个"只会聊天的嘴巴",而变成了一个能动手干活的助手

2. 让 AI 的回答更准确、更可靠

没有 ReAct 的时候,AI 遇到不确定的问题只能"硬编"——也就是我们常说的"AI 幻觉",看着像那么回事,但其实是瞎说的。

有了 ReAct,AI 会先想"这个问题我不确定,我得查一下",然后去调用工具获取真实数据,再基于真实数据来回答。从"凭记忆猜"变成了"查了再说",准确率自然大幅提升。

3. 解题过程透明可追踪

ReAct 的每一步——思考了什么、调了什么工具、拿到了什么结果——都是明明白白写在对话记录里的。

这意味着如果 AI 给了一个错误的答案,你可以回溯整个过程,找到到底是哪一步出了问题:是思考方向不对?是调用了错误的工具?还是工具本身返回的数据有误?

这种可追踪性对于调试和优化 AI Agent 来说非常重要。

4. 具备了处理复杂多步骤任务的能力

有些问题不是查一次就能解决的,需要多步骤、多工具协作。比如:

“帮我查一下北京今天的天气,如果温度低于 10 度,就发一条提醒消息给我”

这个任务需要:先查天气 → 判断温度 → 条件满足则发消息。这就是一个典型的多步骤任务。

ReAct 的循环机制天然支持这种场景——每一轮循环解决一个子问题,观察结果后再决定下一步做什么,直到整个任务完成。

5. 它是 AI Agent 的基石

现在市面上你看到的各种 AI Agent 产品——能帮你写代码、做数据分析、自动化办公的那些——底层几乎都是 ReAct 模式在驱动

可以说,ReAct 是 AI 从"聊天机器人"进化为"智能助手"的关键技术跳板。学懂了 ReAct,你就理解了 AI Agent 的核心运作原理。

总结

最后,让我们来做一个总复盘:

ReAct 是什么? → Reasoning + Acting,让 AI 学会"边想边做"的解题方法论。核心是 思考→行动→观察→再思考 的闭环循环。

古法 ReAct 怎么实现的? → 用 Prompt 规范 AI 的输出格式(Thought→Action→PAUSE),然后代码用正则表达式解析字符串来调用工具。能用,但脆弱。

现代 ReAct 怎么实现的? → 借助大模型厂商提供的 Function Call 能力,用标准 JSON 来定义工具和解析调用。更稳定、更可靠、开发成本更低。

古法和现代的核心区别? → 从"人工字符串约定"升级为"机器可理解的结构化协议",思想没变,实现方式脱胎换骨。

ReAct 为什么重要? → 它让 AI 有了调用工具的能力、更准确的回答、可追踪的推理过程,是 AI Agent 的核心技术支柱。

如果你是刚入门 AI Agent 开发的同学,ReAct 是你必须要搞懂的第一个核心概念。理解了它,后面学什么框架、什么架构,都会轻松很多。

希望这篇文章能帮到你,咱们下篇见!