ReAct框架深度解析:让Agent会思考再行动

🧠 Reasoning + Acting | 思维链 + 工具调用 | 数学原理 + 代码实现 | LangChain实战 | 完整项目代码


📖 什么是ReAct框架?

核心思想

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

复制代码
传统LLM: 输入 → 直接输出答案
ReAct:   输入 → 思考 → 行动 → 观察 → 思考 → 行动 → ... → 输出

关键洞察:

  • 人类解决问题时,会交替进行"思考"和"行动"
  • 纯思维链(CoT)缺乏外部信息
  • 纯工具调用缺乏推理过程
  • ReAct结合两者优势

为什么需要ReAct?

问题1:纯LLM的局限
复制代码
# 问题:2023年诺贝尔文学奖得主的出生地人口是多少?

# 纯LLM回答
llm.invoke("2023年诺贝尔文学奖得主的出生地人口是多少?")
# 输出: "抱歉,我不知道" ❌
# 原因:训练数据截止,无法获取最新信息
问题2:纯工具调用的局限
复制代码
# 直接调用搜索工具
search("2023年诺贝尔文学奖得主")
# 输出: "Jon Fosse"

search("Jon Fosse 出生地")
# 输出: "Haugesund, Norway"

search("Haugesund 人口")
# 输出: "37,000"

# 问题:
# - 没有推理过程,难以调试
# - 无法处理复杂逻辑
# - 容易陷入死循环
ReAct的解决方案
复制代码
Thought: 我需要先找到2023年诺贝尔文学奖得主
Action: search["2023年诺贝尔文学奖得主"]
Observation: Jon Fosse

Thought: 现在我知道得主是Jon Fosse,需要找他的出生地
Action: search["Jon Fosse 出生地"]
Observation: Haugesund, Norway

Thought: 我找到了出生地是Haugesund,现在需要查这个城市的人口
Action: search["Haugesund 人口"]
Observation: 37,000

Thought: 我已经获得了所有信息,可以给出最终答案
Final Answer: 2023年诺贝尔文学奖得主Jon Fosse的出生地Haugesund人口约为37,000人 ✅

优势:

  • ✅ 有清晰的推理过程
  • ✅ 可以获取实时信息
  • ✅ 易于调试和优化
  • ✅ 可解释性强

🏗️ ReAct架构详解

核心组件

复制代码
┌─────────────────────────────────────────┐
│           ReAct Agent                    │
├─────────────────────────────────────────┤
│                                         │
│  ┌──────────┐    ┌──────────┐          │
│  │ Thought  │───→│  Action  │          │
│  │ (推理)   │    │ (行动)   │          │
│  └──────────┘    └────┬─────┘          │
│       ↑               │                │
│       │          ┌────▼─────┐          │
│       │          │Observation│          │
│       │          │ (观察)   │          │
│       │          └──────────┘          │
│       │               │                │
│       └───────────────┘                │
│         迭代循环                        │
└─────────────────────────────────────────┘

执行流程

复制代码
Step 1: 接收用户问题
   ↓
Step 2: Thought - 分析问题,决定下一步
   ↓
Step 3: Action - 选择并执行工具
   ↓
Step 4: Observation - 获取工具返回结果
   ↓
Step 5: 判断是否完成?
   ├─ 是 → Final Answer
   └─ 否 → 回到 Step 2

状态机模型

复制代码
class ReActState:
    """ReAct状态机"""
    
    def __init__(self):
        self.question: str = ""           # 原始问题
        self.thoughts: List[str] = []     # 思考历史
        self.actions: List[dict] = []     # 行动历史
        self.observations: List[str] = [] # 观察历史
        self.final_answer: str = None     # 最终答案
        self.step_count: int = 0          # 步数计数
        self.max_steps: int = 10          # 最大步数

🛠️ ReAct数学原理

形式化定义

ReAct可以看作一个马尔可夫决策过程(MDP)

