AgentScope-Java ReActAgent 代码实现讲解

一、概述

ReActAgent 是 AgentScope-Java 中实现 ReAct (Reasoning and Acting) 模式的核心智能体类。它结合了推理(思考和规划)与行动(工具执行)的迭代循环,直到完成任务或达到最大迭代次数。

核心特性

  • 响应式流式处理:使用 Project Reactor 实现非阻塞执行
  • Hook 系统:可扩展的钩子系统用于监控和拦截智能体执行
  • 人机协作 (HITL) :支持通过 stopAgent() 在 PostReasoningEvent/PostActingEvent 中中断
  • 结构化输出:继承自 StructuredOutputCapableAgent 提供类型安全的输出生成

二、核心架构

2.1 类继承关系

java 复制代码
public class ReActAgent extends StructuredOutputCapableAgent

ReActAgent 继承自 StructuredOutputCapableAgent,获得了结构化输出能力。

2.2 核心依赖组件

java 复制代码
// 核心依赖
private final Memory memory;                    // 短期记忆
private final String sysPrompt;                  // 系统提示
private final Model model;                      // 语言模型
private final int maxIters;                     // 最大迭代次数
private final ExecutionConfig modelExecutionConfig;    // 模型执行配置
private final ExecutionConfig toolExecutionConfig;     // 工具执行配置
private final GenerateOptions generateOptions;  // 生成选项
private final PlanNotebook planNotebook;        // 计划笔记本
private final ToolExecutionContext toolExecutionContext; // 工具执行上下文
private final StatePersistence statePersistence; // 状态持久化配置

三、执行流程

3.1 主入口:doCall 方法

doCall 是智能体执行的主入口:

java 复制代码
@Override
protected Mono<Msg> doCall(List<Msg> msgs) {
    Set<String> pendingIds = getPendingToolUseIds();

    // 没有待处理的工具 -> 正常处理
    if (pendingIds.isEmpty()) {
        addToMemory(msgs);
        return executeIteration(0);
    }

    // 有待处理的工具 -> 验证并添加工具结果
    validateAndAddToolResults(msgs, pendingIds);
    return hasPendingToolUse() ? acting(0) : executeIteration(0);
}

逻辑说明

  1. 检查是否有待处理的工具调用(没有对应结果的工具)
  2. 如果没有,正常添加消息到记忆并开始迭代
  3. 如果有,验证输入消息并添加工具结果,然后决定进入 acting 阶段还是继续迭代

3.2 ReAct 循环

ReAct 循环由两个主要阶段组成:

3.2.1 Reasoning(推理)阶段

reasoning 方法实现推理阶段:

java 复制代码
private Mono<Msg> reasoning(int iter, boolean ignoreMaxIters) {
    // 检查最大迭代次数(除非 ignoreMaxIters 为 true)
    if (!ignoreMaxIters && iter >= maxIters) {
        return summarizing();
    }

    ReasoningContext context = new ReasoningContext(getName());

    return checkInterruptedAsync()
            .then(notifyPreReasoningEvent(prepareMessages()))
            .flatMapMany(event -> {
                GenerateOptions options = event.getEffectiveGenerateOptions() != null
                        ? event.getEffectiveGenerateOptions()
                        : buildGenerateOptions();
                return model.stream(event.getInputMessages(), toolkit.getToolSchemas(), options)
                        .concatMap(chunk -> checkInterruptedAsync().thenReturn(chunk));
            })
            .doOnNext(chunk -> {
                List<Msg> chunkMsgs = context.processChunk(chunk);
                for (Msg msg : chunkMsgs) {
                    notifyReasoningChunk(msg, context).subscribe();
                }
            })
            .then(Mono.defer(() -> Mono.justOrEmpty(context.buildFinalMessage())))
            .onErrorResume(InterruptedException.class, error -> {
                Msg msg = context.buildFinalMessage();
                if (msg != null) {
                    memory.addMessage(msg);
                }
                return Mono.error(error);
            })
            .flatMap(this::notifyPostReasoning)
            .flatMap(event -> {
                Msg msg = event.getReasoningMessage();
                if (msg != null) {
                    memory.addMessage(msg);
                }

                // HITL 停止
                if (event.isStopRequested()) {
                    return Mono.just(msg.withGenerateReason(GenerateReason.REASONING_STOP_REQUESTED));
                }

                // gotoReasoning 请求(例如由 StructuredOutputHook)
                if (event.isGotoReasoningRequested()) {
                    List<Msg> gotoMsgs = event.getGotoReasoningMsgs();
                    if (gotoMsgs != null) {
                        gotoMsgs.forEach(memory::addMessage);
                    }
                    return reasoning(iter + 1, true);
                }

                // 检查完成条件
                if (isFinished(msg)) {
                    return Mono.just(msg);
                }

                // 继续到 acting 阶段
                return checkInterruptedAsync().then(acting(iter));
            })
            .switchIfEmpty(Mono.defer(() -> Mono.justOrEmpty((Msg) null)));
}

