【02】ReAct 模式深度解析:让 AI Agent 学会「边想边做」

1 阅读6分钟

本篇属于「AI Agent 开发实战系列」第 2 篇

前言

在上一篇文章中,我们了解了 AI Agent 的核心概念:感知环境、自主决策、执行动作。

但有一个关键问题:Agent 如何做出「思考」与「行动」的协调?

这就是今天要介绍的 ReAct 模式——目前最广泛使用的 Agent 推理范式。


前置知识

阅读本文前,建议:

  • 了解 AI Agent 基本概念(可阅读第 1 篇
  • 具备基础 Python 编程能力
  • 了解 LLM API 的基本调用方式

核心概念

一、为什么需要 ReAct?

传统的 LLM 推理有两种模式:

1. Chain-of-Thought(CoT)—— 只想不做

问题:3 + 5 = ?
思考:3 + 5 就是把3和5相加...

回答:3 + 5 = 8

2. Action-Only —— 只做不想

问题:帮我查下北京天气
直接行动:调用天气API
返回结果:北京今天 25°C ❌

问题在哪?

  • CoT 虽然能推理复杂问题,但无法与外部环境交互
  • Action-Only 能执行动作,但缺乏中间推理过程

ReAct 的核心思想:将推理(Reasoning)和行动(Acting)交替进行,让模型「边想边做,边做边想」。

二、ReAct 模式的原理

ReAct = Reasoning + Acting

┌─────────────────────────────────────────────────────────────┐
│                    ReAct 核心循环                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│    ┌─────────┐                                              │
│    │  思考   │◀─────────────────────┐                        │
│    │(Thought)│                      │                        │
│    └────┬────┘                      │                        │
│         │                           │                        │
│         ▼                           │                        │
│    ┌─────────┐                      │                        │
│    │  行动   │                      │                        │
│    │(Action) │──────────────────────┘                        │
│    └────┬────┘                                              │
│         │                                                   │
│         ▼                                                   │
│    ┌─────────┐                                              │
│    │  观察   │────── 反馈到下一步思考 ──────────────────────  │
│    │(Observe)│                                              │
│    └─────────┘                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

一个具体例子

用户:帮我买一杯星巴克的大杯拿铁,用优惠券

Thought: 用户想买咖啡,需要先了解:
         1. 星巴克有什么口味?
         2. 大杯拿铁的价格?
         3. 有哪些优惠券可用?
Action: search_coffee_store(store="星巴克")
Observe: 找到星巴克门店 nearby...
        
Thought: 现在知道了附近有星巴克门店,继续查询...
Action: get_menu(store_id="starbucks_001")
Observe: 菜单获取成功,拿铁中杯35元,大杯42Thought: 找到了大杯拿铁42元,现在看看优惠券...
Action: get_coupons(user_id="user_123")
Observe: 找到优惠券:「首单满5010Thought: 42元不够满50,不能用这张券。我需要先凑单
         或者找其他优惠券...
Action: search_coupons(keyword="拿铁")
Observe: 找到可用券:「咖啡类单品9折」
        
Thought: 42元打9折是37.8元,比原价便宜4.2元。
         用户说用优惠券,那我就用这张。
Action: create_order(item="大杯拿铁", coupon="咖啡9折")
Observe: 订单创建成功!总价37.8元,已自动使用优惠券

Answer: 已为您下单星巴克大杯拿铁,使用「咖啡9折」券后
        实付 37.8 元。预计30分钟内送达。✅

三、ReAct vs 其他推理模式对比

模式思考行动观察适用场景
ReAct复杂任务、多步骤推理
CoT数学推理、逻辑分析
ToT✅✅树搜索探索多种方案
Reflexion✅+反思需要自我改进

实践指南

环境准备

pip install langchain langchain-openai langchain-core

基础 ReAct Agent 实现

import os
from typing import List, Dict, Any
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

class ReActAgent:
    """ReAct 模式的简单实现"""
    
    def __init__(self, tools: List[Dict]):
        self.client = OpenAI()
        self.tools = tools
        self.max_iterations = 10
        
    def _format_tools(self) -> str:
        """格式化工具描述"""
        tool_desc = []
        for i, tool in enumerate(self.tools):
            tool_desc.append(
                f"{i}. {tool['name']}: {tool['description']}"
            )
        return "\n".join(tool_desc)
    
    def _create_prompt(self, question: str, history: List[Dict]) -> List[Dict]:
        """创建带 ReAct 格式的系统提示"""
        system_prompt = f"""你是一个使用 ReAct 模式的 AI Agent。

## 工作流程
对于每个问题,你需要按以下格式思考和行动:

Thought: 你现在的思考,分析问题
Action: 你要执行的行动(如:search_web, calculator, get_weather)
Action_Input: 行动的具体输入参数
Observation: 观察行动的结果

最后用以下格式回答:
Answer: 最终答案

## 可用工具
{self._format_tools()}

## 重要规则
1. 每一步必须包含 Thought 和 Action
2. 如果无法回答,说 "Answer: 无法回答"
3. 最多执行 {self.max_iterations} 步
"""
        
        messages = [{"role": "system", "content": system_prompt}]
        messages.extend(history)
        messages.append({"role": "user", "content": question})
        
        return messages
    
    def _execute_tool(self, tool_name: str, tool_input: str) -> str:
        """执行工具调用"""
        # 简化实现:实际项目中这里会真正调用工具
        print(f"🔧 执行工具: {tool_name}({tool_input})")
        
        # 模拟工具返回
        if tool_name == "calculator":
            try:
                result = eval(tool_input)
                return f"计算结果: {result}"
            except:
                return "计算错误"
        elif tool_name == "search":
            return f"搜索「{tool_input}」的结果: [示例内容]"
        else:
            return f"工具 {tool_name} 执行完成"
    
    def _parse_response(self, response: str) -> Dict[str, str]:
        """解析 LLM 返回的响应"""
        lines = response.strip().split("\n")
        result = {}
        
        for line in lines:
            if line.startswith("Thought:"):
                result["thought"] = line.replace("Thought:", "").strip()
            elif line.startswith("Action:"):
                result["action"] = line.replace("Action:", "").strip()
            elif line.startswith("Action_Input:"):
                result["action_input"] = line.replace("Action_Input:", "").strip()
            elif line.startswith("Answer:"):
                result["answer"] = line.replace("Answer:", "").strip()
            elif line.startswith("Observation:"):
                result["observation"] = line.replace("Observation:", "").strip()
        
        return result
    
    def run(self, question: str) -> str:
        """运行 ReAct Agent"""
        history = []
        
        for i in range(self.max_iterations):
            # 1. 获取 LLM 响应
            messages = self._create_prompt(question, history)
            response = self.client.chat.completions.create(
                model="gpt-4",
                messages=messages
            )
            llm_output = response.choices[0].message.content
            print(f"\n📝 步骤 {i+1} LLM输出:\n{llm_output}\n")
            
            # 2. 解析响应
            parsed = self._parse_response(llm_output)
            
            # 3. 记录历史
            history.append({"role": "assistant", "content": llm_output})
            
            # 4. 检查是否得到答案
            if "answer" in parsed:
                return parsed["answer"]
            
            # 5. 执行工具
            if "action" in parsed and "action_input" in parsed:
                tool_result = self._execute_tool(
                    parsed["action"],
                    parsed["action_input"]
                )
                observation = f"Observation: {tool_result}"
                print(f"📍 {observation}\n")
                
                history.append({
                    "role": "assistant", 
                    "content": observation
                })
        
        return "达到最大迭代次数,未能得到答案"


# 定义工具
tools = [
    {
        "name": "calculator",
        "description": "执行数学计算,如: 25 * 3 + 10"
    },
    {
        "name": "search",
        "description": "搜索网络信息,如: 北京天气"
    }
]

# 运行 Agent
if __name__ == "__main__":
    agent = ReActAgent(tools)
    
    question = "25美元兑换人民币,按7.2汇率计算"
    result = agent.run(question)
    print(f"\n🎯 最终答案: {result}")

使用 LangChain 实现 ReAct

LangChain 提供了更完善的 ReAct 实现:

from langchain import hub
from langchain.agents import AgentType, initialize_agent
from langchain.agents.format_scratchpad import format_xml
from langchain.tools import Tool
from langchain_openai import ChatOpenAI

# 定义工具
def calculate(expression: str) -> str:
    """计算器工具"""
    try:
        return f"计算结果: {eval(expression)}"
    except:
        return "计算错误"

def search_wikipedia(query: str) -> str:
    """搜索工具(示例)"""
    return f"关于「{query}」的信息..."

tools = [
    Tool.from_function(
        func=calculate,
        name="calculator",
        description="用于数学计算,输入数学表达式"
    ),
    Tool.from_function(
        func=search_wikipedia,
        name="wikipedia",
        description="搜索维基百科获取信息"
    )
]

# 加载 ReAct 提示词
prompt = hub.pull("hwchase17/react")

# 初始化 Agent
llm = ChatOpenAI(model="gpt-4", temperature=0)
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# 运行
result = agent.run("北京的人口是多少?然后计算如果每人发100元需要多少钱?")

进阶拓展

ReAct 的优缺点

优点

  • ✅ 推理过程透明,可追踪
  • ✅ 易于调试和优化
  • ✅ 适用于需要多步推理的任务
  • ✅ 与工具调用天然结合

缺点

  • ❌ 迭代次数多,token 消耗大
  • ❌ 推理速度受限于 LLM 调用
  • ❌ 复杂的推理链可能出错

优化策略

1. 控制推理长度

# 设置最大 token 限制
max_tokens = 500  # 限制单次输出

# 提前终止
if len(steps) > 5 and confidence < 0.8:
    return "需要更多信息"

2. 使用更强的模型

# 简单问题用小模型
model = "gpt-3.5-turbo"  # 快速

# 复杂推理用大模型
model = "gpt-4"  # 准确

3. 添加自我反思

Thought: 我已经完成了计算,但让我检查一下...
Reflection: 计算过程看起来正确,但可能遗漏了...
Action: verify_calculation

总结

本文深入讲解了 ReAct 模式:

  1. ReAct = Reasoning + Acting,让 Agent 边想边做
  2. 核心循环:Thought → Action → Observation → Thought...
  3. 优势:推理透明、易调试、适合复杂任务
  4. 实践:可以用纯 Python 或 LangChain 实现

ReAct 是 Agent 开发的基础模式,后续的复杂功能(工具调用、多Agent协作)都是基于此构建。

下篇预告: 在下一篇文章中,我们将介绍 MCP 协议——Anthropic 推出的标准化 Agent 通信协议。你将学习到:

  • MCP 协议的概念和优势
  • 如何快速搭建一个 MCP Server
  • MCP 与现有框架的集成

参考资料


本文是「AI Agent 开发实战系列」第 2 篇,系列共 10 篇。