[!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 其实很为难——它的训练数据里未必记住了精确的行星质量数值,就算记住了也可能不准确。更关键的是,它不会做精确计算,大语言模型本质上是在"预测下一个词",不是在算数学题。
那怎么办?
如果是你,你会怎么做?你大概率会这样:
想一想:我需要地球的质量和火星的质量,然后把它们加起来
查一查:打开搜索引擎,先查地球质量 → 得到 5.972 × 10²⁴ kg
再查一下:继续查火星质量 → 得到 6.42 × 10²³ kg
算一算:掏出计算器,5.972e24 + 6.42e23 = 6.614e24
回答:加起来大概是 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 的调用格式也是字符串,我们解析也是靠正则匹配字符串。
这就带来几个问题:
格式脆弱:AI 有时候不完全按格式输出,比如多了个空格、换了个冒号,正则就匹配不上了
复杂参数搞不定:如果工具的输入是一个嵌套的 JSON 对象(比如 Map 套 Map),用字符串来描述和解析就是噩梦
错误处理全靠自己:AI 输出了一个不存在的工具名怎么办?格式错了怎么办?全得手动写异常处理
开发成本高:每个项目都要自己设计格式、写解析逻辑、处理各种边界情况
说白了,古法 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 是你必须要搞懂的第一个核心概念。理解了它,后面学什么框架、什么架构,都会轻松很多。
希望这篇文章能帮到你,咱们下篇见!