【AI应用开发】ReAct 原理是什么?和普通直接提问 LLM 差别在哪?

【AI应用开发】ReAct 原理是什么?和普通直接提问 LLM 差别在哪?


1. 一句话理解 ReAct

ReAct = Reasoning(推理)+ Acting(行动),是一种让 LLM 在解决问题时"边想边做"的范式。

普通 LLM 面对问题 → 直接输出答案(一步完成)

ReAct Agent 面对问题 → 思考→行动→观察→思考→行动→观察→...→输出答案(循环完成)


2. ReAct 名字拆解:三个关键词

ReAct 这个缩写由三个词组成,分别对应 Agent 循环中的三种输出模式:

复制代码
ReAct = Reasoning + Acting
          │           │
          ▼           ▼
      Thought       Action
    (思考/推理)    (行动/调用工具)
                      │
                      ▼
                 Observation
                 (观察/获取结果)
组件 英文 含义 形式
Thought 思考/推理 LLM 分析当前状态,决定下一步做什么 自然语言推理描述
Action 行动 LLM 选择调用哪个工具及其参数 结构化工具调用
Observation 观察 接收工具执行结果,作为下一轮思考的输入 工具返回的原始数据

3. 普通 LLM 的"一次推理"模式

先理解普通 LLM 是怎么工作的,才能理解 ReAct 改进了什么。

3.1 普通 LLM 的工作流程

复制代码
用户提问 → [Prompt 构造] → LLM 一次推理 → 输出答案

完整走一遍:

复制代码
用户:"北京今天天气怎么样?"

普通 LLM 处理:
  ┌─────────────────────────────────────────────┐
  │ 输入: "User: 北京今天天气怎么样?"          │
  │                                             │
  │ LLM 内部一次推理:                          │
  │   "我不知道今天天气,但我可以根据常识推测..."  │
  │                                             │
  │ 输出: "北京今天可能是晴天,大约25度左右"    │ → 幻觉!没有真实数据
  └─────────────────────────────────────────────┘

核心特征:只有一次输入→一次输出的过程。

3.2 普通 LLM 的根本困境

复制代码
LLM 的知识 = 训练数据(截止到某个时间点)

问题类型              │ 普通 LLM 的表现
──────────────────────┼─────────────────────
"写一首诗"            │ ✅ 完美,需要创意
"1+1等于几"           │ ✅ 完美,基础常识
"今天深圳天气"        │ ❌ 瞎编,知识已过期
"我公司上月销量"      │ ❌ 瞎编,私有数据
"帮我订机票"          │ ❌ 无法行动,只能说话

普通的 LLM 就像一个被锁在房间里的天才会话者------他脑子很好,但看不到外面,也动不了手。


4. ReAct 的"循环推理"模式

4.1 ReAct 的工作流程

ReAct 给 LLM 装上了"眼睛"和"手":

复制代码
用户提问 → Thought → Action → Observation → Thought → Action → Observation → ... → Final Answer
               ↑                                                    │
               └──────────────── 循环往复 ──────────────────────────┘

走一遍同一个问题:

复制代码
用户:"北京今天天气怎么样?"

ReAct Agent 处理:
┌─────────────────────────────────────────────────────────┐
│ Thought 1: 用户想知道北京今天的天气,我需要调用天气API  │
│                                                        │
│ Action 1: get_weather(city="北京")                     │
│                                                        │
│ Observation 1: "北京今天多云,23°C,湿度65%"            │
│                                                        │
│ Thought 2: 已经获得了真实的天气数据,可以直接回答用户   │
│                                                        │
│ Final Answer: "北京今天多云,气温 23°C,湿度 65%"       │
└─────────────────────────────────────────────────────────┘

这一次,LLM 没有瞎猜,因为它拿到了真实的、实时的数据

4.2 为什么是"循环"?

关键是 ReAct 允许 LLM 在每一次 Action 后重新"观察"结果,然后再次"思考"。这个循环让 Agent 能够:

  • ✅ 发现第一次工具结果不够 → 再调用一次
  • ✅ 发现工具调用失败 → 换一个工具
  • ✅ 发现需要多步操作 → 继续下一步
  • ✅ 判断任务已完成 → 输出最终答案

5. 两者核心差异对比

5.1 一张表说清

