1369 字
约 4 分钟
17
ReAct推理与行动
无标签

ReAct:让模型学会边想边做

系列:ToyAgent 智能体知识库 03/26

一句话核心:Thought → Action → Observation → 循环,直到 Final Answer——几乎所有智能体框架的内核。

为什么需要

基础对话模型有两个致命短板:

  1. 幻觉:一本正经地编造实时信息。问它天气,它会自信地给一个貌似合理的假数据。
  2. 一次成型:复杂问题需要拆步骤求解,模型却总想一口气答完,中间环节全靠脑补,错一步全盘皆错。

ReAct(Reasoning + Acting)的解法是强制模型一次只走一小步:先说想法,再说行动,然后停下等人执行,拿到观察结果再继续想。把"大问题"切成"小步 + 真实反馈",两个短板同时被治住。

核心机制

协议约定:让模型输出结构化 JSON

靠系统提示词约定输出格式,每轮二选一:

private static final String SYSTEM_PROMPT = """
        你是一个使用 ReAct 模式解决问题的智能体。
        每一轮只输出一个 JSON 对象,格式二选一:
        1. 继续行动:{"thought": "...", "action": "工具名", "action_input": "参数"}
        2. 给出答案:{"thought": "...", "final": "最终答案"}
        可用工具:
        - get_weather(city): 查询城市天气
        - calculator(expression): 计算四则运算
        """;

循环体:20 行写完 ReAct

for (int step = 1; step <= MAX_STEPS; step++) {
    String raw = model.chat(messages);              // 1. 模型思考
    Object parsed = Json.parse(raw);

    String fin = Json.str(parsed, "final");
    if (fin != null) return fin;                     // 2. 有答案 → 结束

    String action = Json.str(parsed, "action");      // 3. 有行动 → 执行工具
    String observation = executeTool(action,
            Json.str(parsed, "action_input"));

    messages.add(Message.assistant(raw));            // 4. 结果喂回模型
    messages.add(Message.user("Observation: " + observation));
}
return "已达最大步数,循环终止";                       // 5. 保险丝

灵魂所在:第 4 步

把工具结果作为新消息追加进对话再发给模型。模型没有"感知世界"的能力,它的一切认知都来自上下文——上下文里有什么,它就"活"在什么世界里。忘记回填 Observation,模型就会基于想象继续推理,幻觉立刻回来。

stateDiagram-v2
    [*] --> Thought
    Thought --> Action : 输出工具名与参数
    Action --> Observation : 系统执行工具
    Observation --> Thought : 回填进上下文
    Thought --> FinalAnswer : 输出 final
    FinalAnswer --> [*]

图示:ReAct 的思考-行动-观察状态循环,每轮二选一——继续行动或给出最终答案,Step1 与 Step2 就是在这个环上各走一圈。

举例:一个完整循环的对话轨迹

用户:北京今天适合穿短袖吗?

--- Step 1 ---
Thought: 需要先查北京实时气温才能判断。
Action:  get_weather  |  action_input: {"city": "北京"}
系统执行工具 → "晴,气温 29°C"
Observation 追加进消息列表。

--- Step 2 ---
Thought: 29°C 高于 26°C,适合短袖。
Final:   北京今天晴,29°C,适合穿短袖,注意防晒。

注意这个轨迹里模型没有一步是靠猜的:气温来自工具返回的真实数据,结论建立在 Observation 之上。如果工具返回"城市未收录",模型下一步会看到这个失败并尝试换参数或如实告知——这就是"失败可读"让模型能自我纠错。

反例:若第 4 步忘记回填 Observation,Step 2 时模型的上下文里没有任何真实气温,它只能编一个"大概 25 度左右吧"——幻觉复现。

为什么必须设 MAX_STEPS

如果模型反复要求调用同一个工具,或工具一直返回错误,循环就会无限烧钱。生产系统里这个保险丝叫 Budget Guard(步数 / Token / 时间三重预算),是 Agent Runtime 的标配。教学版用最简单的步数上限表达这个思想。

graph TD
    S1["Step 1 思考"] --> S1a["Action 查天气"]
    S1a --> S1b["Observation 晴 29 度"]
    S1b --> S2["Step 2 思考"]
    S2 --> S2a["Final 结论 适合短袖"]
    S2a --> D["结束"]
    S1b --> X["步数超过 MAX_STEPS"]
    X --> F["保险丝终止 返回兜底提示"]

图示:一个真实的两步循环轨迹,以及 MAX_STEPS 保险丝在循环失控时的兜底出口。

面试考点

  • "ReAct 和直接 Function Calling 有什么区别?"——ReAct 是一种推理-行动交替的模式,可以用提示词协议实现;Function Calling 是模型厂商提供的结构化能力(原生 tool_calls 字段)。生产框架的 ReAct 通常跑在原生 Function Calling 上,而不是让模型手写 JSON。
  • "如何防止 ReAct 循环失控?"——三重预算(步数/Token/时间)+ 重复动作检测(连续相同 action 提前熔断)+ 工具失败可读化。

生产化路径

  • 别手写 JSON 协议,用原生 Function Calling(见 Step04 / LangChain4j 的 @Tool):教学版提示词协议帮你理解语义,生产版用结构化字段避免模型输出格式漂移导致的解析失败。
  • 把 Budget Guard 做进 Runtime 层而非业务代码:步数、Token、超时三项预算统一配置、统一告警(参考本项目 learn 模块的输出 Token 上限配置实践)。
  • Observation 写入要完整:工具失败信息、错误码、耗时都应进入上下文,模型纠错能力直接取决于它看到的反馈质量。
ReAct推理与行动
https://clxhxhhr.top/posts/4703/
作者
clxstart
发布于
2026-10-13
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。