状态空间 S:

  • s_t = (q, h_t)
  • q: 原始问题
  • h_t: 历史轨迹 {(thought_1, action_1, obs_1), ..., (thought_t, action_t, obs_t)}

动作空间 A:

  • a_t ∈ {Tool_1, Tool_2, ..., Tool_n, Finish}
  • 可以选择的工具或结束

转移函数 T:

  • s_{t+1} = T(s_t, a_t)
  • 执行动作a_t,获得观察obs_t,更新历史

奖励函数 R:

  • R = 1 如果最终答案正确
  • R = 0 否则

目标:

  • 最大化期望奖励:max E[R | policy]
  • policy: π(a_t | s_t) - 策略函数

与传统方法的对比

方法 公式 特点
Standard Prompting P(y | x) 直接生成答案
Chain-of-Thought P(y, r | x) 生成推理+答案
Tool Use P(a | x) → y 选择工具→执行
ReAct P(r, a, o | x, h) 推理+行动+观察

其中:

  • x: 输入问题
  • y: 最终答案
  • r: 推理(thought)
  • a: 行动(action)
  • o: 观察(observation)
  • h: 历史轨迹

💻 ReAct代码实现

方案1:从零实现ReAct

复制代码
from typing import List, Dict, Optional
import re


class ReActAgent:
    """ReAct Agent实现"""
    
    def __init__(self, llm, tools: List[callable], max_steps: int = 10):
        self.llm = llm
        self.tools = {tool.__name__: tool for tool in tools}
        self.max_steps = max_steps
        
        # 历史记录
        self.thoughts = []
        self.actions = []
        self.observations = []
    
    def run(self, question: str) -> str:
        """运行ReAct流程
        
        Args:
            question: 用户问题
            
        Returns:
            最终答案
        """
        print(f"🤔 问题: {question}\n")
        
        for step in range(self.max_steps):
            print(f"--- Step {step + 1} ---")
            
            # Step 1: Thought - 生成思考
            thought = self._generate_thought(question)
            self.thoughts.append(thought)
            print(f"💭 Thought: {thought}")
            
            # Step 2: Action - 解析并执行行动
            action_name, action_input = self._parse_action(thought)
            
            if action_name == "Finish":
                # 完成任务
                final_answer = action_input
                print(f"✅ Final Answer: {final_answer}")
                return final_answer
            
            # 执行工具
            if action_name in self.tools:
                observation = self.tools[action_name](action_input)
                self.actions.append({"name": action_name, "input": action_input})
                self.observations.append(observation)
                print(f"🔧 Action: {action_name}({action_input})")
                print(f"👁️  Observation: {observation}\n")
            else:
                observation = f"未知工具: {action_name}"
                print(f"❌ {observation}\n")
        
        # 超过最大步数
        return "抱歉,我无法在限定步数内解决这个问题"
    
    def _generate_thought(self, question: str) -> str:
        """生成思考内容"""
        # 构建提示词
        history = self._format_history()
        
        prompt = f"""你是一个智能助手,使用ReAct框架解决问题。

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

当前问题:{question}

{history}

请按照以下格式回答:
Thought: <你的思考>
Action: <工具名>[<参数>]

或者如果你已经知道答案:
Thought: 我已经知道了答案
Action: Finish[<最终答案>]
"""
        
        response = self.llm.invoke(prompt).content
        return response
    
    def _parse_action(self, thought: str) -> tuple:
        """解析行动"""
        # 提取Action行
        action_match = re.search(r'Action:\s*(\w+)\[(.+?)\]', thought)
        
        if action_match:
            action_name = action_match.group(1)
            action_input = action_match.group(2)
            return action_name, action_input
        
        # 如果没有找到Action,默认Finish
        return "Finish", thought
    
    def _format_history(self) -> str:
        """格式化历史记录"""
        if not self.thoughts:
            return ""
        
        history_parts = []
        for i, (thought, obs) in enumerate(zip(self.thoughts, self.observations)):
            history_parts.append(f"Step {i+1}:")
            history_parts.append(f"Thought: {thought}")
            if i < len(self.actions):
                action = self.actions[i]
                history_parts.append(f"Action: {action['name']}[{action['input']}]")
            if obs:
                history_parts.append(f"Observation: {obs}")
            history_parts.append("")
        
        return "\n".join(history_parts)
    
    def _format_tools(self) -> str:
        """格式化工具列表"""
        tools_desc = []
        for name, tool in self.tools.items():
            doc = tool.__doc__ or "无描述"
            tools_desc.append(f"- {name}: {doc.split(chr(10))[0]}")
        return "\n".join(tools_desc)


