ReAct模式深度解析:让AI像人类一样"边想边做"

0 阅读12分钟

AI Agent实战系列·第2篇
LangChain、AutoGPT背后的核心思想

写在前面

上期回顾: 第1篇我们用100行代码实现了一个最简单的Agent,理解了"感知-决策-执行"的基本循环。

本期你将收获:

  • ✅ 理解ReAct论文的核心思想
  • ✅ 掌握Thought-Action-Observation模式
  • ✅ 实现一个生产级的ReAct Agent
  • ✅ 学会设计高质量的Prompt

一、从一个真实失败案例说起

上周我让第一版的Agent帮我做数据分析,结果翻车了:

任务:分析sales.csv,找出销售额最高的月份

Agent的执行过程:
🔧 读取文件 sales.csv
📊 文件内容:date,product,sales\n2024-01...

💭 思考:我看到了数据,现在需要分析
🔧 使用计算器:sum(数据)
❌ 错误:计算器无法处理这种数据

💭 思考:那我试试搜索
🔧 搜索:如何分析CSV数据
📊 搜索结果:[一堆教程]

💭 思考:emmm...我不知道该怎么办了
❌ 任务失败

问题出在哪?

Agent虽然知道要"边想边做",但它的思考质量太低

  • ❌ 没有明确的推理链条
  • ❌ 不知道自己做到了哪一步
  • ❌ 缺乏对结果的反思

这就像一个新手解题:虽然知道要步骤,但思路混乱。


二、ReAct:给Agent装上"推理引擎"

2022年,普林斯顿和Google的研究员提出了ReAct(Reasoning and Acting)模式,彻底解决了这个问题。

2.1 ReAct的核心理念

ReAct = Reasoning(推理)+ Acting(行动)

传统Agent:想一下 → 做 → 看结果 → 再想
ReAct Agent:详细推理 → 做 → 反思结果详细推理 → 做...

2.2 ReAct的三要素

┌─────────────────────────────────────┐
│                                     │
│  Thought (思考)                     │
│    - 当前状态是什么?               │
│    - 我要达成什么目标?             │
│    - 下一步该做什么?               │
│    - 为什么要这样做?               │
│    ↓                                │
│  Action (行动)                      │
│    - 具体的工具调用                 │
│    ↓                                │
│  Observation (观察)                 │
│    - 执行结果                       │
│    - 是否符合预期?                 │
│    - 需要调整策略吗?               │
│    ↓                                │
└─────┘ (循环)                        │

2.3 对比理解

让我们看一个具体例子:

任务: 查询"北京明天的天气温度是否适合穿短袖"

❌ 普通Agent的思考

行动:查询天气
结果:明天25度
行动:搜索穿衣建议
结果:25度可以穿短袖
完成

✅ ReAct Agent的思考

Thought: 这个问题需要两步:
         1. 先获取明天北京的温度
         2. 判断这个温度是否适合穿短袖
         让我先查天气

Action: weather_api
Action Input: {"city": "北京", "date": "明天"}

Observation: 明天北京 15-25度,多云

Thought: 温度范围是15-25度。这个温度波动较大。
         早晚15度偏凉,中午25度较暖。
         我需要考虑全天情况,给出更周到的建议。
         短袖可能不够,建议带件外套。

Action: Finish
Action Input: 明天北京温度15-25度,建议穿长袖+外套,
             中午热的话可以脱掉外套,但不建议只穿短袖。

Final Answer: [详细建议]

看到区别了吗?ReAct会:

  1. 📝 明确说明推理过程
  2. 🎯 清楚知道当前进度
  3. 🤔 对结果进行深度分析
  4. 💡 给出更周到的建议

三、ReAct Prompt设计精髓

ReAct的魔力90%来自于精心设计的Prompt。让我们深入拆解:

3.1 经典ReAct Prompt模板

REACT_PROMPT = """
你是一个问题解决专家,可以使用以下工具:

{tool_descriptions}

使用以下格式回答问题:

Question: 用户的问题
Thought: 你应该思考要做什么
Action: 要使用的工具,必须是[{tool_names}]之一
Action Input: 工具的输入参数
Observation: 工具的返回结果
... (这个 Thought/Action/Action Input/Observation 可以重复N次)
Thought: 我现在知道最终答案了
Final Answer: 对用户问题的最终回答

重要规则:
1. 每次只能使用一个工具
2. 必须等待Observation后才能继续
3. 详细说明你的推理过程
4. 如果遇到错误,分析原因并调整策略

开始!

Question: {question}
Thought: 
"""

