揭秘 ReAct 框架:让大模型从“空想家”变身“实干家”

大家好,我是你们的老朋友,一名在代码世界摸爬滚打多年的技术博主。

最近,大语言模型(LLM)的应用如火如荼。但我们发现,单纯依靠模型的“大脑”(推理能力)往往不够用:它可能产生幻觉,或者无法获取最新的实时信息;而单纯让它调用工具(Tool Calling),又缺乏复杂的逻辑规划能力,容易在复杂任务中迷失方向。

这就引出了今天的主角——ReAct 框架

很多同学在面试或学习中听过这个词,觉得它很高深。其实,ReAct 的核心思想非常朴素且优雅:把“思考”(Reasoning)和“行动”(Action)结合起来。今天,我们就通过深入浅出的讲解和实战代码,彻底搞懂 ReAct 是如何工作的,以及它在企业级应用中的演进。

一、 什么是 ReAct?核心原理解析

ReAct 这个名字来源于两个单词的组合:Reasoning(推理)+ Action(行动)。

在传统的 Chain-of-Thought (CoT) 思维链中,模型只是在内部进行逻辑推导,最后给出一个答案。这就像是一个学生在闭卷考试,只能靠记忆和逻辑,不能查资料,也不能用计算器。

而在纯工具调用(Tool Calling)模式中,模型更像是一个只会执行命令的机器人,缺乏对全局任务的动态规划能力。

ReAct 框架打破了这两者的界限。 它让 LLM 进入一个循环状态:

  1. Thought(思考):模型分析当前情况,决定下一步该做什么。
  2. Action(行动):模型选择一个工具(如搜索引擎、计算器、数据库查询)并执行。
  3. Observation(观察):模型获取工具执行后的结果。

这个过程会不断循环,直到模型认为已经收集了足够的信息,从而给出最终答案(Final Answer)。

ReAct vs 其他模式

为了更直观地对比,我们可以看下表:

特性 纯 CoT (思维链) 纯 Tool Calling ReAct 框架
核心能力 逻辑推理 外部交互 推理 + 交互
信息获取 仅限训练数据 依赖预设工具 动态获取外部信息
决策能力 静态线性推导 被动响应 动态规划与修正
适用场景 数学题、逻辑题 简单查询、固定流程 复杂多步任务、开放域问答

二、 ReAct 的工作流程图

光说文字可能有点抽象,我们来看一张标准的 ReAct 执行流程图。这是一个典型的闭环控制过程。

需要更多信息

信息充足

用户输入问题

LLM 接收输入

生成 Thought
下一步该做什么?

生成 Action
调用工具 API

执行工具
例如: 搜索/计算/查库

获取 Observation
工具返回结果

更新上下文
将结果加入历史对话

生成 Final Answer
输出最终结论

结束

从图中可以看出,Thought → Action → Observation 构成了一个紧密的循环。这个循环赋予了 Agent(智能体)“边做边想”的能力。如果工具返回的结果不理想,模型可以在下一个 Thought 步骤中调整策略,重新选择工具或参数。

三、 实战演示:Python 实现简易 ReAct Loop

虽然目前业界主流使用 LangChain 等框架来构建 Agent,但理解底层的实现逻辑至关重要。下面我们用 Python 模拟一个最简化的 ReAct 循环,假设我们有一个简单的“搜索工具”和“计算器工具”。

注意:为了演示清晰,这里简化了 LLM 的调用部分,重点展示控制流逻辑。

import json
import time

# 模拟的工具函数
def search_tool(query: str) -> str:
    """模拟搜索引擎"""
    print(f"🔍 [Action] 正在搜索: {query}")
    # 实际场景中这里会调用 Google/Bing API
    if "天气" in query:
        return "北京今天天气晴朗,气温25度。"
    elif "人口" in query:
        return "中国人口约为14亿。"
    return "未找到相关信息。"

def calculator_tool(expression: str) -> str:
    """模拟计算器"""
    print(f"🧮 [Action] 正在计算: {expression}")
    try:
        # 注意:生产环境中严禁直接使用 eval,需使用安全沙箱
        result = eval(expression)
        return str(result)
    except Exception as e:
        return f"计算错误: {str(e)}"

# 定义工具注册表
TOOLS = {
    "search": search_tool,
    "calculate": calculator_tool
}