推理阶段流程

  1. 检查是否达到最大迭代次数,如果是则进入总结阶段
  2. 创建 ReasoningContext 用于累积推理内容
  3. 检查中断状态
  4. 通知 PreReasoningEvent hooks
  5. 从模型流式生成响应
  6. 处理每个 chunk 并通知 ReasoningChunkEvent hooks
  7. 构建最终消息
  8. 通知 PostReasoningEvent hooks
  9. 根据 hook 的决定:
    • 如果请求停止,返回停止消息
    • 如果请求 gotoReasoning,继续推理(忽略 maxIters)
    • 如果完成(没有工具调用),返回消息
    • 否则进入 acting 阶段

3.2.2 Acting(行动)阶段

acting 方法实现行动阶段:

java 复制代码
private Mono<Msg> acting(int iter) {
    // 只提取待处理的工具调用(记忆中没有结果的)
    List<ToolUseBlock> pendingToolCalls = extractPendingToolCalls();

    if (pendingToolCalls.isEmpty()) {
        // 没有待处理的工具,继续下一次迭代
        return executeIteration(iter + 1);
    }

    // 设置内部 chunk 回调,将工具 chunk 转发到 ActingChunkEvent hooks
    toolkit.setInternalChunkCallback(
            (toolUse, chunk) -> notifyActingChunk(toolUse, chunk).subscribe());

    // 只执行待处理的工具
    return notifyPreActingHooks(pendingToolCalls)
            .flatMap(this::executeToolCalls)
            .flatMap(results -> {
                // 分离成功和待处理的结果
                List<Map.Entry<ToolUseBlock, ToolResultBlock>> successPairs =
                        results.stream()
                                .filter(e -> !e.getValue().isSuspended())
                                .toList();
                List<Map.Entry<ToolUseBlock, ToolResultBlock>> pendingPairs =
                        results.stream()
                                .filter(e -> e.getValue().isSuspended())
                                .toList();

                // 如果没有成功结果需要处理
                if (successPairs.isEmpty()) {
                    if (!pendingPairs.isEmpty()) {
                        return Mono.just(buildSuspendedMsg(pendingPairs));
                    }
                    return executeIteration(iter + 1);
                }

                // 通过 hooks 处理成功结果并添加到记忆
                return Flux.fromIterable(successPairs)
                        .concatMap(this::notifyPostActingHook)
                        .last()
                        .flatMap(event -> {
                            // HITL 停止
                            if (event.isStopRequested()) {
                                return Mono.just(event.getToolResultMsg()
                                        .withGenerateReason(GenerateReason.ACTING_STOP_REQUESTED));
                            }

                            // 如果有待处理结果,构建 suspended Msg
                            if (!pendingPairs.isEmpty()) {
                                return Mono.just(buildSuspendedMsg(pendingPairs));
                            }

                            // 继续下一次迭代
                            return executeIteration(iter + 1);
                        });
            });
}

行动阶段流程

  1. 提取待处理的工具调用
  2. 如果没有待处理工具,继续下一次迭代
  3. 设置内部 chunk 回调
  4. 通知 PreActingEvent hooks
  5. 执行工具调用
  6. 分离成功和待处理(suspended)的结果
  7. 处理成功结果:
    • 通知 PostActingEvent hooks
    • 添加到记忆
    • 根据 hook 决定停止、返回 suspended 消息或继续迭代

3.3 总结阶段

summarizing 方法在达到最大迭代次数时调用:

