核心摘要

ReAct Agent 模式让模型交替提出决策、执行外部行动并读取观察结果,使下一步能够利用环境反馈。 ReAct 不是具体软件框架,可见 Thought 文本也不是真实推理证明,Tool 返回值更不会自动可信。生产实现必须具备结构化决策、服务端权限门禁、有界 Observation、显式停止规则、副作用恢复和轨迹级评测。

核心要点

  • ReAct 是模式,不是软件包。 原始研究使用包含推理轨迹、行动和环境观察的少样本任务轨迹。
  • Tool 反馈同时带来能力和风险。 Observation 可能提供当前证据、返回错误,也可能携带间接提示词注入。
  • 可见推理只是可选的运行数据。 可以记录行动原因或摘要,但不能把生成式解释作为授权或合规证据。
  • 控制权属于运行时。 Tool 权限、参数校验、超时、预算、审批、重试和终止都必须在模型外执行。
  • 评测必须覆盖完整轨迹。 只看最终答案会漏掉无效调用、越权尝试、循环、无依据结论和结果不明的写操作。

什么是 ReAct Agent 模式?

ReAct 是 Reasoning and Acting 的缩写,是一种将模型生成的推理轨迹、任务行动与环境观察交替组织的提示和控制模式。 Yao 等研究者提出该方法,目的是把过去常被分开研究的两种能力结合起来:围绕任务进行推理,以及与外部知识源或环境交互。

原论文在 HotpotQA、FEVER、ALFWorld 与 WebShop 上评测 ReAct。论文中的结果只适用于对应模型、Prompt、Tool、数据集和上下文示例,不能证明所有现代 Agent 都应公开思维链,也不能证明 ReAct 在所有工作流中占优。

需要区分三个层次:

text
ReAct
  = 推理与行动模式

ReAct Agent
  = 使用该模式的 Agent 实现

Agent 框架
  = 可能实现 ReAct,并额外提供状态、Tool、Trace、
    持久化、部署等能力的软件库

旧 URL 或类名中仍可能包含 framework,但长期稳定的概念是 ReAct 模式。LangChain ReAct Agent 等框架类只是实现,不是定义本身。

ReAct 循环如何工作?

ReAct 循环由模型提出行动,再由可信运行时代码执行,并在模型作出下一次决策前返回标准化 Observation。 经典论文把生成阶段标记为 ThoughtActionObservation;现代运行时可以隐藏内部推理,只暴露结构化决策与简短的运行摘要。

flowchart TD A["用户目标与可信策略"] --> B["构建有界上下文"] B --> C["模型决策"] C --> D{"决策类型"} D -- "完成" --> E["校验最终结果"] D -- "调用 Tool" --> F["权限与参数门禁"] D -- "升级处理" --> G["人工或权威服务"] F -- "拒绝" --> H["结构化拒绝 Observation"] F -- "允许" --> I["执行 Tool"] I --> J["标准化结果与来源"] H --> K["更新运行状态"] J --> K K --> L{"触发停止规则?"} L -- "否" --> B L -- "是" --> M["携带明确原因停止"] E --> M

每个边界的责任不同:

边界 模型可以提出 运行时必须执行
决策 回答、Tool 调用、升级处理 决策类型白名单与输出 Schema
Tool Tool 名称和参数 身份、授权、参数、超时、目标地址
Observation 对返回数据的解释 大小、类型、来源、脱敏、信任标签
重试 再尝试一次 可重试性、退避、幂等、次数预算
完成 最终回答 证据、格式、策略和任务成功校验
停止 完成或失败 最大步数、墙钟时间、费用、取消、重复失败

Trace 证明执行过程,不证明隐藏推理

ReAct Trace 能证明系统请求了什么行动、返回了什么 Observation,却不能证明生成的 Thought 忠实呈现模型内部计算。 思维链忠实度研究表明,模型可能使用未在解释中披露的信息,也可能生成与答案因果过程不一致的说明。