维度 普通 LLM 直接提问 ReAct Agent
推理次数 1 次 多次循环
外部工具 可调用任意工具
数据来源 仅训练记忆 工具实时返回 + 训练记忆
回答准确性 依赖训练知识,易幻觉 基于真实工具数据,更准确
可处理任务 简单的、知识类 复杂的、需要实时信息/操作的
错误处理 无法自查 可观察错误并重试
Token 消耗 高(多轮对话)
响应速度 快(一次推理) 慢(多次推理 + 工具等待)
实现复杂度 极低 中等

5.2 直观对比:同一个任务的不同表现

复制代码
任务:"查一下茅台今天的股价,如果低于 1800 就建议买入,否则建议观望"

普通 LLM 回答:
  "茅台今天的股价大概是 1750 左右,建议您关注..." 
  → 问题:股价是编的,决策没有依据

ReAct Agent 回答:
  Thought 1: 需要获取茅台实时股价
  Action 1: stock_price("600519")
  Observation 1: 当前价 1782.50
  
  Thought 2: 1782.50 < 1800,触发买入条件
  Thought 3: 已有了所有需要的信息
  Final Answer: "茅台(600519)当前股价 1782.50 元,低于 1800 元阈值,建议考虑买入。"
  → 每个数字都是真实数据,决策有据可查

6. ReAct 的运作机制深度拆解

6.1 完整循环详解

复制代码
         ┌──────────────────────────┐
         │    接收用户输入           │
         └────────┬─────────────────┘
                  │
                  ▼
         ┌──────────────────────────┐
         │ LLM 产生 Thought         │  ← "我应该做什么?需要什么工具?"
         │ (分析现状,规划下一步)    │
         └────────┬─────────────────┘
                  │
                  ▼
         ┌──────────────────────────┐
         │ LLM 产生 Action          │  ← "调用哪个工具?参数是什么?"
         │ (选择工具及参数)          │
         └────────┬─────────────────┘
                  │
                  ▼
         ┌──────────────────────────┐
         │ 执行工具调用              │  ← 你的代码实际运行工具函数
         │ 获取 Observation          │
         └────────┬─────────────────┘
                  │
                  ▼
         ┌──────────────────────────┐
         │ Observation 喂回 LLM     │  ← 工具结果拼入上下文
         └────────┬─────────────────┘
                  │
                  ▼
         ┌──────────────────────────────────┐
         │ LLM 判断:任务完成了吗?          │
         │                                  │
         │  ├── 没有 → 产生新的 Thought     │ → 回到循环开头
         │  │                                │
         │  └── 完成了 → 输出 Final Answer   │ → 退出循环
         └──────────────────────────────────┘

6.2 每一步的 Prompt 怎么变

以一个真实的调用序列为例,看看每一轮 Prompt 的变化:

第 1 轮 ------ LLM 决定调用工具

复制代码
System: 你是一个能使用工具的助手。可用工具: get_weather
User: 北京今天天气怎么样?

LLM 输出:
  Thought: 需要查询北京天气
  Action: get_weather(city="北京")

第 2 轮 ------ 工具结果注入后 LLM 再推理

复制代码
System: 你是一个能使用工具的助手。可用工具: get_weather
User: 北京今天天气怎么样?
Assistant: Thought: 需要查询北京天气
           Action: get_weather(city="北京")
Tool Result: "北京今天多云,23°C"
        
LLM 输出:
  Thought: 已拿到真实天气数据,可以直接回答
  Final Answer: "北京今天多云,气温 23°C"

关键观察:每一轮工具结果都被追加到对话历史中,LLM 能"看到"之前发生了什么,基于新信息做下一步决策。

6.3 多步 ReAct 的完整示例

复制代码
用户: "帮我查一下苹果(AAPL)和谷歌(GOOGL)今天股价,告诉我哪个涨得多"

Round 1:
  Thought: 需要分别查询两只股票的股价
  Action: stock_price(symbol="AAPL")
  Observation: AAPL 当前 $175.30, 涨跌幅 +1.2%

Round 2:
  Thought: AAPL 数据拿到了,还需要查 GOOGL
  Action: stock_price(symbol="GOOGL")
  Observation: GOOGL 当前 $141.80, 涨跌幅 +0.8%

Round 3:
  Thought: 两只股票的数据都有了。
          AAPL +1.2% vs GOOGL +0.8%,AAPL 涨得更多
  Final Answer: "今天 AAPL 涨 1.2%,GOOGL 涨 0.8%,苹果涨得更多。"

7. 为什么 ReAct 能降幻觉?

