一、概述
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);
}
逻辑说明:
- 检查是否有待处理的工具调用(没有对应结果的工具)
- 如果没有,正常添加消息到记忆并开始迭代
- 如果有,验证输入消息并添加工具结果,然后决定进入 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)));
}
推理阶段流程:
- 检查是否达到最大迭代次数,如果是则进入总结阶段
- 创建 ReasoningContext 用于累积推理内容
- 检查中断状态
- 通知 PreReasoningEvent hooks
- 从模型流式生成响应
- 处理每个 chunk 并通知 ReasoningChunkEvent hooks
- 构建最终消息
- 通知 PostReasoningEvent hooks
- 根据 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);
});
});
}
行动阶段流程:
- 提取待处理的工具调用
- 如果没有待处理工具,继续下一次迭代
- 设置内部 chunk 回调
- 通知 PreActingEvent hooks
- 执行工具调用
- 分离成功和待处理(suspended)的结果
- 处理成功结果:
- 通知 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 事件类型
- PreReasoningEvent:推理前
- PostReasoningEvent:推理后
- ReasoningChunkEvent:推理流式 chunk
- PreActingEvent:行动前
- PostActingEvent:行动后
- ActingChunkEvent:行动流式 chunk
- PreSummaryEvent:总结前
- PostSummaryEvent:总结后
- 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 状态持久化
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 是一个功能强大、设计优雅的智能体实现,具有以下特点:
- 清晰的架构:ReAct 循环分为推理和行动两个阶段,逻辑清晰
- 响应式编程:使用 Project Reactor 实现非阻塞流式处理
- 可扩展性:通过 Hook 系统支持各种扩展和拦截
- 灵活性:支持多种配置选项和高级特性
- 健壮性:完善的错误处理和验证机制
- 易用性:Builder 模式提供流畅的 API
这个实现为构建复杂的 AI 智能体提供了坚实的基础。