生产环境应优先记录有界事件契约:

json
{
  "step": 3,
  "decision": "tool_call",
  "decisionSummary": "回答前核对订单状态",
  "tool": "lookup_order",
  "argumentDigest": "sha256:...",
  "policyDecision": "allowed",
  "observation": {
    "status": "ok",
    "source": "orders-service",
    "bytes": 84
  },
  "remainingBudget": {
    "steps": 2,
    "wallTimeMs": 1200
  }
}

这类事件可以支持调试,但不能宣称 decisionSummary 完整揭示模型真实推理。敏感 Prompt、原始 Tool Payload、凭证和个人数据仍需脱敏与访问控制。

ReAct 与思维链、Function Calling、Agent Loop 的区别

ReAct 是 Agent Loop 内部的一种策略,不等同于思维链、Function Calling 或运行时本身。

概念 主要关注点 必须执行外部行动? 生产边界
思维链提示 生成中间推理文本 可见解释可能不忠实
Function Calling 结构化 Tool 名称和参数 Schema 不会自动授予权限
ReAct 模式 交替执行决策、行动与观察 通常需要 Observation 可能误导或注入指令
Agent Loop 重复执行模型与 Tool 步骤 可选 预算、状态、重试、取消、停止原因
确定性工作流 按代码定义的路径执行 可选 灵活性较低,但更易测试和治理

原生 Function Calling 改善了 Action 接口:模型可以输出类型化 Tool 请求,不再需要用正则解析文本。它不会让外围循环自动安全。应用仍需判断当前主体能否对特定对象调用该 Tool,以及数据能否发送到目标地址。

哪些场景适合 ReAct?

当下一步确实依赖执行后才能获得的 Observation 时,ReAct 才有价值。 典型场景包括探索式调研、诊断故障、理解陌生代码库,或连续查询多个来源直到证据充分。

以下情况优先使用确定性工作流:

  • 步骤可以预先确定;
  • 每个状态转移都有严格业务规则;
  • 写操作必须经过固定审批链;
  • 任务只是有界的数据转换;
  • 普通搜索或单次模型调用已经达到质量门槛。

ReAct 用额外模型调用、延迟、费用和故障路径换取适应性控制。是否值得必须与更简单的基线实测,不能因为任务是“多步骤”就默认引入 Agent。

可运行的结构化 ReAct 控制器

下面的控制器展示 ReAct 最持久的部分:Policy 提出结构化决策,可信代码负责校验和执行 Tool。 示例只使用 Python 标准库和只读模拟 Tool,无需 API Key 即可运行。生产接入时,可以把 example_policy 替换为返回相同 Decision Schema 的模型适配器。

python
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Dict, List, Optional


class DecisionKind(str, Enum):
    TOOL = "tool"
    FINISH = "finish"
    ESCALATE = "escalate"


@dataclass(frozen=True)
class Decision:
    kind: DecisionKind
    tool: Optional[str] = None
    arguments: Dict[str, Any] = field(default_factory=dict)
    answer: Optional[str] = None


@dataclass(frozen=True)
class Observation:
    tool: str
    ok: bool
    data: Dict[str, Any]
    source: str


@dataclass
class RunState:
    goal: str
    observations: List[Observation] = field(default_factory=list)


Policy = Callable[[RunState], Decision]
Tool = Callable[[Dict[str, Any]], Observation]


def lookup_order(arguments: Dict[str, Any]) -> Observation:
    order_id = arguments.get("order_id")
    if not isinstance(order_id, str) or not order_id.startswith("ord_"):
        return Observation(
            tool="lookup_order",
            ok=False,
            data={"error": "invalid_order_id"},
            source="orders-service",
        )
    return Observation(
        tool="lookup_order",
        ok=True,
        data={"order_id": order_id, "status": "shipped"},
        source="orders-service",
    )


TOOLS: Dict[str, Tool] = {"lookup_order": lookup_order}