# ==================== 工具定义 ====================

def search(query: str) -> str:
    """搜索网络信息"""
    # 模拟搜索引擎
    knowledge_base = {
        "2023年诺贝尔文学奖得主": "Jon Fosse",
        "Jon Fosse 出生地": "Haugesund, Norway",
        "Haugesund 人口": "37,000",
        "Python最新版本": "Python 3.12",
        "北京天气": "晴,25°C"
    }
    
    return knowledge_base.get(query, f"未找到'{query}'的信息")


def calculate(expression: str) -> str:
    """计算数学表达式"""
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return str(result)
    except:
        return "计算错误"


# ==================== 使用示例 ====================

def example_react_agent():
    """ReAct Agent使用示例"""
    
    from langchain_openai import ChatOpenAI
    
    # 创建LLM
    llm = ChatOpenAI(model="gpt-4", temperature=0)
    
    # 创建Agent
    agent = ReActAgent(
        llm=llm,
        tools=[search, calculate],
        max_steps=10
    )
    
    # 测试问题
    question = "2023年诺贝尔文学奖得主的出生地人口是多少?"
    
    answer = agent.run(question)
    print(f"\n🎯 最终答案: {answer}")


if __name__ == "__main__":
    example_react_agent()

方案2:使用LangChain ReAct

复制代码
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate


# 定义工具
@tool
def search_knowledge(query: str) -> str:
    """搜索知识库获取信息"""
    knowledge_base = {
        "2023年诺贝尔文学奖": "Jon Fosse",
        "Jon Fosse": "挪威作家,出生于Haugesund",
        "Haugesund": "挪威城市,人口约37,000"
    }
    
    for key, value in knowledge_base.items():
        if key in query:
            return value
    
    return "未找到相关信息"


@tool
def calculate_math(expression: str) -> str:
    """计算数学表达式"""
    try:
        result = eval(expression, {"__builtins__": {}}, {})
        return str(result)
    except:
        return "计算错误"


# 创建ReAct Agent
def create_react_agent_example():
    """创建LangChain ReAct Agent"""
    
    llm = ChatOpenAI(model="gpt-4", temperature=0)
    
    tools = [search_knowledge, calculate_math]
    
    # ReAct提示词模板
    react_prompt = PromptTemplate.from_template("""
Answer the following questions as best you can. You have access to the following tools:

{tools}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: {input}
Thought:{agent_scratchpad}
""")
    
    # 创建Agent
    agent = create_react_agent(llm, tools, react_prompt)
    
    # 创建执行器
    agent_executor = AgentExecutor(
        agent=agent,
        tools=tools,
        verbose=True,
        max_iterations=10,
        handle_parsing_errors=True
    )
    
    return agent_executor


# 使用示例
def example_langchain_react():
    """LangChain ReAct示例"""
    
    agent = create_react_agent_example()
    
    question = "2023年诺贝尔文学奖得主的出生地人口是多少?"
    
    result = agent.invoke({"input": question})
    
    print(f"\n✅ 答案: {result['output']}")


if __name__ == "__main__":
    example_langchain_react()

🎯 ReAct优化技巧

1. 提示词工程优化

