ReAct 原理

ReAct(Reasoning + Acting)让 LLM 交替进行推理(Thought)和行动(Action),通过环境反馈(Observation)驱动推理链前进。

问题 → Thought → Action → Observation → Thought → Action → ... → Answer
        ↑                                    ↑
     内部推理                            环境反馈

为什么 ReAct 有效

纯推理(CoT)纯行动ReAct
基于错误事实推理盲目尝试推理指导行动,行动修正推理
无法获取外部信息无法利用中间结论动态获取并整合信息
错误沿链传播每步独立观察反馈纠正方向

Thought-Action-Observation 循环

Thought 1: 分析问题,决定第一步
Action 1: 调用工具
Observation 1: 工具返回结果

Thought 2: 基于观察决定下一步
Action 2: 调用工具
Observation 2: 工具返回结果

Thought N: 已有足够信息
Action: FINISH
Answer: 最终答案

示例

问题: 2024年奥斯卡最佳影片导演出生城市人口?
Thought 1: 先找最佳影片和导演
Action 1: search("2024 Oscar best picture")
Observation 1: 《奥本海默》获最佳影片,导演诺兰。
Thought 2: 诺兰出生城市
Action 2: search("Nolan birthplace")
Observation 2: 出生于伦敦。
Thought 3: 伦敦人口
Action 3: search("London population")
Observation 3: 约898万。
Thought 4: 信息齐全
Action 4: FINISH
Answer: 诺兰出生于伦敦,人口约898万。

ReAct vs CoT

维度CoTReAct
外部信息
错误纠正
延迟
成本
适用场景数学/逻辑需外部信息的复杂任务

工具调用集成

工具定义

from dataclasses import dataclass
from typing import Callable

@dataclass
class Tool:
    name: str
    description: str
    func: Callable
    parameters: dict
    
    def execute(self, **kwargs) -> str:
        try:
            return str(self.func(**kwargs))
        except Exception as e:
            return f"Error: {e}"

tools = [
    Tool("search", "搜索网络", search_web, 
         {"query": {"type": "string"}}),
    Tool("calculate", "数学计算", calculate,
         {"expression": {"type": "string"}}),
]

ReAct Prompt 模板

REACT_PROMPT = """你是能使用工具的智能助手。

可用工具:
{tools_description}

格式:
Thought: 推理过程
Action: 工具名称
Action Input: JSON参数
Observation: (系统自动填入)

完成后:
Thought: 最终推理
Action: FINISH
Final Answer: 最终答案

最多 {max_iterations} 轮。

问题: {question}
Thought 1:"""

ReAct 引擎核心

class ReActEngine:
    def __init__(self, llm, tools, max_iter=10):
        self.llm = llm
        self.tools = {t.name: t for t in tools}
        self.max_iter = max_iter
    
    def run(self, question: str) -> dict:
        history = ""
        for i in range(self.max_iter):
            response = self.llm.generate(self._build_prompt(question, history), temperature=0)
            parsed = self._parse(response)
            if parsed.get('action') == 'FINISH':
                return {'answer': parsed.get('final_answer', ''), 'iterations': i+1}
            name = parsed.get('action', '')
            obs = self.tools[name].execute(**parsed.get('input', {})) if name in self.tools else f"未知工具: {name}"
            history += f"\nThought: {parsed['thought']}\nAction: {name}\nObservation: {obs}\n"
        return {'answer': '达到最大迭代', 'timeout': True}

高级优化

class RobustReActEngine(ReActEngine):
    def _check_loop(self, trace, window=3) -> bool:
        if len(trace) < window * 2:
            return False
        recent = trace[-window:]
        for i in range(len(trace) - window):
            if all(r['action'] == s['action'] for r, s in zip(recent, trace[i:i+window])):
                return True
        return False

适用场景

场景适用性原因
多跳问答⭐⭐⭐⭐⭐需逐步获取信息
数据分析⭐⭐⭐⭐⭐需查询+计算+分析
代码调试⭐⭐⭐⭐需执行代码看结果
创意写作不需外部信息
数学证明⭐⭐CoT 更高效
客服系统⭐⭐⭐⭐需查知识库+执行操作
研究助手⭐⭐⭐⭐⭐信息收集+综合分析

实战建议

  1. 工具描述要精确:选错工具的原因是描述模糊
  2. 限制迭代次数:设 8-10 轮上限
  3. 温度设为 0:ReAct 需要确定性
  4. 结构化输出:用 JSON 模式替代正则解析
  5. 缓存工具结果:减少 API 调用
  6. 日志全量记录:trace 是调试金矿
  7. 成本监控:多轮调用容易产生意外成本
  8. 用 function calling:LLM 支持时用它替代文本解析

ReAct 给 LLM 装上"手和眼"——不只是用脑子想,还能动手做。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。