3.2 Prompt设计的5个关键点

1️⃣ 明确的格式约束

# ❌ 模糊的指令
"思考一下然后使用工具"

# ✅ 明确的格式
"使用以下格式:
Thought: [你的思考]
Action: [工具名称]
Action Input: [参数]"

2️⃣ 提供思考框架

# ✅ 好的Thought提示
Thought: 
  - 当前状态:我已经知道了什么?
  - 目标:我要达成什么?
  - 策略:下一步应该做什么?为什么?

3️⃣ 展示Few-shot示例

REACT_PROMPT = """
示例:
Question: 计算23*47的结果是否大于1000
Thought: 我需要先计算23*47,然后判断是否大于1000
Action: calculator
Action Input: 23 * 47
Observation: 1081
Thought: 1081确实大于1000
Final Answer: 是的,23*47=1081,大于1000

现在轮到你了:
Question: {question}
"""

4️⃣ 错误处理指导

如果工具返回错误:
1. 仔细分析错误原因
2. 检查输入参数是否正确
3. 考虑是否需要换个工具
4. 不要重复同样的错误操作

5️⃣ 终止条件

当满足以下条件时使用Final Answer:
1. 你已经有足够信息回答问题
2. 或者,所有可用工具都无法继续推进
3. 给出明确的答案或说明无法完成的原因

四、完整实现:生产级ReAct Agent

现在让我们实现一个真正可用的ReAct Agent。

4.1 工具定义

"""
tools.py - 工具定义
"""

from typing import Dict, Any, Callable
import json

class Tool:
    """工具基类"""
    def __init__(self, name: str, description: str, func: Callable):
        self.name = name
        self.description = description
        self.func = func
    
    def run(self, input_str: str) -> str:
        """执行工具"""
        try:
            # 解析输入
            if input_str.startswith('{'):
                inputs = json.loads(input_str)
                result = self.func(**inputs)
            else:
                result = self.func(input_str)
            
            return str(result)
        except Exception as e:
            return f"Error: {str(e)}"

# ========== 具体工具实现 ==========

def calculator(expression: str) -> float:
    """执行数学计算"""
    try:
        # 安全的计算
        allowed_names = {"abs": abs, "round": round, "min": min, "max": max}
        result = eval(expression, {"__builtins__": {}}, allowed_names)
        return result
    except:
        raise ValueError(f"无法计算表达式: {expression}")

def search(query: str) -> str:
    """网络搜索(这里用模拟数据)"""
    # 实际应该调用搜索API
    mock_results = {
        "天气": "北京明天 15-25度,多云",
        "新闻": "今日头条新闻...",
    }
    
    for key in mock_results:
        if key in query:
            return mock_results[key]
    
    return f"关于'{query}'的搜索结果:[模拟数据]"

def wikipedia(query: str) -> str:
    """维基百科查询(模拟)"""
    return f"维基百科关于'{query}'的信息:[模拟内容]"

# ========== 创建工具实例 ==========

TOOLS = [
    Tool(
        name="Calculator",
        description="用于数学计算。输入:数学表达式(如'23*47')",
        func=calculator
    ),
    Tool(
        name="Search",
        description="搜索实时信息。输入:搜索关键词",
        func=search
    ),
    Tool(
        name="Wikipedia",
        description="查询维基百科。输入:查询主题",
        func=wikipedia
    )
]

4.2 ReAct Agent核心实现

"""
react_agent.py - ReAct Agent实现
"""

import re
import openai
from typing import List, Dict
from tools import Tool