class SimpleReActAgent:
    def __init__(self, max_iterations=5):
        self.max_iterations = max_iterations
        self.history = []

    def think(self, task: str, observations: list) -> str:
        """
        模拟 LLM 的思考过程。
        在实际生产中,这里会调用 LLM API (如 GPT-4),
        Prompt 会包含任务、历史思考和观察结果。
        """
        # 这里为了演示,硬编码了一些简单的逻辑判断
        # 真实场景中,LLM 会根据上下文生成 JSON 或特定格式的字符串
        
        if not observations:
            # 初始思考
            if "天气" in task:
                return json.dumps({"thought": "我需要查询北京的天气。", "action": "search", "action_input": "北京天气"})
            elif "计算" in task or "+" in task or "*" in task:
                return json.dumps({"thought": "我需要执行数学计算。", "action": "calculate", "action_input": "12 * 12"})
        
        # 如果有观察结果,通常意味着任务完成或需要进一步处理
        # 这里简化为直接生成最终答案
        last_obs = observations[-1] if observations else ""
        return json.dumps({"thought": "我已经获得了所需信息。", "action": "finish", "action_input": f"根据搜索结果: {last_obs}"})

    def run(self, task: str):
        print(f"🚀 [Start] 开始处理任务: {task}\n")
        observations = []
        
        for i in range(self.max_iterations):
            print(f"--- Iteration {i+1} ---")
            
            # 1. Thought & Action Decision
            response_str = self.think(task, observations)
            response = json.loads(response_str)
            
            thought = response.get("thought", "")
            action_name = response.get("action", "")
            action_input = response.get("action_input", "")
            
            print(f"💭 [Thought] {thought}")
            
            # 2. Check if finished
            if action_name == "finish":
                print(f"✅ [Final Answer] {action_input}")
                return action_input
            
            # 3. Execute Action
            if action_name in TOOLS:
                try:
                    observation = TOOLS[action_name](action_input)
                    print(f"👀 [Observation] {observation}\n")
                    observations.append(observation)
                except Exception as e:
                    error_msg = f"工具执行错误: {str(e)}"
                    print(f"❌ [Error] {error_msg}\n")
                    observations.append(error_msg)
            else:
                print(f"⚠️ [Warning] 未知工具: {action_name}\n")
                break
                
        return "达到最大迭代次数,未能完成任务。"

# 运行示例
if __name__ == "__main__":
    agent = SimpleReActAgent()
    
    # 案例 1: 需要调用搜索工具
    print("="*30)
    agent.run("北京今天的天气怎么样?")
    
    print("\n" + "="*30)
    
    # 案例 2: 需要调用计算工具
    agent.run("计算 12 乘以 12 的结果")

代码运行逻辑解析

  1. 初始化:Agent 接收用户任务。
  2. Think 阶段think 方法模拟 LLM 根据当前上下文(任务+历史观察)生成下一步指令。在实际工程中,这一步是 prompt engineering 的核心,你需要告诉 LLM 可用的工具列表和输出格式。
  3. Action 阶段:解析 LLM 的输出,匹配对应的工具函数并执行。
  4. Observation 阶段:捕获工具的执行结果,并将其存入 observations 列表,作为下一次 think 的输入上下文。
  5. 循环终止:当 LLM 输出 finish 动作或达到最大迭代次数时,循环结束。

四、 从 Demo 到生产:ReAct 的局限与进阶

上面的代码只是一个玩具模型。在真实的企业级应用中,原生的 ReAct 框架面临几个挑战:

  1. 状态控制能力有限:简单的线性循环难以处理复杂的分支逻辑(比如:如果搜索失败,该怎么办?如果需要并行调用多个工具呢?)。
  2. 上下文窗口限制:随着 Thought-Action-Observation 循环次数增加,Prompt 会变得极长,导致成本增加且容易超出 Token 限制。
  3. 缺乏长期记忆:原生 ReAct 通常是无状态的,无法记住用户之前的偏好或历史对话中的关键信息。

因此,现在的生产级 Agent 系统通常不会只使用裸版的 ReAct,而是结合以下技术进行增强:

  • LangGraph / State Machine:使用有向图(DAG)或状态机来管理 Agent 的流程。不再是简单的 while 循环,而是可以根据条件跳转到不同的节点(例如:路由节点、反思节点)。
  • Memory(记忆模块):引入向量数据库或摘要机制,让 Agent 拥有短期记忆(当前对话)和长期记忆(用户画像、知识库)。
  • Planner(规划器):在 ReAct 循环之前,先让 LLM 生成一个高层级的计划(Plan),将大任务拆解为子任务,再逐个执行。
  • Reflection(反思/自我修正):在执行完 Action 后,增加一个 Critic(评论家)角色,评估结果的质量。如果质量不高,触发重试或调整策略。

进阶架构示意图

ReAct Loop for SubTask 2

ReAct Loop for SubTask 1

Pass

Fail

Pass

Fail

用户输入

Planner: 任务拆解

子任务 1

子任务 2

Thought

Action

Observation

Reflection

完成

Thought

Action

Observation

Reflection

完成

Synthesizer: 结果汇总

最终回答

Memory Store

五、 总结与建议

ReAct 框架是大模型从“聊天机器人”进化为“智能代理(Agent)”的关键一步。它通过推理与行动的交替循环,解决了大模型无法实时获取信息和执行操作痛点。

给开发者的建议:

  1. 入门首选:如果你是初学者,建议先从 LangChain 的 AgentExecutor 入手,它本质上就是封装好的 ReAct Loop,能快速上手。
  2. 关注 Prompt 质量:ReAct 的效果高度依赖 Prompt 的设计。清晰地定义工具的描述(Description)和输入参数格式,能显著提高 LLM 调用工具的准确率。
  3. 迈向生产:当你的业务逻辑变复杂时,不要死守单一的 ReAct 循环。尝试引入 LangGraph 来构建有状态的、可回溯的工作流,并结合 Reflection 机制提高系统的鲁棒性。

希望这篇文章能帮你理清 ReAct 的脉络。技术在不断演进,但核心思想始终是为了解决更复杂的问题。保持好奇,持续实践!

参考资料

更多推荐