AgentScope

AgentScope Java中ReactAgent的实现原理

ReAct 的论文核心思想就一句话:让 LLM 交替"思考(Reasoning)"和"行动(Acting)",直到得出最终答案。落到工程里要解决五个问题: 1. 怎么把 LLM 决定的工具调用真正执行掉(reasoning → ac…

TL;DR

ReAct 的论文核心思想就一句话:让 LLM 交替"思考(Reasoning)"和"行动(Acting)",直到得出最终答案。落到工程里要解决五个问题: 1. 怎么把 LLM 决定的工具调用真正执行掉(reasoning → ac…

ReAct 的论文核心思想就一句话:让 LLM 交替"思考(Reasoning)"和"行动(Acting)",直到得出最终答案。落到工程里要解决五个问题: 1. 怎么把 LLM 决定的工具调用真正执行掉(reasoning → acting) 2. 怎么把工具结果再喂回 LLM(acting → reasoning) 3. 什么时候停(max_iters / 没有 tool_use / HITL stop / 完成) 4. 流式输出怎么保证既能展示给用户、又能在结束时拼成完整 Msg 5. 任意一步暂停或崩溃后,下次能从中间状态恢复 ReActAgent 的所有复杂度都是在回答这五个问题。

Builder装配

要了解ReActAgent的实现原理,需要从他的build方法讲起:

public


}

ReActAgent 自己不持有工具——所有工具都注册在 Toolkit 上,Agent 只是引用。Toolkit 深拷贝是关键。同一个 Toolkit 注册到多个 Agent 时,每个 Agent 拿到的是独立副本——这样 RAG/SkillBox 等子系统给 Agent A 注册的工具不会污染 Agent B。这是后面 RAG/SkillBox 能"自动注入工具"的安全前提。 置顺序也有意为之: - LongTermMemory 优先,因为 STATIC_CONTROL 模式会装 StaticLongTermMemoryHook,要早于其他 hook 拿到 PreReasoningEvent; - RAG 在 GENERIC 模式下也是装 GenericRAGHook,同样进 hooks 列表;AGENTIC 模式则注册 retrieve_knowledge 工具进 toolkit; - SkillBox 注册 SkillHook 同时把 skills 当工具暴露; - 它们都通过 同一个入口(toolkit + hook list)把能力插到 ReAct 主循环里——意味着所有"扩展能力"对主循环是透明的。 PendingToolRecoveryHook 在最早期注册(priority=10),所以 PreCallEvent 时它第一个跑,能在用户 hook 之前补救悬空的 ToolUse。

call()

AgentBase.call(List) 是 final 的:

public


        resource


}

这条链定义了 所有 Agent 的统一行为契约。ReActAgent.doCall 拿到的是 PreCall hook 可能修改后的 input messages(hook 可以改写、可以注入新消息),返回的 Msg 又会被 PostCall hook 包一层(可以全局改写最终回答)。 核心逻辑在ReActAgent.doCall方法中实现。

doCall()

doCall进来之后,有四个分支:

@Override
protected


}
  • 分支 ① 是最常见路径:memory 干净,把用户消息入 memory,从 iteration 0 开始 reasoning。
  • 分支 ② 是 HITL 恢复路径:用户上次暂停,这次空 call 进来 → 直接进 acting 把那批 pending ToolUse 跑掉。
  • 分支 ③ 是用户主动注入 ToolResult 路径:典型场景是 ToolSuspendException 抛出后用户填表回填、或者用户想"伪造一个工具失败回复"让模型重新规划。validateAndAddToolResults 严格校验:ID 必须匹配 pending、不能重复、部分回填时不能夹带文本。
  • 分支 ④ 兜底:进入这里说明 PendingToolRecoveryHook 被关掉而用户也没自己处理——直接报错而非"假装没看见"。

reasoning()

剥掉 reactor 噪音,逻辑大致是:

private


            model
                         toolkit
                         options


                event


}

acting()

private


    toolkit


.


}

迭代终止与 summarizing()

ReAct 循环靠以下条件之一终止: - isFinished(msg) 为 true(reasoning 没产生任何 ToolUseBlock)→ 直接返回 reasoning msg; - event.isStopRequested()(PostReasoning 或 PostActing 的 stopAgent)→ 返回携带 _STOP_REQUESTED 的 msg; - 工具 throw ToolSuspendException → buildSuspendedMsg 返回 TOOL_SUSPENDED; - iter >= maxIters → 进入 summarizing(); - 中断 → 走 onErrorResume + handleInterrupt。 summarizing() 是强制收尾机制:

protected Mono<Msg> summarizing() {
    List<Msg> messageList = prepareSummaryMessages();       // memory + 一条强制 USER 提示
    return notifyPreSummaryHook(messageList, opts)
        .flatMap(e -> streamAndAccumulateSummary(e.getInputMessages(), e.getEffectiveGenerateOptions())
            .flatMap(msg -> notifyPostSummaryHook(msg, opts)
                .map(post -> {
                    Msg finalMsg = post.getSummaryMessage()
                            .withGenerateReason(GenerateReason.MAX_ITERATIONS);
                    memory.addMessage(finalMsg);
                    return finalMsg;
                })))
        .onErrorResume(this::handleSummaryError);
}
版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。