java 复制代码
protected Mono<Msg> summarizing() {
    log.debug("Maximum iterations reached. Generating summary...");

    List<Msg> messageList = prepareSummaryMessages();
    GenerateOptions generateOptions = buildGenerateOptions();

    return notifyPreSummaryHook(messageList, generateOptions)
            .flatMap(preSummaryEvent -> {
                List<Msg> effectiveMessages = preSummaryEvent.getInputMessages();
                GenerateOptions effectiveOptions = preSummaryEvent.getEffectiveGenerateOptions();

                return streamAndAccumulateSummary(effectiveMessages, effectiveOptions)
                        .flatMap(msg -> notifyPostSummaryHook(msg, effectiveOptions)
                                .map(postEvent -> {
                                    Msg finalMsg = postEvent.getSummaryMessage()
                                            .withGenerateReason(GenerateReason.MAX_ITERATIONS);
                                    memory.addMessage(finalMsg);
                                    return finalMsg;
                                }));
            })
            .onErrorResume(this::handleSummaryError);
}

四、关键方法解析

4.1 工具调用处理

extractPendingToolCalls

extractPendingToolCalls 提取待处理的工具调用:

java 复制代码
private List<ToolUseBlock> extractPendingToolCalls() {
    List<ToolUseBlock> allToolCalls = extractRecentToolCalls();
    if (allToolCalls.isEmpty()) {
        return List.of();
    }

    Set<String> pendingIds = getPendingToolUseIds();
    return allToolCalls.stream()
            .filter(toolUse -> pendingIds.contains(toolUse.getId()))
            .toList();
}

这个方法确保只执行那些还没有结果的工具调用,避免重复执行。

validateAndAddToolResults

validateAndAddToolResults 验证并添加工具结果:

java 复制代码
private void validateAndAddToolResults(List<Msg> msgs, Set<String> pendingIds) {
    if (msgs == null || msgs.isEmpty()) {
        return;
    }

    List<ToolResultBlock> results = msgs.stream()
            .flatMap(m -> m.getContentBlocks(ToolResultBlock.class).stream())
            .toList();

    if (results.isEmpty()) {
        throw new IllegalStateException(
                "Cannot add messages without tool results when pending tool calls exist. "
                + "Pending IDs: " + pendingIds);
    }

    // 检查重复 ID
    Set<String> providedIds = new HashSet<>();
    for (ToolResultBlock r : results) {
        if (!providedIds.add(r.getId())) {
            throw new IllegalStateException("Duplicate tool result ID: " + r.getId());
        }
    }

    // 检查所有提供的 ID 都匹配待处理的 ID
    Set<String> invalidIds = providedIds.stream()
            .filter(id -> !pendingIds.contains(id))
            .collect(Collectors.toSet());
    if (!invalidIds.isEmpty()) {
        throw new IllegalStateException(
                "Invalid tool result IDs: " + invalidIds + ". Expected: " + pendingIds);
    }

    // 检查非 ToolResultBlock 内容
    boolean hasTextContent = msgs.stream()
            .flatMap(m -> m.getContent().stream())
            .anyMatch(block -> !(block instanceof ToolResultBlock));

    // 如果只提供了部分结果,不允许文本内容
    boolean isPartialResults = !providedIds.containsAll(pendingIds);
    if (isPartialResults && hasTextContent) {
        throw new IllegalStateException(
                "Cannot include text content when providing partial tool results. "
                + "Provided: " + providedIds + ", Pending: " + pendingIds);
    }

    msgs.forEach(memory::addMessage);
}

验证规则

  • 空输入:无操作
  • 没有工具结果:抛出错误
  • 有工具结果:验证 ID 匹配待处理 ID,无重复
  • 部分结果 + 文本内容:抛出错误(只有在所有工具完成时才允许文本内容)

4.2 完成条件判断

isFinished 判断是否应该终止 ReAct 循环:

java 复制代码
private boolean isFinished(Msg msg) {
    if (msg == null) {
        return true;
    }

    List<ToolUseBlock> toolCalls = msg.getContentBlocks(ToolUseBlock.class);

    // 没有工具调用 - 完成
    // 如果有工具调用(即使是不存在的),继续到 acting 阶段
    // ToolExecutor 会为模型返回"工具未找到"错误
    return toolCalls.isEmpty();
}

