Skip to content

02. ReAct 循环与动态推演:构建能行其思的自主系统

在第一章中,我们认识到 Plan-and-Solve(规划-求解)范式在静态长线任务中的优势,但它在面对高动态性、不可预知的外部世界时常常“抓瞎”——现实世界中的 API 可能会超时、返回空数据,甚至返回一个完全推翻预设假设的新事实。

2022 年由普林斯顿大学与 Google 团队联合提出的 ReAct(Reasoning + Acting)范式,正是解决这一动态交互困境的核心基石。本章将深入剖析 ReAct 的动态闭环机理,嵌入实时交互模拟器,并讲解生产环境中防死循环、步数熔断与自愈反思的工程解法。


一、ReAct 范式起源:推理与行动的辩证统一

在智能体演进史上,学术界曾经探索过两条相互割裂的技术路径:

三大范式深度对比矩阵

比较维度纯推理 (Reasoning Only)纯行动 (Acting Only)ReAct (协同共生)
典型代表Standard CoT / ScratchpadWebGPT / 经典强化学习策略网络LangChain Agent / AutoGPT / Claude Code
知识来源仅依赖参数权重 (Parametric Memory)仅依赖当前环境 Observation参数权重推理 + 外部环境动态回传
致命缺陷产生虚假幻觉、无法获知最新事实动作盲目、探索空间过大时极易迷失多轮 Token 消耗增加、需严密防死循环
可解释性高(有人类可读的思考步骤)极低(黑盒动作序列)最高(思考与动作交替验证,清晰可审计)
纠错能力无(生成即终局)弱(依靠 reward 缓慢收敛)极强(根据工具报错自主反思修改参数)

💡 为什么必须将 Reasoning 与 Acting 结合?

  • Reasoning 指导 Action:用自然语言思维链规划下一步该调用什么工具、构造什么参数,并解释为什么这么做。
  • Action 产生的 Observation 反哺 Reasoning:外界返回的真实数据成为下一步思考的严谨事实锚点,动态修正偏差与初始假设。

二、TAO 状态机模型:Thought-Action-Observation 循环

ReAct 智能体的核心心跳由经典的三元组构成的闭环所驱动,简称为 TAO 循环(The TAO Loop)

典型的 ReAct 提示词底层契约 (Few-Shot Prompt Template)

在没有原生 Function Calling 特性之前,早期 ReAct 是通过高度结构化的提示词来约束模型遵循这一格式的:

text
你是一个具备工具调用能力的自主智能体。针对用户提出的问题,你必须严格按照以下格式交替进行:

Question: 需要解答的用户问题
Thought: 你对当前状况的分析、接下来的行动意图和工具选择理由
Action: 调用的工具名称,例如 [search, calculate, weather]
Action Input: 传入工具的参数,例如 "深圳 未来3天天气"
Observation: 工具执行后返回的客观现实数据
... (Thought/Action/Action Input/Observation 的循环可以重复多次)
Thought: 我已经获得了所有必需信息,可以给出最终结论
Final Answer: 最终向用户输出的完整回答

三、在线交互仿真:体验 ReAct 循环流转

为了建立直观的动态心智模型,下方是全站内置的 ReAct 智能体交互仿真器

请点击右上角的 “下一步 ➔” 按钮,逐步观察一个真实天气查询与穿衣决策场景下,Agent 的内部思考(Thought)、外部工具交互(Action)、环境回传(Observation)以及最终结案(Final Answer)的完整生命周期:

ReAct 循环交互仿真器 (Thought ➔ Action ➔ Observation)
1
用户输入
2
Thought 1
3
Action 1
4
Observation
5
Thought 2
6
Final Answer
👤User Prompt (原始指令)
用户通过自然语言提出复合任务:"帮我查询深圳未来3天的天气,并根据结果输出一份穿衣建议。"
User: 帮我查询深圳未来3天的天气,并根据结果输出一份穿衣建议。

🔍 仿真组件深度观察指南

  1. 步骤 1 (用户输入):自然语言复合需求注入,模型识别出隐式约束(深圳 + 3天 + 穿衣建议)。
  2. 步骤 2 (Thought 1):Agent 意识到自己没有当天的天气事实,因此决策发起 weather_service.query
  3. 步骤 3 (Action 1):生成结构化参数对象,交由外部天气 API 执行。
  4. 步骤 4 (Observation):外部环境返回了结构化的多天天气 JSON 数据,此时模型上下文被追加了这一块真实数据。
  5. 步骤 5 (Thought 2):拿到数据后,Agent 并没有机械地直接把 JSON 扔给用户,而是分析数据中的“降温”、“雨水”特征,推导出了“防风外套、雨具”的衍生决策逻辑。
  6. 步骤 6 (Final Answer):任务目标完全达成,跳出循环向用户交付温暖专业的排版建议。

四、生产级工程挑战与治理体系

原型 Demo 跑通一次 ReAct 很容易,但在生产环境中让智能体全天候稳定运行,必须面临三大工程挑战:

1. 死循环预防 (Infinite Loop Prevention)

模型可能会因为幻觉或外部工具输出不符合预期,陷入反复调用相同工具的死结中。

  • 参数签名哈希比对(Action Hash Deduplication):系统在运行时维护一个滑动窗口内的 hash(Action + Action_Input) 集合。如果检测到完全相同的工具调用连续出现超过 2 次,立即拦截该调用,强制向模型注入警告 Observation:“系统检测到你正在重复调用相同参数的工具,请换一种查询策略或尝试其他工具。
  • 语义停滞检测(Semantic Stagnation Detection):评估连续两次 Thought 的语义相似度,若相似度过高且无新 Action 发生,触发中断。