这是 ReAct 最核心的价值。降低幻觉的机制有三层:

7.1 真实数据替代记忆猜测

复制代码
普通 LLM:
  "茅台股价多少?" → 翻内部记忆 → 记忆中可能有错误训练数据 → 瞎编

ReAct:
  "茅台股价多少?" → 调用股票 API → 拿到实时精确数据 → 基于数据回答

幻觉的第一来源是模型记忆不准确。ReAct 通过工具获取外部真实数据,直接绕过这一问题。

7.2 观察发现矛盾时可纠正

复制代码
Round 1:
  Thought: 用户要查"A公司Q2营收"
  Action: search(query="A公司 Q2 营收")
  Observation: "未找到 A公司 Q2 营收数据"  ← 搜索无结果

Round 2:
  Thought: 搜索结果没有 Q2 数据,但可能有其他相关财报
  Action: search(query="A公司 最新财报 营收")
  Observation: "A公司 2024 Q3 财报:营收 5.2 亿"  ← 找到了更近期的

Round 3:
  Thought: 只找到了 Q3 数据,没有 Q2 的。诚实告知用户。
  Final Answer: "未找到 A公司 Q2 营收数据,最新的 Q3 财报显示营收 5.2 亿。"

普通 LLM 在搜索无果时,很可能直接开始编造。ReAct 的"观察→再思考"机制让 LLM 有机会承认"没找到"而不是"编一个"。

7.3 多步交叉验证

复制代码
搜索工具 A 返回: "iPhone 16 起售价 6999 元"
搜索工具 B 返回: "iPhone 16 起售价 5999 元"

ReAct 可以:
  Thought: 两个来源数据不一致,需要更多交叉验证
  Action: search(query="iPhone 16 官方价格")
  Observation: "苹果官网显示 iPhone 16 起售价 5999 元"
  
  Thought: 官网价格确认为 5999 元,以官网为准

8. 手写 ReAct Agent 完整实现

下面是一个完整可运行的 ReAct Agent 实现(约 150 行),使用 OpenAI API:

python 复制代码
"""
ReAct Agent 手写实现
零框架依赖,只用了 openai 库做 LLM 推理
"""

import json
import re
from openai import OpenAI

client = OpenAI()  # 需要设置 OPENAI_API_KEY 环境变量

# ============================================================
# 第一部分:定义工具
# ============================================================

def get_weather(city: str) -> str:
    """获取城市天气(模拟)"""
    weather_data = {
        "北京": "多云,23°C,湿度 45%",
        "上海": "晴,28°C,湿度 60%",
        "深圳": "阵雨,30°C,湿度 80%",
    }
    return weather_data.get(city, f"未找到 {city} 的天气数据")

def calculator(expression: str) -> str:
    """安全执行数学计算"""
    try:
        allowed = set("0123456789+-*/().% ")
        if not all(c in allowed for c in expression):
            return "错误:表达式包含不允许的字符"
        result = eval(expression)
        return str(result)
    except Exception as e:
        return f"计算错误:{e}"

def web_search(query: str) -> str:
    """模拟网络搜索"""
    search_results = {
        "茅台股价": "贵州茅台(600519)当前股价 1782.50 元",
        "iPhone": "iPhone 16 起售价 5999 元",
    }
    for key, value in search_results.items():
        if key in query:
            return value
    return f"关于「{query}」未找到相关结果"

# 工具注册表
TOOLS = {
    "get_weather": {
        "function": get_weather,
        "description": "获取指定城市的天气信息",
        "parameters": {"city": "城市名称,例如'北京'"},
    },
    "calculator": {
        "function": calculator,
        "description": "执行数学计算,支持加减乘除",
        "parameters": {"expression": "数学表达式,例如'3*15'"},
    },
    "web_search": {
        "function": web_search,
        "description": "搜索网络信息",
        "parameters": {"query": "搜索关键词"},
    },
}

# ============================================================
# 第二部分:ReAct Prompt 模板
# ============================================================

SYSTEM_PROMPT = """你是一个能使用工具的智能助手。请严格按照以下格式回复:

当需要调用工具时:
Thought: [你的推理过程,分析当前状态和下一步计划]
Action: tool_name(param1="value1", param2="value2")

当获得工具结果后需要继续思考:
Thought: [基于观察结果的下一步推理]
Action: tool_name(...)  或  Final Answer

当你有了足够信息回答用户时:
Thought: [最终推理总结]
Final Answer: [你的完整回答]

重要规则:
1. 每次只能调用一个工具
2. 必须先有 Thought 再有 Action
3. 不要在 Final Answer 之前输出任何其他内容
4. 如果工具调用失败或返回空结果,尝试其他方法而不是编造

当前可用的工具:
{}

请开始。""".format(
    "\n".join(
        f"- {name}: {info['description']},参数:{info['parameters']}"
        for name, info in TOOLS.items()
    )
)