def run_react(policy: Policy, goal: str, max_steps: int = 4) -> str:
    state = RunState(goal=goal)

    for _ in range(max_steps):
        decision = policy(state)

        if decision.kind is DecisionKind.FINISH:
            if not decision.answer:
                raise ValueError("finish decision requires an answer")
            return decision.answer

        if decision.kind is DecisionKind.ESCALATE:
            raise RuntimeError("run requires human review")

        if decision.kind is not DecisionKind.TOOL:
            raise ValueError("unsupported decision kind")
        if decision.tool not in TOOLS:
            raise PermissionError("tool is not allowlisted")

        observation = TOOLS[decision.tool](decision.arguments)
        state.observations.append(observation)

    raise TimeoutError("maximum ReAct steps reached")


def example_policy(state: RunState) -> Decision:
    if not state.observations:
        return Decision(
            kind=DecisionKind.TOOL,
            tool="lookup_order",
            arguments={"order_id": "ord_123"},
        )

    latest = state.observations[-1]
    if not latest.ok:
        return Decision(kind=DecisionKind.ESCALATE)

    return Decision(
        kind=DecisionKind.FINISH,
        answer=(
            f"Order {latest.data['order_id']} is "
            f"{latest.data['status']}."
        ),
    )


print(run_react(example_policy, "查询订单 ord_123"))
# Order ord_123 is shipped.

示例刻意把 Policy 与 Runtime 分开。模型可以请求 lookup_order,却不能添加新 Tool、绕过白名单、伪造 Observation 或延长 max_steps

扩展到生产环境的顺序

按以下顺序补充控制:

  1. **结构化模型适配器:**按版本化 Schema 校验 Decision JSON。
  2. **绑定主体的策略门禁:**同时授权 Tool、对象、参数和目标地址。
  3. **Observation 信封:**保留来源、时间戳、Schema 版本、大小与信任标签。
  4. **运行预算:**强制限制步数、Token、墙钟时间、Tool 调用和费用。
  5. **取消机制:**在下一次行动前撤销 Worker Lease。
  6. **最终校验器:**根据证据和任务验收条件检查结果。
  7. **持久化:**将流程 Checkpoint 与外部业务副作用分开处理。

不要在缺少实测故障的情况下直接增加长期记忆或多 Agent。更多上下文和更多执行主体会扩大攻击面,也会让轨迹归因更困难。

Tool 返回值是不可信 Observation 通道

每个 ReAct Observation 都必须被视为来自特定来源的数据,不能成为新指令或新权限。 网页、邮件、代码仓库文件、MCP 结果和 OCR 文本都可能包含间接提示词注入。即使是可信服务,也可能返回过时、畸形、超大或跨租户数据。

把 Observation 送回模型前应执行:

  • 在 Tool 内落实认证和对象级授权;
  • 限制字节数、嵌套深度、重定向、行数与 MIME 类型;
  • 将来源和租户身份与 Payload 一起保留;
  • 将 Tool 数据与可信运行时指令分离;
  • 脱敏凭证和不必要的个人数据;
  • 限制出站目标,禁止 Observation 自行授予能力;
  • 测试多语言、编码、延迟触发和跨 Tool 注入链。

Prompt 分隔符和分类器可以提供风险信号,却不能形成授权边界。更完整的威胁模型见提示词注入防御指南

重试与外部副作用

外部写操作结果不明时,ReAct Agent 不能盲目重试。 支付、邮件、部署、删除或创建工单发生超时,并不代表副作用没有提交。

有副作用的 Tool 必须具备:

  • 由应用生成的幂等键;
  • 包含 plannedconfirmedambiguous 和终态的持久化 Effect Record;
  • 重试前向权威系统对账;
  • 绑定具体对象、参数和目标地址的审批;
  • 已确认副作用可逆时的补偿操作;
  • 防止过期 Worker 继续行动的 Fencing 或 Lease。

模型可以建议重试,真正决定重试是否合法的是权威服务。对话或 Agent Loop 的 Checkpoint 不会让外部系统自动获得 exactly-once 语义。

