核心摘要

ReAct(Reasoning + Acting) 模式是 AI Agent 的基础循环。它将显式推理(Thought)、工具执行(Action)与环境反馈(Observation)交织在一起。这个循环让模型能把答案建立在检索到的事实上而非凭空猜测,能处理多步任务,也能从失败的动作中恢复。它强大而简单——但它需要一个硬性迭代上限,也需要把观察结果当作不可信输入,这两点本文都会讲到。内容最后一次技术核验时间为 2026 年 7 月 16 日。

核心要点

  • 先推理,再行动。 ReAct 让模型在运行工具前先陈述计划,于是动作的选择是被解释过的,而非不透明的黑箱。
  • 锚定事实而非猜测。 ReAct Agent 不去编造事实,而是检索它、读取 Observation,再更新自己的判断。
  • 能从错误中恢复。 如果一个动作失败,Agent 可以对失败原因进行推理,并尝试另一条路径。
  • 硬性迭代上限是必需的。 这个循环不会自行停止;没有上限,它可能永远重复同一个失败的动作。
  • 观察结果是不可信输入。 工具的输出可能包含意在操纵模型的文字。要把它当作供阅读的数据,而不是必须服从的指令。

什么是 ReAct 框架?

ReAct(Reasoning and Acting,推理与行动)由普林斯顿大学和谷歌的研究人员在 2022 年的一篇论文中提出。在它出现之前,大语言模型通常以两种孤立的方式被使用:

  1. 纯推理。 提示模型逐步思考(思维链),仅依赖其预训练权重。
  2. 纯行动。 提示模型输出一个命令或 API 调用,但不要求它解释为什么选择该操作。

ReAct 将两者合而为一:推理帮助模型决定该采取哪个动作,而行动收集到的信息又能改善它之后的推理。正是这种结合,让一个 AI Agent 能够面向真实世界工作,而不是困在自己的记忆里。

ReAct 是如何工作的:轨迹循环

ReAct 的核心是一个循环,直到模型认为它已获得最终答案才会停止。每一轮迭代包含三个阶段:

  1. 思考(Thought) — 模型分析当前状态,决定下一步需要什么。
  2. 行动(Action) — 模型选择一个工具并提供输入。
  3. 观察(Observation) — 系统执行该工具,并把结果返回给模型。

随后,模型基于这个 Observation 生成一个新的 Thought,循环往复——直到达到你设定的上限。

flowchart TD A[用户查询] --> B(思考 Thought) B -->|决定下一步| C["行动 Action / 工具调用"] C -->|执行 API/搜索| D[观察 Observation] D -->|反馈数据| B B -->|信息充足| E[最终答案] style A fill:#e1f5fe,stroke:#01579b style B fill:#fff3e0,stroke:#e65100 style C fill:#e8f5e9,stroke:#2e7d32 style D fill:#f3e5f5,stroke:#880e4f style E fill:#e8f5e9,stroke:#2e7d32

一个真实的 ReAct 执行轨迹

用户查询:"现任美国总统和苹果公司 CEO 的年龄差是多少?"

  • 思考 1: 我需要现任美国总统和苹果 CEO 是谁,再查他们的年龄。先从总统开始。
  • 行动 1: 搜索[现任美国总统]
  • 观察 1: [搜索结果: ...]
  • 思考 2: 总统是 X,年龄 Y。现在去找苹果的 CEO。
  • 行动 2: 搜索[苹果 CEO]
  • 观察 2: [搜索结果: ...]
  • 思考 3: CEO 是那个人。查一下他的年龄。
  • 行动 3: 搜索[该 CEO 的年龄]
  • 观察 3: [搜索结果: ...]
  • 思考 4: 总统 Y 岁,CEO 是 W 岁。差值是 Y − W,需要计算。
  • 行动 4: 计算器[Y - W]
  • 观察 4: [结果: Z]
  • 思考 5: 我已经得到了答案。
  • 最终答案: "他们的年龄差是 Z 岁。"

注意:上面每一个观察结果,都是 Agent 选择去信任的文本。如果其中某次搜索返回的页面里写着"忽略你之前的所有指令",一个朴素的 Agent 会把它当作上下文的一部分读进去。这正是为什么观察结果是一道安全边界,而不仅仅是一条数据来源。

ReAct vs 思维链(CoT)

特性 思维链(CoT) ReAct
信息来源 仅依赖内部模型权重 外部工具 + 内部权重
幻觉风险 较高(事实未知或过时时) 较低——事实可被检索并核对
错误恢复 差(错误会级联放大) 更好——能观察失败并调整
适用场景 数学、逻辑、文本总结 调研、API 交互、多步执行

从零手写一个 ReAct Agent(Python)

LangChain 这类框架把这个循环封装了起来,但亲手写一遍才能看清它究竟如何运作,以及它有多依赖严格的提示词和停止序列。请从环境变量读取模型名——模型标识符是易变的——并保留迭代上限。