复制代码
# 好的ReAct提示词
react_prompt = """
你是一个智能助手,使用ReAct框架逐步解决问题。

可用工具:
- search: 搜索信息
- calculate: 数学计算
- get_weather: 查询天气

重要规则:
1. 每次只执行一个动作
2. 仔细观察每个动作的结果
3. 基于观察结果进行下一步推理
4. 当确信知道答案时,使用Finish

格式:
Thought: <推理过程>
Action: <工具名>[<参数>]
Observation: <工具返回>
...
Final Answer: <最终答案>
"""

2. 工具设计优化

复制代码
# 好的工具设计
@tool
def search_precise(query: str) -> str:
    """精确搜索特定信息
    
    参数应该是具体的查询词,如:
    - "2023年诺贝尔文学奖得主"
    - "Python 3.12发布日期"
    
    避免模糊查询。
    """
    pass

# 不好的工具设计
@tool
def search(x):
    """搜索"""
    pass

3. 早期停止策略

复制代码
class SmartReActAgent(ReActAgent):
    """智能ReAct Agent - 带早期停止"""
    
    def _should_stop(self, thought: str, observation: str) -> bool:
        """判断是否应该停止"""
        
        # 策略1:观察到明确答案
        if "答案是" in observation or "结果是" in observation:
            return True
        
        # 策略2:重复观察(可能陷入循环)
        if observation in self.observations[-3:]:
            return True
        
        # 策略3:达到置信度阈值
        confidence = self._estimate_confidence(thought, observation)
        if confidence > 0.9:
            return True
        
        return False
    
    def _estimate_confidence(self, thought: str, observation: str) -> float:
        """估计答案置信度"""
        # 简化实现:基于关键词
        confidence_keywords = ["确定", "确认", "显然是", "毫无疑问"]
        score = sum(1 for kw in confidence_keywords if kw in thought)
        return min(score / len(confidence_keywords), 1.0)

4. 并行探索(Tree of Thoughts扩展)

复制代码
class ParallelReActAgent(ReActAgent):
    """并行ReAct - 同时探索多个思路"""
    
    def run_parallel(self, question: str, num_paths: int = 3) -> str:
        """并行运行多个ReAct路径"""
        
        results = []
        
        for i in range(num_paths):
            # 每个路径独立运行
            agent_copy = self.clone()
            result = agent_copy.run(question)
            results.append({
                "path": i + 1,
                "answer": result,
                "steps": agent_copy.step_count
            })
        
        # 投票选择最佳答案
        best_answer = self._vote(results)
        
        return best_answer
    
    def _vote(self, results: List[dict]) -> str:
        """投票机制"""
        # 统计相同答案的出现次数
        answer_counts = {}
        for r in results:
            answer = r["answer"]
            answer_counts[answer] = answer_counts.get(answer, 0) + 1
        
        # 返回出现最多的答案
        return max(answer_counts, key=answer_counts.get)

📊 ReAct性能评估

评估指标

指标 说明 目标值
准确率 正确答案比例 > 85%
平均步数 完成任务的平均步数 < 5步
成功率 成功完成任务的比例 > 90%
响应时间 从输入到输出的时间 < 10秒
Token消耗 每次任务的Token数 < 2000

基准测试

复制代码
def benchmark_react():
    """ReAct性能基准测试"""
    
    test_cases = [
        {
            "question": "2023年诺贝尔文学奖得主的出生地人口是多少?",
            "expected_answer": "37000"
        },
        {
            "question": "计算 (123 + 456) * 789",
            "expected_answer": "456831"
        },
        {
            "question": "Python的最新版本是什么?",
            "expected_answer": "3.12"
        }
    ]
    
    agent = ReActAgent(llm, tools=[search, calculate])
    
    results = []
    for test in test_cases:
        start_time = time.time()
        answer = agent.run(test["question"])
        elapsed = time.time() - start_time
        
        # 评估准确性
        is_correct = test["expected_answer"] in answer
        
        results.append({
            "question": test["question"],
            "correct": is_correct,
            "steps": agent.step_count,
            "time": elapsed
        })
    
    # 统计结果
    accuracy = sum(1 for r in results if r["correct"]) / len(results)
    avg_steps = sum(r["steps"] for r in results) / len(results)
    avg_time = sum(r["time"] for r in results) / len(results)
    
    print(f"准确率: {accuracy:.2%}")
    print(f"平均步数: {avg_steps:.1f}")
    print(f"平均时间: {avg_time:.2f}秒")