class ReActAgent:
    """ReAct模式的Agent实现"""
    
    def __init__(self, tools: List[Tool], model: str = "gpt-3.5-turbo"):
        self.tools = {tool.name: tool for tool in tools}
        self.model = model
    
    def run(self, question: str, max_iterations: int = 10, verbose: bool = True):
        """执行任务"""
        # 构建初始prompt
        prompt = self._build_prompt(question)
        
        if verbose:
            print(f"\n{'='*60}")
            print(f"🎯 Question: {question}")
            print(f"{'='*60}\n")
        
        iteration = 0
        while iteration < max_iterations:
            iteration += 1
            
            # 调用LLM
            response = self._call_llm(prompt)
            
            if verbose:
                print(f"【第{iteration}轮】")
                print(response)
                print()
            
            # 检查是否完成
            if "Final Answer:" in response:
                answer = self._extract_final_answer(response)
                if verbose:
                    print(f"{'='*60}")
                    print(f"✅ Final Answer: {answer}")
                    print(f"{'='*60}\n")
                return answer
            
            # 解析并执行Action
            action, action_input = self._parse_action(response)
            
            if action:
                # 执行工具
                observation = self._execute_action(action, action_input)
                
                if verbose:
                    print(f"📊 Observation: {observation}")
                    print(f"{'-'*60}\n")
                
                # 更新prompt
                prompt += f"{response}\nObservation: {observation}\nThought: "
            else:
                # 没有找到有效的Action,提示继续
                prompt += f"{response}\nThought: "
        
        return "任务未在规定步数内完成"
    
    def _build_prompt(self, question: str) -> str:
        """构建ReAct prompt"""
        # 工具描述
        tool_descriptions = "\n".join([
            f"- {name}: {tool.description}"
            for name, tool in self.tools.items()
        ])
        
        tool_names = ", ".join(self.tools.keys())
        
        prompt = f"""Answer the following question using these tools:

{tool_descriptions}

Use this format:

Question: the input question
Thought: think about what to do step by step
Action: the tool to use, must be one of [{tool_names}]
Action Input: the input to the tool
Observation: the result from the tool
... (repeat Thought/Action/Action Input/Observation as needed)
Thought: I now know the final answer
Final Answer: the final answer to the question

Important:
- Think step by step
- Only use one tool at a time
- Wait for Observation before continuing
- If you make a mistake, analyze why and try a different approach

Begin!

Question: {question}
Thought: """
        
        return prompt
    
    def _call_llm(self, prompt: str) -> str:
        """调用大语言模型"""
        response = openai.ChatCompletion.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0,
            max_tokens=500
        )
        return response.choices[0].message.content.strip()
    
    def _parse_action(self, response: str) -> tuple:
        """解析Action和Action Input"""
        # 匹配 Action: xxx
        action_match = re.search(r"Action:\s*(.+?)(?:\n|$)", response)
        # 匹配 Action Input: xxx
        input_match = re.search(r"Action Input:\s*(.+?)(?:\n|$)", response)
        
        if action_match and input_match:
            action = action_match.group(1).strip()
            action_input = input_match.group(1).strip()
            return action, action_input
        
        return None, None
    
    def _execute_action(self, action: str, action_input: str) -> str:
        """执行工具"""
        if action not in self.tools:
            return f"Error: Tool '{action}' not found. Available tools: {list(self.tools.keys())}"
        
        tool = self.tools[action]
        return tool.run(action_input)
    
    def _extract_final_answer(self, response: str) -> str:
        """提取最终答案"""
        match = re.search(r"Final Answer:\s*(.+?)(?:\n|$)", response, re.DOTALL)
        if match:
            return match.group(1).strip()
        return response

4.3 使用示例

"""
main.py - 使用示例
"""

from react_agent import ReActAgent
from tools import TOOLS

# 设置API Key
import openai
openai.api_key = "your-api-key-here"

# 创建Agent
agent = ReActAgent(tools=TOOLS)

# 测试1:简单计算
print("\n" + "="*60)
print("测试1:计算问题")
print("="*60)
agent.run("计算 (123 + 456) * 2 的结果是多少?")

# 测试2:需要搜索
print("\n" + "="*60)
print("测试2:需要搜索信息")
print("="*60)
agent.run("北京明天的天气怎么样?")

# 测试3:多步推理
print("\n" + "="*60)
print("测试3:复杂推理")
print("="*60)
agent.run("23乘以47的结果是否大于1000?")

4.4 运行效果

============================================================
🎯 Question: 23乘以47的结果是否大于1000?
============================================================

【第1轮】
Thought: 我需要先计算23乘以47的值,然后判断结果是否大于1000
Action: Calculator
Action Input: 23 * 47

📊 Observation: 1081
------------------------------------------------------------

【第2轮】
Thought: 计算结果是1081,我现在需要判断1081是否大于1000。
显然,1081 > 1000,所以答案是肯定的。
Final Answer: 是的,23×47=1081,这个结果大于1000

============================================================
 Final Answer: 是的,23×47=1081,这个结果大于1000
============================================================

五、ReAct vs 普通Agent:实测对比

让我们用同一个任务测试两种Agent:

任务: "分析一下,如果我每天存100元,一年能存多少钱?如果这些钱投资年化5%的理财,一年后总共有多少?"

普通Agent的表现

【第1轮】
思考:计算一年的存款
工具:calculator(100 * 365)
结果:36500

【第2轮】
思考:计算理财收益
工具:calculator(36500 * 0.05)
结果:1825

【第3轮】
答案:37325