# ============================================================
# 第三部分:ReAct 循环引擎
# ============================================================

class ReactAgent:
    def __init__(self, max_steps=10):
        self.max_steps = max_steps
        self.tools = TOOLS
    
    def run(self, user_input: str) -> str:
        """执行 ReAct 循环"""
        messages = [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_input},
        ]
        
        step_count = 0
        
        while step_count < self.max_steps:
            step_count += 1
            print(f"\n{'='*50}")
            print(f"📍 Round {step_count}")
            
            # 调用 LLM
            response = client.chat.completions.create(
                model="gpt-4o",
                messages=messages,
                temperature=0.0,  # 确定性输出,适合工具调用
            )
            
            reply = response.choices[0].message.content
            print(f"🤖 LLM 输出:\n{reply}")
            
            # 将 LLM 回复加入对话历史
            messages.append({"role": "assistant", "content": reply})
            
            # 解析输出:判断是 Final Answer 还是 Action
            if "Final Answer:" in reply:
                final = reply.split("Final Answer:")[-1].strip()
                print(f"✅ 最终回答: {final}")
                return final
            
            # 解析 Action
            action_match = re.search(r'Action:\s*(\w+)\((.*?)\)', reply)
            if not action_match:
                # 没有解析到 Action,可能是格式错误
                messages.append({
                    "role": "user",
                    "content": "请严格按照格式回复:先 Thought 再 Action 或 Final Answer"
                })
                continue
            
            tool_name = action_match.group(1)
            args_str = action_match.group(2)
            
            # 解析参数
            args = self._parse_args(args_str)
            
            # 执行工具
            if tool_name in self.tools:
                result = self.tools[tool_name]["function"](**args)
            else:
                result = f"错误:未知工具 '{tool_name}'。可用工具:{list(self.tools.keys())}"
            
            print(f"🔧 工具 {tool_name}({args}) → {result}")
            
            # 将工具结果作为 Observation 加入对话
            observation_msg = f"Observation: {result}"
            messages.append({"role": "user", "content": observation_msg})
        
        return "抱歉,达到最大执行步数,未能完成任务。"
    
    def _parse_args(self, args_str: str) -> dict:
        """解析工具调用参数字符串为字典"""
        args = {}
        # 匹配 key="value" 模式
        for match in re.finditer(r'(\w+)\s*=\s*"([^"]*)"', args_str):
            args[match.group(1)] = match.group(2)
        return args


# ============================================================
# 第四部分:运行
# ============================================================

if __name__ == "__main__":
    agent = ReactAgent(max_steps=10)
    
    # 测试一:单步工具
    print("\n" + "="*60)
    print("  测试 1:单步工具调用")
    print("="*60)
    agent.run("北京今天天气怎么样?")
    
    # 测试二:多步工具
    print("\n" + "="*60)
    print("  测试 2:多步工具调用")
    print("="*60)
    agent.run("查一下茅台股价,再帮我算 1782.50 * 100 等于多少")

8.1 运行结果示例

复制代码
==================================================
  测试 1:单步工具调用
==================================================

==================================================
📍 Round 1
🤖 LLM 输出:
Thought: 用户想知道北京天气,需要调用天气工具
Action: get_weather(city="北京")
🔧 工具 get_weather({'city': '北京'}) → 多云,23°C,湿度 45%

==================================================
📍 Round 2
🤖 LLM 输出:
Thought: 已经拿到了北京的天气数据,可以回答了
Final Answer: 北京今天多云,气温 23°C,湿度 45%
✅ 最终回答: 北京今天多云,气温 23°C,湿度 45%

9. ReAct Prompt 模板的精妙之处

ReAct 的 Prompt 设计不是随意的,每一部分都有工程上的考量:

9.1 格式约束为什么这么严格

python 复制代码
# ❌ 宽松格式 ------ LLM 容易不听话
"你可以调用工具,也可以直接回答"

# ✅ 严格格式 ------ LLM 知道每一步该输出什么
"""
当需要调用工具时:
Thought: ...
Action: tool_name(param="value")

当可以回答时:
Thought: ...
Final Answer: ...
"""