常见故障与控制措施

故障模式 发生原因 控制措施
重复行动 Observation 没有改变 Policy 重复调用检测器与明确停止原因
幻觉 Tool 模型编造名称或 Schema 白名单与严格 Decision 校验
参数错误 生成过程中丢失任务约束 类型化 Schema、对象授权、澄清
Observation 注入 返回内容包含指令 来源、数据与指令分离、最小权限
证据漂移 最终回答超出 Observation Claim-to-source 校验与拒答
成本失控 循环持续且没有可测进展 步数、时间、Token、Tool 与费用预算
重复副作用 把超时误判为失败 幂等键、Effect Record、对账
错误审计信心 生成的 Thought 看似合理 记录可执行事件,不推断模型心理状态

重试策略必须按操作分类。有界只读查询可能允许重试;结果不明的写操作必须进入对账流程,不能再次交给模型自由尝试。

如何评测 ReAct Agent?

应在相同任务、Tool、模型、预算和基础设施上,把 ReAct 与直接回答、确定性工作流进行对照。 原论文的基准结果证明了该模式的研究价值,不代表普遍的生产优势。

至少测量:

text
task_success
unsafe_success
supported_answer_rate
tool_selection_accuracy
invalid_argument_rate
unnecessary_tool_rate
mean_and_p95_steps
repeated_action_rate
recovery_after_tool_error
ambiguous_effect_rate
latency_and_cost_per_success
human_escalation_precision

除了 LLM Judge,还要使用轨迹断言:

  • 禁止的 Tool 从未实际执行;
  • Tool 参数与获授权对象一致;
  • 最终回答中的重要主张都能映射到 Observation;
  • Observation 没有改变运行时权限;
  • 每次运行都带已知原因停止;
  • 故障注入能触发对账、升级或有界重试;
  • 取消后没有继续产生副作用。

故障切片应覆盖过时数据、空结果、畸形 Payload、权限拒绝、提示词注入、提交前超时、提交后超时、重复结果和 Observation 冲突。

常见问题

有了原生 Tool Calling,ReAct 还有价值吗?

有,但实现方式已经变化。原生 Tool Calling 用结构化请求替代 Action: search[...] 这类文本。如果模型读取执行结果后再决定下一步,仍是 ReAct 式自适应循环。完整运行时模型可参阅 Agent Loop 指南

UI 应该展示模型的 Thought 吗?

通常不应把 Thought 当作忠实推理展示。更适合呈现的是简短行动摘要、来源、Tool 状态、审批和可核验结果。生成式解释在受控调试中可能有帮助,但可能泄露敏感上下文,也未必解释决策的真实原因。

ReAct 能减少幻觉吗?

当 Agent 检索到相关、正确的证据并正确使用时,ReAct 可以减少无依据猜测;但它也可能放大错误证据、过时结果、数据投毒或提示词注入。应测量有依据回答率和检索故障,而不是把 Tool 访问视为事实保证。

ReAct 一定优于 Plan-and-execute 吗?

不一定。ReAct 会在每次 Observation 后调整策略,但会产生串行延迟和重复模型调用。子任务已知、可并行且容易校验时,Plan-and-execute 可能更合适;状态转移和审批必须固定时,应使用确定性工作流。

ReAct 循环应该允许多少步?

不存在通用数字。应根据任务状态机、延迟和成本预算、Tool 风险及回放中的步数分布确定。每次运行仍必须具备硬上限、超时、取消路径和重复行动检测器。

总结

当任务下一步依赖新的环境反馈时,ReAct 仍是一种有价值的模式。它最持久的贡献是把决策、行动与观察交替组织起来,而不是要求公开思维链或采用某个指定框架。

生产级 ReAct Agent 因此应是受控的 Agent Loop:模型负责提出,运行时负责授权,Tool 在有界能力内执行,Observation 保留来源,校验器决定是否继续或完成。

一手资料

相关阅读