问题: 答案错了!理财收益应该是逐步投入的,不是一次性投入。

ReAct Agent的表现

【第1轮】
Thought: 这个问题有两个部分:
1. 每天存100元,一年总存款
2. 这些钱投资理财的收益

第一个问题简单,100*365即可。
但第二个问题需要注意:钱是每天存入的,不是一次性投入,
所以收益计算要考虑时间因素。

先算总存款:
Action: Calculator
Action Input: 100 * 365

Observation: 36500

【第2轮】
Thought: 一年总存款36500元。

现在关键问题是理财收益。由于是每天存入,
平均每笔钱只存了半年(第一天的钱存365天,最后一天的钱存1天,
平均约182.5天)。

简化计算:36500 * 0.05 * 0.5 = 912.5
这是个近似值。

Action: Calculator  
Action Input: 36500 * 0.05 * 0.5

Observation: 912.5

【第3轮】
Thought: 理财收益约912.5元。
总额 = 本金36500 + 收益912.5 = 37412.5

Action: Calculator
Action Input: 36500 + 912.5

Observation: 37412.5

【第4轮】
Thought: 我现在知道最终答案了

Final Answer: 
如果每天存100元,一年总共存入36500元。
由于是逐日存入(而非一次性投入),平均存期约半年,
按年化5%理财,收益约912.5元。
一年后总共约有37412.5元。

注:这是简化计算,实际收益取决于具体的计息方式。

看到区别了吗?

  • ReAct考虑了逐日投入的特点
  • 提供了更准确的计算逻辑
  • 给出了注意事项

六、ReAct的5个进阶技巧

技巧1:添加自我反思

Thought: 我刚才的做法是否正确?
- 检查:输入参数是否有误?
- 检查:选择的工具是否合适?
- 检查:逻辑推理是否严密?

技巧2:使用中间变量

Thought: 让我定义一些变量来组织思路
- total_days = 365
- daily_saving = 100
- annual_rate = 0.05
这样更清晰

技巧3:分解复杂问题

Thought: 这个问题比较复杂,让我分解为3个子问题:
1. 先解决A
2. 再解决B  
3. 最后整合

技巧4:记录已知信息

Thought: 目前我已知:
- X = 100
- Y = 200
还需要知道:
- Z = ?

技巧5:错误恢复

Thought: 上一步出错了,让我分析原因:
错误信息:XXX
可能原因:YYY
新的策略:ZZZ

七、常见问题与解决方案

Q1: 为什么Agent一直重复同样的操作?

原因: Prompt没有强调"不要重复"

解决:

prompt += """
Important: 
- If an action failed, try a DIFFERENT approach
- Don't repeat the same action with the same input
"""

Q2: Agent的思考太简单怎么办?

解决: 使用思考模板

Thought: 
- Current situation: [what I know]
- Goal: [what I want to achieve]  
- Plan: [next step and why]
- Potential issues: [what could go wrong]

Q3: 如何处理工具返回的错误?

解决: 在Observation后添加错误处理提示

if "Error" in observation:
    prompt += "\n(Hint: The tool returned an error. Analyze the error and try a different approach)"

八、实战练习

练习1:增强计算能力

任务: 让Agent处理这个问题

"计算从1到100的所有偶数之和"

提示: 可能需要循环或使用数学公式

练习2:多工具协作

任务: 添加一个文件读取工具

def read_file(filename: str) -> str:
    with open(filename, 'r') as f:
        return f.read()

让Agent读取文件并分析内容

练习3:优化Prompt

任务: 改进Prompt,让Agent:

  • 思考更详细
  • 更少出错
  • 给出更好的答案

总结

今天我们深入学习了ReAct模式:

核心思想: Reasoning(推理) + Acting(行动)
关键要素: Thought → Action → Observation循环
成功关键: 高质量的Prompt设计
实战价值: 显著提升Agent的可靠性和智能程度

💡 核心洞察: ReAct的强大在于"显式推理"——让AI把思考过程说出来,就像教学生做题要写步骤一样。


下期预告

第3篇:《工具系统设计:给AI Agent装上"手和脚"》

下期我们将深入探讨:

  • 如何设计一个健壮的工具系统
  • Docker沙箱:让Agent安全地执行代码
  • 5个强大的实用工具实现
  • 工具编排和链式调用

如果这篇文章对你有帮助,欢迎点赞、分享!

本文是《AI Agent实战系列》的文章,后续还会更新AI Agent进阶玩法。如果你也AI Agent感兴趣,请关注公众号【架构之旅】,第一时间解锁全套实战教程,错过不再补~