原因:不给 LLM 格式约束,它会在 Action 执行后直接又输出一个新的 Action(跳过 Observation 的消化),或者在 Action 和 Final Answer 混在一起输出,导致解析崩溃。

9.2 Thought 不是多余的

初学者常问:"既然有 Action,为什么还要 Thought?直接 Action 不行吗?"

答案是:Thought 强迫 LLM 做显式推理,极大提高行动质量

复制代码
有 Thought:
  Thought: 用户要查天气和股价,这两个任务互相独立
          我可以先查天气再查股价,但更高效的方法是...
  Action: get_weather("北京")

无 Thought(直接 Action):
  Action: get_weather("北京")  
  → LLM 没有显式思考过程,容易选错工具或参数

Thought 的另一个作用是可解释性------用户能看到 Agent 的思考过程,知道它为什么做出了某个决定。

9.3 Observation 的角色

Observation 不是 LLM 生成的,而是系统注入的。它作为下一轮的上下文回填:

复制代码
messages 数组随时间变化:

Round 1 输入: [system, user:"查天气"]
Round 1 LLM输出: "Thought: 需要天气工具\nAction: get_weather(city='北京')"
                
系统注入: "Observation: 多云,23°C"
                
Round 2 输入: [system, user, assistant(含Action), user(Observation)]
Round 2 LLM输出: "Thought: 拿到数据了\nFinal Answer: 今天多云..."

10. ReAct 的局限与改进方向

10.1 ReAct 的三大局限

局限 表现 影响
Token 消耗大 每轮都要把完整历史发给 LLM 长任务成本高
串行执行慢 一次只能调用一个工具 独立子任务不能并行
长链迷失 步骤多了 LLM 会忘记初衷 复杂任务容易偏航

10.2 改进方案

Plan-Execute:先规划全步骤,执行时不再每步推理,减少 Token 开销。

复制代码
ReAct(每步都推理):
  Thought(步1) → Action → Observation → Thought(步2) → Action → Observation → ...

Plan-Execute(规划一次,执行多步):
  全局 Plan → 按步执行 → 仅在失败时重规划 → 汇总

ReWOO:规划与执行完全分离,可并行任务同时执行。

Tool-use with Parallel Calls:支持一次 Action 调用多个互不依赖的工具。

这些改进方案将在本系列《详解(三):Function Calling 的执行流程》中进一步展开。


11. 本篇总结

问题 答案
ReAct 是什么? Reasoning + Acting,让 LLM 边想边做的 Agent 范式
和普通 LLM 提问的核心区别? ReAct 多了"思考→行动→观察"的循环,能获取外部真实数据
为什么能降幻觉? 用工具获取真实数据替代记忆猜测,观察结果可验证
Thought 有什么用? 显式推理过程,提高决策质量 + 增加可解释性
ReAct 有什么局限? Token 消耗大、串行慢、长链易迷失

一句话记住:普通 LLM 是"一次推理,一次输出",ReAct Agent 是"多次思行循环,基于真实数据决策"。

相关推荐
RobinDevNotes1 小时前
开源AI渗透测试智能体自动验证真实漏洞
人工智能·网络安全·ai·个人开发·开发工具
数据皮皮侠AI1 小时前
上市公司数字供应链金融指数(2010-2024)
大数据·人工智能·算法
SynthWriter1 小时前
下班前接到急活?我用 TRAE Work 20 分钟搞定了一套完整页面原型
前端
minimoon_jojo1 小时前
Antd Table虚拟滚动详解
前端
海兰1 小时前
【思考】AI编码企业落地:从“通用生成”到“工程落地”的鸿沟与跨越
人工智能
颜进强1 小时前
Calude Code - 23 用 MCP 把 Jenkins 变成 AI 队友:一次对话完成自动化发布
前端·后端
wangjialelele1 小时前
Selenium4 + Java Web自动化测试入门指南:从环境搭建到常用操作详解
java·开发语言·前端·测试工具·自动化
白拾1 小时前
【ICML 2026 (Oral)】RoboMME 论文解读:首个大规模具身记忆评测基准,系统评估 VLA 长程记忆
人工智能·机器人·具身智能·vla·icml·记忆·基准评测
word1 小时前
用 AI 搭一条自动化热点监控流水线:RSS 聚合、智能筛选、定时推送一次跑通
人工智能