4.3 消息准备

prepareMessages 准备模型输入消息:

java 复制代码
private List<Msg> prepareMessages() {
    List<Msg> messages = new ArrayList<>();
    if (sysPrompt != null && !sysPrompt.trim().isEmpty()) {
        messages.add(Msg.builder()
                .name("system")
                .role(MsgRole.SYSTEM)
                .content(TextBlock.builder().text(sysPrompt).build())
                .build());
    }
    messages.addAll(memory.getMessages());
    return messages;
}

五、Hook 系统

ReActAgent 提供了丰富的 Hook 事件用于监控和拦截执行流程:

5.1 Hook 事件类型

  1. PreReasoningEvent:推理前
  2. PostReasoningEvent:推理后
  3. ReasoningChunkEvent:推理流式 chunk
  4. PreActingEvent:行动前
  5. PostActingEvent:行动后
  6. ActingChunkEvent:行动流式 chunk
  7. PreSummaryEvent:总结前
  8. PostSummaryEvent:总结后
  9. SummaryChunkEvent:总结流式 chunk

5.2 Hook 通知方法

notifyHooks 是通用的 hook 通知方法:

java 复制代码
private <T extends HookEvent> Mono<T> notifyHooks(T event) {
    Mono<T> result = Mono.just(event);
    for (Hook hook : getSortedHooks()) {
        result = result.flatMap(hook::onEvent);
    }
    return result;
}

Hooks 按优先级顺序执行(优先级值越小越先执行)。

六、Builder 模式

ReActAgent 使用 Builder 模式构建,提供流畅的 API:

6.1 基本配置

java 复制代码
ReActAgent agent = ReActAgent.builder()
    .name("Assistant")
    .description("A helpful assistant")
    .sysPrompt("You are a helpful assistant.")
    .model(model)
    .toolkit(toolkit)
    .memory(new InMemoryMemory())
    .maxIters(10)
    .build();

6.2 高级配置

执行配置

java 复制代码
.modelExecutionConfig(ExecutionConfig.builder()
    .timeout(Duration.ofSeconds(30))
    .maxRetries(3)
    .build())
.toolExecutionConfig(ExecutionConfig.builder()
    .timeout(Duration.ofSeconds(60))
    .maxRetries(2)
    .build())

生成选项

java 复制代码
.generateOptions(GenerateOptions.builder()
    .temperature(0.7)
    .topP(0.9)
    .maxTokens(1000)
    .build())

Hooks

java 复制代码
.hook(new MyCustomHook())
.hooks(List.of(hook1, hook2, hook3))

长期记忆

java 复制代码
.longTermMemory(longTermMemory)
.longTermMemoryMode(LongTermMemoryMode.BOTH)

RAG

java 复制代码
.knowledge(knowledgeBase)
.ragMode(RAGMode.GENERIC)
.retrieveConfig(RetrieveConfig.builder()
    .limit(5)
    .scoreThreshold(0.5)
    .build())

计划功能

java 复制代码
.planNotebook(PlanNotebook.builder().build())
// 或使用便捷方法
.enablePlan()

技能箱

java 复制代码
.skillBox(skillBox)

状态持久化

java 复制代码
.statePersistence(StatePersistence.builder()
    .memoryManaged(true)
    .toolkitManaged(true)
    .planNotebookManaged(false)
    .build())

七、高级特性

7.1 工具挂起 (Tool Suspension)

当工具抛出 ToolSuspendException 时:

  • 异常被 Toolkit 捕获并转换为待处理的 ToolResultBlock
  • 成功结果存储在记忆中,待处理结果不存储
  • 返回包含 GenerateReason.TOOL_SUSPENDED 的消息

7.2 结构化输出重试

通过 StructuredOutputHook 实现:

  • 当结构化输出验证失败时,请求 gotoReasoning
  • 添加错误消息到记忆
  • 继续推理(忽略 maxIters)

7.3 人机协作 (HITL)

在 PostReasoningEvent 或 PostActingEvent 中调用 stopAgent()

  • 立即停止执行
  • 返回当前消息和停止原因

7.4 状态持久化

saveToloadFrom 方法:

java 复制代码
@Override
public void saveTo(Session session, SessionKey sessionKey) {
    // 保存智能体元数据
    session.save(sessionKey, "agent_meta", 
        new AgentMetaState(getAgentId(), getName(), getDescription(), sysPrompt));

    // 保存记忆(如果管理)
    if (statePersistence.memoryManaged()) {
        memory.saveTo(session, sessionKey);
    }

    // 保存 toolkit activeGroups(如果管理)
    if (statePersistence.toolkitManaged() && toolkit != null) {
        session.save(sessionKey, "toolkit_activeGroups", 
            new ToolkitState(toolkit.getActiveGroups()));
    }

    // 保存 PlanNotebook(如果管理)
    if (statePersistence.planNotebookManaged() && planNotebook != null) {
        planNotebook.saveTo(session, sessionKey);
    }
}

八、使用示例

8.1 基本使用

java 复制代码
// 创建模型
DashScopeChatModel model = DashScopeChatModel.builder()
    .apiKey(System.getenv("DASHSCOPE_API_KEY"))
    .modelName("glm-4.7")
    .build();

// 创建工具包
Toolkit toolkit = new Toolkit();
toolkit.registerObject(new MyToolClass());

// 构建智能体
ReActAgent agent = ReActAgent.builder()
    .name("Assistant")
    .sysPrompt("You are a helpful assistant.")
    .model(model)
    .toolkit(toolkit)
    .memory(new InMemoryMemory())
    .maxIters(10)
    .build();

// 使用智能体
Msg response = agent.call(Msg.builder()
    .name("user")
    .role(MsgRole.USER)
    .content(TextBlock.builder().text("What's the weather?").build())
    .build()).block();

8.2 带 Hooks 的使用

java 复制代码
ReActAgent agent = ReActAgent.builder()
    .name("Assistant")
    .model(model)
    .toolkit(toolkit)
    .hook(new LoggingHook())
    .hook(new MetricsHook())
    .build();

8.3 带计划功能

java 复制代码
ReActAgent agent = ReActAgent.builder()
    .name("Assistant")
    .model(model)
    .toolkit(toolkit)
    .enablePlan()
    .build();

8.4 带长期记忆

java 复制代码
ReActAgent agent = ReActAgent.builder()
    .name("Assistant")
    .model(model)
    .toolkit(toolkit)
    .longTermMemory(longTermMemory)
    .longTermMemoryMode(LongTermMemoryMode.BOTH)
    .build();

8.5 带 RAG

java 复制代码
ReActAgent agent = ReActAgent.builder()
    .name("Assistant")
    .model(model)
    .toolkit(toolkit)
    .knowledge(knowledgeBase)
    .ragMode(RAGMode.GENERIC)
    .build();

九、总结

ReActAgent 是一个功能强大、设计优雅的智能体实现,具有以下特点:

  1. 清晰的架构:ReAct 循环分为推理和行动两个阶段,逻辑清晰
  2. 响应式编程:使用 Project Reactor 实现非阻塞流式处理
  3. 可扩展性:通过 Hook 系统支持各种扩展和拦截
  4. 灵活性:支持多种配置选项和高级特性
  5. 健壮性:完善的错误处理和验证机制
  6. 易用性:Builder 模式提供流畅的 API

这个实现为构建复杂的 AI 智能体提供了坚实的基础。

相关推荐
KubeSphere2 小时前
为什么改了配置,Pod 却没重启?Kubernetes 真相来了
后端
风吹心凉2 小时前
AI Agent、MCP、Prompt、Function Calling
人工智能·prompt
银月光科技2 小时前
细分市场带动下 UV LED行业发展潜力巨大
人工智能·物联网·uv
龙侠九重天2 小时前
使用 OpenClaw 自动化日常任务的 10 种实用方法
人工智能·ai编程·openclaw
aircrushin2 小时前
Harness Engineering:Anthropic实现让 AI 6小时无人干预生成完整项目
人工智能
码云数智-园园2 小时前
自助建站哪个好?自助建站平台深度对比
人工智能
gelald2 小时前
JVM - 垃圾回收
java·jvm·后端
章鱼丸-2 小时前
DAY40 训练与测试规范写法
人工智能·算法·机器学习