🔍 ReAct vs 其他框架

对比分析

框架 优点 缺点 适用场景
ReAct 推理+行动,可解释性强 步数多,速度慢 复杂多步任务
CoT 简单快速 无法获取外部信息 纯推理任务
Toolformer 自动学习工具使用 训练成本高 大规模部署
Self-Ask 分解子问题 需要预定义分解 结构化问题

选择建议

复制代码
任务类型判断:
├─ 需要外部信息?
│  ├─ 是 → ReAct 或 Self-Ask
│  └─ 否 → CoT
│
├─ 需要多步推理?
│  ├─ 是 → ReAct
│  └─ 否 → Standard Prompting
│
└─ 需要高可解释性?
   ├─ 是 → ReAct
   └─ 否 → Toolformer

💡 最佳实践总结

1. 提示词设计

复制代码
# ✅ 好的提示词
prompt = """
按以下步骤思考:
1. 理解问题的核心
2. 确定需要的信息
3. 选择合适的工具
4. 执行并观察结果
5. 基于结果继续推理
"""

# ❌ 不好的提示词
prompt = "回答问题"

2. 工具粒度

复制代码
# ✅ 细粒度工具(推荐)
@tool
def search_person(name: str): ...

@tool
def search_place(place: str): ...

@tool
def search_number(query: str): ...

# ❌ 粗粒度工具
@tool
def search_anything(query: str): ...

3. 错误处理

复制代码
try:
    observation = tool(action_input)
except Exception as e:
    observation = f"工具执行失败: {str(e)}"
    # 在下一步Thought中处理错误

4. 调试技巧

复制代码
# 启用详细日志
agent = ReActAgent(llm, tools, verbose=True)

# 保存执行轨迹
trajectory = {
    "thoughts": agent.thoughts,
    "actions": agent.actions,
    "observations": agent.observations
}
save_to_file(trajectory, "debug.json")

🔗 相关资源


📝 总结

ReAct框架通过结合推理(Reasoning)行动(Acting),让Agent能够:

像人类一样思考 - 清晰的推理过程

获取外部信息 - 调用工具弥补知识盲区

自我修正 - 基于观察调整策略

可解释性强 - 每一步都有据可查

掌握ReAct框架,你就能构建出真正智能的Agent系统!

下一步: 动手实现一个ReAct Agent,从简单的问答开始,逐步增加复杂度。


专栏: AI Agent实战专栏
日期: 2026年5月12日
系列: Agent底层原理与前沿技术系列第1篇

相关推荐
前端 贾公子1 小时前
从零开始:使用Node.js和Cheerio进行轻量级网页数据提取
前端·vue.js
阿星做前端1 小时前
不想再给ai回复下一步了,于是我给agent装上了一个自动挡
前端·后端·程序员
毛骗导演1 小时前
Skill 还是 Tool?——从 OpenClaw 源码看 Agent 能力扩展的两种范式
前端·架构
周杰伦fans1 小时前
禁止edge浏览器更新
前端·edge
user297525876121 小时前
使用SSE实现流式渲染实践
前端·javascript
LPieces1 小时前
【LPieces-UI】02-Icon组件的设计与实现
前端·vue.js
我本地是好的1 小时前
解决高德地图无外网访问难题:Vue项目代理转发全攻略
前端
wand codemonkey1 小时前
Maven Web 项目 + Tomcat 从零排错全流程(零遗漏版)
前端·tomcat·maven
豆苗学前端1 小时前
【前端内功】同为数据驱动,为什么只有 React 的"心智负担"这么重?(附实战优化指南)
前端·vue.js·面试