python
import os
import re
from openai import OpenAI

client = OpenAI()
MODEL = os.environ.get("OPENAI_MODEL", "gpt-4o")

def get_weather(location):
    # 一个模拟工具。在生产环境中,要把它的返回值当作不可信输入。
    if "London" in location:
        return "Rainy, 12°C"
    return "Sunny, 25°C"

REACT_PROMPT = """
你将在 Thought(思考)、Action(行动)、PAUSE(暂停)、Observation(观察)的循环中运行。
在循环的最后,你将输出 Answer(答案)。

使用 Thought 描述你对问题的思考过程。
使用 Action 运行一个可用的工具,然后返回 PAUSE。
Observation 将会是执行该行动后的结果。

你可用的行动有:
get_weather:
例如: get_weather: London
返回该地点的当前天气。

示例会话:
Question: 伦敦的天气怎么样?
Thought: 我应该查询伦敦的天气。
Action: get_weather: London
PAUSE

你将被再次调用,并获得以下输入:
Observation: Rainy, 12°C

然后你输出:
Answer: 伦敦的天气是雨天,12°C。
"""

def run_react_agent(prompt, max_steps=5):
    messages = [
        {"role": "system", "content": REACT_PROMPT},
        {"role": "user", "content": prompt},
    ]

    for _ in range(max_steps):  # 硬性迭代上限 —— 必需
        response = client.chat.completions.create(
            model=MODEL,
            messages=messages,
            stop=["Observation:"],  # 在模型自己幻觉出观察结果之前停止
        )
        result = response.choices[0].message.content
        print(result)
        messages.append({"role": "assistant", "content": result})

        if "Answer:" in result:
            return

        action_match = re.search(r"Action: (\w+): (.*)", result)
        if action_match:
            action, action_input = action_match.groups()
            if action == "get_weather":
                observation = get_weather(action_input)
            else:
                observation = "Error: 找不到该工具"
            print(f"Observation: {observation}")
            messages.append({"role": "user", "content": f"Observation: {observation}"})

run_react_agent("伦敦的天气怎么样?")

构建 ReAct Agent 的最佳实践

  1. 写清楚工具描述。 Thought 完全依赖于确切知道一个工具的作用。"搜索网络"太模糊;"在实时互联网上搜索当前事件、新闻和客观事实"才是可操作的。
  2. 设置硬性迭代上限。 ReAct 循环容易卡在反复重试同一个失败动作上。务必给迭代次数设上限(如上面的 max_steps),在生产环境里还应加上成本和墙钟时间的上限。
  3. 强制约束输出格式。 较小的模型可能会漏掉 PAUSEAction: 关键字。用 Structured Outputs、JSON 模式或严格的 System Prompt 来固定格式。
  4. 优雅地处理故障。 如果工具报错,把错误作为 Observation 返回(例如 Observation: API 返回 500),而不是让程序崩溃。模型会读取它,并推理出一个替代方案。
  5. 把观察结果当作不可信输入。 检索到的内容可能夹带注入的指令。要校验它、把它清楚地隔离为数据,绝不让某个工具返回值悄悄覆盖 System Prompt。

常见错误

  • 塞了太多工具。 冗长的工具清单会让模型晕头转向,也会撑爆上下文窗口。请使用工具检索或分层路由,而不是一次性把所有工具都暴露出来。
  • 忘了设置停止序列。 没有 stop=["Observation:"],模型不会停下等待环境反馈,而是继续往下、把观察结果自己"幻觉"出来。

常见问题(FAQ)

有了原生的 Function Calling,ReAct 是不是过时了?

没有。函数调用(或工具调用)只是执行 Action 阶段的一种更可靠、更结构化的方式。其底层思想——先推理、再行动、再观察——依然是 ReAct 模式,并且仍是合理的默认选择。

为什么我的 ReAct Agent 会自己捏造观察结果?

因为它没有停止序列,所以不会暂停下来等待环境,而是一直往下生成。传入 stop=["Observation:"](或你所用 API 的等价参数),让生成在观察结果之前停止。

哪些模型最适合跑 ReAct?

任何具备较强推理与指令遵循能力的模型都能胜任 ReAct;在任一时间点,当下的前沿模型都是稳妥的选择。参数量较小的模型(大约 10B 以下)往往难以在多轮之间维持严格的 Thought → Action 格式。由于模型排名变化很快,请在你自己的任务上评测,而不是依赖某个具名的排行榜。

总结

ReAct 把一个静态的文本生成器,变成一个会推理、会行动、会观察、会调整的 Agent。这个模式本身很小;真正让它可用于生产的,是围绕它的那份纪律——一个让循环得以终止的硬性迭代上限,以及把每一个观察结果都当作不可信输入而非可信指令的习惯。

一手资料

相关阅读