2. 最大步数限制与预算控制 (Max Iterations Budget)

每一次 TAO 循环都会消耗昂贵的 LLM API 调用与往返延迟:

  • 硬性步数上限(Hard Cap):通常针对单智能体设置 max_iterations = 6 ~ 10
  • 倒计时警示(Countdown Warning):当达到 max_iterations - 1 步时,在 System Prompt 中动态追加高优先级指令:“警告:你只剩下最后 1 步行动机会。请停止所有新工具调用,基于目前已知信息总结并输出 Final Answer。
  • 熔断降级(Circuit Breaking):一旦突破步数预算,系统主动退出并向用户返回当前阶段性成果或友好提示,绝不让请求无限悬挂。

3. 反思机制与自愈(Self-Correction & Reflexion)

当工具抛出异常(如 SQL 语法错误、网络超时、缺少字段)时,简单粗暴的报错重试通常只会重蹈覆辙。

2023 年麻省理工学院等提出的 Reflexion 架构 引入了情景反思记忆(Episodic Reflection Memory)


五、工业级 Python 核心代码实现剖析

以下是一个具备步数熔断、参数重复拦截与异常自愈能力的完整微型 ReAct 引擎实现:

python
import hashlib
from typing import Dict, Callable, Any, Optional

class RobustReActEngine:
    def __init__(self, llm_client, tools: Dict[str, Callable], max_iterations: int = 6):
        self.llm = llm_client
        self.tools = tools
        self.max_iterations = max_iterations
        self.action_history_hashes = set()

    def _hash_action(self, action_name: str, action_input: str) -> str:
        """计算动作签名哈希,用于检测死循环"""
        raw = f"{action_name.strip()}::{action_input.strip()}"
        return hashlib.md5(raw.encode()).hexdigest()

    def run(self, user_query: str) -> str:
        messages = [
            {"role": "system", "content": self._build_system_prompt()},
            {"role": "user", "content": user_query}
        ]
        
        current_step = 0
        while current_step < self.max_iterations:
            current_step += 1
            print(f"\n--- [Cycle {current_step}/{self.max_iterations}] ---")

            # 若临近预算上限,发出迫近警告
            if current_step == self.max_iterations:
                messages.append({
                    "role": "user", 
                    "content": "【系统通知】已达到最大思考预算,请立刻输出 Final Answer 总结交付!"
                })

            # 1. 触发 LLM 认知推理
            step_output = self.llm.chat(messages)
            print(f"Agent 输出:\n{step_output}")
            messages.append({"role": "assistant", "content": step_output})

            # 2. 检查是否达成最终答案
            if "Final Answer:" in step_output:
                return step_output.split("Final Answer:")[-1].strip()

            # 3. 解析 Action 与 Action Input
            action, action_input = self._parse_action(step_output)
            if not action:
                # 既无 Final Answer 也无有效 Action,引导纠错
                messages.append({
                    "role": "user", 
                    "content": "错误:未检测到有效 Action 或 Final Answer,请修正输出格式!"
                })
                continue

            # 4. 死循环拦截检测
            action_hash = self._hash_action(action, action_input)
            if action_hash in self.action_history_hashes:
                observation = "【系统拦截】你已尝试过完全相同的工具与参数调用!禁止重复,请换用其他策略或工具!"
            else:
                self.action_history_hashes.add(action_hash)
                # 5. 执行外部工具并进行异常兜底捕获 (Self-Correction 基础)
                observation = self._execute_tool_safely(action, action_input)

            print(f"Observation: {observation}")
            messages.append({"role": "user", "content": f"Observation: {observation}"})

        return "【执行超时】智能体已达最大允许推演步数,任务未能完全收敛。"

    def _execute_tool_safely(self, action: str, action_input: str) -> str:
        if action not in self.tools:
            return f"错误:未找到名为 '{action}' 的工具。可用工具列表: {list(self.tools.keys())}"
        try:
            return str(self.tools[action](action_input))
        except Exception as e:
            return f"工具执行异常 [{type(e).__name__}]: {str(e)}。请反思输入参数后重新调用。"

    def _parse_action(self, text: str):
        # 简单提取 Action: xxx 与 Action Input: yyy
        # 生产环境中推荐直接采用 Function Calling 结构化输出
        action, action_input = None, ""
        for line in text.splitlines():
            if line.startswith("Action:"):
                action = line.replace("Action:", "").strip()
            elif line.startswith("Action Input:"):
                action_input = line.replace("Action Input:", "").strip()
        return action, action_input

    def _build_system_prompt(self) -> str:
        tools_doc = "\n".join([f"- {name}: {fn.__doc__}" for name, fn in self.tools.items()])
        return f"可用工具列表:\n{tools_doc}\n遵循 Thought -> Action -> Action Input -> Observation 流程。"

六、本章小结

🔑 核心架构法则

  1. 核心价值:ReAct 将静态逻辑演绎动态环境反馈深度啮合,形成了感知-决策-行动-观测的有机生命体。
  2. 状态流转:TAO(Thought-Action-Observation)循环使得模型能够在每一步都依据客观物理现实重新校正航向。
  3. 确定性防护:在没有步数预算(Max Iterations)、签名去重与反思纠偏(Reflexion)保护的前提下,永远不要直接将裸奔的 ReAct 循环上线生产。

在下一章中,我们将进一步考察智能体是如何与外部世界进行工业级标准对接的:从传统 OpenAI Function Calling 到颠覆性的 Anthropic MCP(Model Context Protocol)协议

👉 下一章:03. 工具调用与 MCP 标准 (含交互报文) →

学思并济 · 躬行求索 | Released under MIT License