From ac124adc6112c1e070691496f3a4363b2387aa65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=8B=E9=A3=8E?= Date: Tue, 13 Jan 2026 14:01:16 +0800 Subject: [PATCH 1/7] =?UTF-8?q?refactor(TriggerExecutor):=20=E5=A4=8D?= =?UTF-8?q?=E7=94=A8GraalCodeExecutor=E5=B9=B6=E7=A7=BB=E9=99=A4=E5=86=97?= =?UTF-8?q?=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此次重构将`TriggerExecutor`中的代码执行部分改用`GraalCodeExecutor`处理,并移除了原有的`CodeExecutionEnvironment`等相关类。这样不仅减少了代码冗余,还提升了整体的一致性和安全性。 --- .../trigger/backend/ExecutionBackend.java | 11 + .../SpringSchedulerExecutionBackend.java | 79 ++++- .../config/TriggerAutoConfiguration.java | 48 ++- .../trigger/config/TriggerProperties.java | 28 +- .../executor/TriggerExecutionCallback.java | 41 +++ .../trigger/executor/TriggerExecutor.java | 326 ++++++++++++++++++ .../trigger/model/SessionSnapshot.java | 243 +++++++++++++ .../trigger/model/TriggerDefinition.java | 55 +++ .../trigger/model/TriggerExecutionResult.java | 172 +++++++++ .../InMemorySessionSnapshotRepository.java | 109 ++++++ .../repository/SessionSnapshotRepository.java | 83 +++++ 11 files changed, 1169 insertions(+), 26 deletions(-) create mode 100644 assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/executor/TriggerExecutionCallback.java create mode 100644 assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/executor/TriggerExecutor.java create mode 100644 assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/SessionSnapshot.java create mode 100644 assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/TriggerExecutionResult.java create mode 100644 assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/repository/InMemorySessionSnapshotRepository.java create mode 100644 assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/repository/SessionSnapshotRepository.java diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/backend/ExecutionBackend.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/backend/ExecutionBackend.java index 215bd23..f07c8dd 100644 --- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/backend/ExecutionBackend.java +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/backend/ExecutionBackend.java @@ -16,6 +16,7 @@ package com.alibaba.assistant.agent.extension.trigger.backend; +import com.alibaba.assistant.agent.extension.trigger.executor.TriggerExecutionCallback; import com.alibaba.assistant.agent.extension.trigger.model.TriggerDefinition; /** @@ -57,5 +58,15 @@ default void refresh(String backendTaskId, TriggerDefinition newDefinition) { */ boolean isRunning(String backendTaskId); + /** + * 设置执行回调 + * 用于在调度触发时执行实际的触发器逻辑 + * + * @param callback 执行回调 + */ + default void setExecutionCallback(TriggerExecutionCallback callback) { + // 默认空实现,子类可覆盖 + } + } diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/backend/SpringSchedulerExecutionBackend.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/backend/SpringSchedulerExecutionBackend.java index 87808ef..7b681b6 100644 --- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/backend/SpringSchedulerExecutionBackend.java +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/backend/SpringSchedulerExecutionBackend.java @@ -16,10 +16,12 @@ package com.alibaba.assistant.agent.extension.trigger.backend; +import com.alibaba.assistant.agent.extension.trigger.executor.TriggerExecutionCallback; import com.alibaba.assistant.agent.extension.trigger.model.ExecutionStatus; import com.alibaba.assistant.agent.extension.trigger.model.ScheduleMode; import com.alibaba.assistant.agent.extension.trigger.model.TriggerDefinition; import com.alibaba.assistant.agent.extension.trigger.model.TriggerExecutionRecord; +import com.alibaba.assistant.agent.extension.trigger.model.TriggerExecutionResult; import com.alibaba.assistant.agent.extension.trigger.repository.TriggerExecutionLogRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,15 +53,26 @@ public class SpringSchedulerExecutionBackend implements ExecutionBackend { private final Map taskDefinitions = new ConcurrentHashMap<>(); + /** + * 执行回调 - 用于实际执行触发器逻辑 + */ + private TriggerExecutionCallback executionCallback; + public SpringSchedulerExecutionBackend(TaskScheduler taskScheduler, TriggerExecutionLogRepository executionLogRepository) { this.taskScheduler = taskScheduler; this.executionLogRepository = executionLogRepository; } + @Override + public void setExecutionCallback(TriggerExecutionCallback callback) { + this.executionCallback = callback; + log.info("SpringSchedulerExecutionBackend setExecutionCallback 设置执行回调"); + } + @Override public String schedule(TriggerDefinition definition) { - log.info("SpringSchedulerExecutionBackend.schedule 注册调度任务: triggerId={}, scheduleMode={}", + log.info("SpringSchedulerExecutionBackend schedule 注册调度任务, triggerId={}, scheduleMode={}", definition.getTriggerId(), definition.getScheduleMode()); String backendTaskId = generateTaskId(definition); @@ -70,21 +83,21 @@ public String schedule(TriggerDefinition definition) { ScheduledFuture scheduledFuture = scheduleByMode(definition, task); scheduledTasks.put(backendTaskId, scheduledFuture); - log.info("SpringSchedulerExecutionBackend.schedule 调度任务注册成功: backendTaskId={}", backendTaskId); + log.info("SpringSchedulerExecutionBackend schedule 调度任务注册成功, backendTaskId={}", backendTaskId); return backendTaskId; } @Override public void cancel(String backendTaskId) { - log.info("SpringSchedulerExecutionBackend.cancel 取消调度任务: backendTaskId={}", backendTaskId); + log.info("SpringSchedulerExecutionBackend cancel 取消调度任务, backendTaskId={}", backendTaskId); ScheduledFuture future = scheduledTasks.remove(backendTaskId); if (future != null) { future.cancel(false); - log.info("SpringSchedulerExecutionBackend.cancel 任务已取消: backendTaskId={}", backendTaskId); + log.info("SpringSchedulerExecutionBackend cancel 任务已取消, backendTaskId={}", backendTaskId); } else { - log.warn("SpringSchedulerExecutionBackend.cancel 任务不存在: backendTaskId={}", backendTaskId); + log.warn("SpringSchedulerExecutionBackend cancel 任务不存在, backendTaskId={}", backendTaskId); } taskDefinitions.remove(backendTaskId); @@ -102,22 +115,22 @@ private ScheduledFuture scheduleByMode(TriggerDefinition definition, Runnable switch (mode) { case CRON: - log.debug("SpringSchedulerExecutionBackend.scheduleByMode CRON调度: expression={}", scheduleValue); + log.debug("SpringSchedulerExecutionBackend scheduleByMode CRON调度, expression={}", scheduleValue); return taskScheduler.schedule(task, new CronTrigger(scheduleValue)); case FIXED_DELAY: long fixedDelay = parseDuration(scheduleValue); - log.debug("SpringSchedulerExecutionBackend.scheduleByMode FIXED_DELAY调度: delay={}ms", fixedDelay); + log.debug("SpringSchedulerExecutionBackend scheduleByMode FIXED_DELAY调度, delay={}ms", fixedDelay); return taskScheduler.scheduleWithFixedDelay(task, Duration.ofMillis(fixedDelay)); case FIXED_RATE: long fixedRate = parseDuration(scheduleValue); - log.debug("SpringSchedulerExecutionBackend.scheduleByMode FIXED_RATE调度: rate={}ms", fixedRate); + log.debug("SpringSchedulerExecutionBackend scheduleByMode FIXED_RATE调度, rate={}ms", fixedRate); return taskScheduler.scheduleAtFixedRate(task, Duration.ofMillis(fixedRate)); case ONE_TIME: Instant executeTime = parseInstant(scheduleValue); - log.debug("SpringSchedulerExecutionBackend.scheduleByMode ONE_TIME调度: time={}", executeTime); + log.debug("SpringSchedulerExecutionBackend scheduleByMode ONE_TIME调度, time={}", executeTime); return taskScheduler.schedule(task, executeTime); default: @@ -127,7 +140,7 @@ private ScheduledFuture scheduleByMode(TriggerDefinition definition, Runnable private void executeTask(String backendTaskId, TriggerDefinition definition) { String executionId = UUID.randomUUID().toString(); - log.info("SpringSchedulerExecutionBackend.executeTask 开始执行任务: executionId={}, triggerId={}", + log.info("SpringSchedulerExecutionBackend executeTask 开始执行任务, executionId={}, triggerId={}", executionId, definition.getTriggerId()); // 创建执行记录 @@ -139,18 +152,46 @@ private void executeTask(String backendTaskId, TriggerDefinition definition) { executionLogRepository.save(record); try { - // TODO: 实际执行业务逻辑 - // 这里需要集成CodeAct的执行能力 - log.info("SpringSchedulerExecutionBackend.executeTask 执行函数: function={}, params={}", - definition.getExecuteFunction(), definition.getParameters()); - - // 模拟执行成功 - executionLogRepository.updateStatus(executionId, ExecutionStatus.SUCCESS, null, null); - log.info("SpringSchedulerExecutionBackend.executeTask 任务执行成功: executionId={}", executionId); + // 使用执行回调执行触发器 + if (executionCallback != null) { + log.info("SpringSchedulerExecutionBackend executeTask 使用回调执行触发器, triggerId={}", + definition.getTriggerId()); + + TriggerExecutionResult result = executionCallback.execute(definition); + + // 根据执行结果更新记录 + if (result.getExecutionSuccess() != null && result.getExecutionSuccess()) { + // 将执行结果包装为Map + Map outputSummary = null; + if (result.getExecutionResult() != null) { + outputSummary = new java.util.HashMap<>(); + outputSummary.put("result", result.getExecutionResult()); + } + executionLogRepository.updateStatus(executionId, ExecutionStatus.SUCCESS, + null, outputSummary); + log.info("SpringSchedulerExecutionBackend executeTask 任务执行成功, executionId={}", executionId); + } + else { + executionLogRepository.updateStatus(executionId, ExecutionStatus.FAILED, + result.getErrorMessage(), null); + log.warn("SpringSchedulerExecutionBackend executeTask 任务执行失败, executionId={}, error={}", + executionId, result.getErrorMessage()); + } + } + else { + // 没有设置回调,使用兼容模式 + log.warn("SpringSchedulerExecutionBackend executeTask 未设置执行回调, 使用兼容模式, triggerId={}", + definition.getTriggerId()); + log.info("SpringSchedulerExecutionBackend executeTask 执行函数, function={}, params={}", + definition.getExecuteFunction(), definition.getParameters()); + + // 兼容模式:标记为成功但实际未执行 + executionLogRepository.updateStatus(executionId, ExecutionStatus.SUCCESS, null, null); + } } catch (Exception e) { - log.error("SpringSchedulerExecutionBackend.executeTask 任务执行失败: executionId={}", executionId, e); + log.error("SpringSchedulerExecutionBackend executeTask 任务执行异常, executionId={}", executionId, e); executionLogRepository.updateStatus(executionId, ExecutionStatus.FAILED, e.getMessage(), null); } } diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/config/TriggerAutoConfiguration.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/config/TriggerAutoConfiguration.java index 66eb67e..adeea4d 100644 --- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/config/TriggerAutoConfiguration.java +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/config/TriggerAutoConfiguration.java @@ -19,9 +19,12 @@ import com.alibaba.assistant.agent.common.tools.TriggerCodeactTool; import com.alibaba.assistant.agent.extension.trigger.backend.ExecutionBackend; import com.alibaba.assistant.agent.extension.trigger.backend.SpringSchedulerExecutionBackend; +import com.alibaba.assistant.agent.extension.trigger.executor.TriggerExecutor; import com.alibaba.assistant.agent.extension.trigger.manager.TriggerManager; +import com.alibaba.assistant.agent.extension.trigger.repository.InMemorySessionSnapshotRepository; import com.alibaba.assistant.agent.extension.trigger.repository.InMemoryTriggerExecutionLogRepository; import com.alibaba.assistant.agent.extension.trigger.repository.InMemoryTriggerRepository; +import com.alibaba.assistant.agent.extension.trigger.repository.SessionSnapshotRepository; import com.alibaba.assistant.agent.extension.trigger.repository.TriggerExecutionLogRepository; import com.alibaba.assistant.agent.extension.trigger.repository.TriggerRepository; import com.alibaba.assistant.agent.extension.trigger.tools.TriggerCodeactToolFactory; @@ -54,18 +57,29 @@ public class TriggerAutoConfiguration { @Bean @ConditionalOnMissingBean public TriggerRepository triggerRepository() { + log.info("TriggerAutoConfiguration triggerRepository 创建默认InMemoryTriggerRepository"); return new InMemoryTriggerRepository(); } @Bean @ConditionalOnMissingBean public TriggerExecutionLogRepository triggerExecutionLogRepository() { + log.info("TriggerAutoConfiguration triggerExecutionLogRepository 创建默认InMemoryTriggerExecutionLogRepository"); return new InMemoryTriggerExecutionLogRepository(); } + @Bean + @ConditionalOnMissingBean + public SessionSnapshotRepository sessionSnapshotRepository() { + log.info("TriggerAutoConfiguration sessionSnapshotRepository 创建默认InMemorySessionSnapshotRepository"); + return new InMemorySessionSnapshotRepository(); + } + @Bean(name = "triggerTaskScheduler") @ConditionalOnMissingBean(name = "triggerTaskScheduler") public TaskScheduler triggerTaskScheduler(TriggerProperties properties) { + log.info("TriggerAutoConfiguration triggerTaskScheduler 创建调度器, poolSize={}", + properties.getScheduler().getPoolSize()); ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(properties.getScheduler().getPoolSize()); scheduler.setThreadNamePrefix("trigger-scheduler-"); @@ -75,23 +89,45 @@ public TaskScheduler triggerTaskScheduler(TriggerProperties properties) { return scheduler; } + @Bean + @ConditionalOnMissingBean + public TriggerExecutor triggerExecutor(SessionSnapshotRepository snapshotRepository, + TriggerProperties properties) { + TriggerProperties.ExecutionConfig config = properties.getExecution(); + log.info("TriggerAutoConfiguration triggerExecutor 创建触发器执行器, timeout={}ms, allowIO={}, allowNativeAccess={}", + config.getExecutionTimeout(), config.isAllowIO(), config.isAllowNativeAccess()); + return new TriggerExecutor( + snapshotRepository, + config.isAllowIO(), + config.isAllowNativeAccess(), + config.getExecutionTimeout() + ); + } + @Bean @ConditionalOnMissingBean public ExecutionBackend executionBackend(TaskScheduler triggerTaskScheduler, - TriggerExecutionLogRepository executionLogRepository) { - return new SpringSchedulerExecutionBackend(triggerTaskScheduler, executionLogRepository); + TriggerExecutionLogRepository executionLogRepository, + TriggerExecutor triggerExecutor) { + log.info("TriggerAutoConfiguration executionBackend 创建执行后端并设置回调"); + SpringSchedulerExecutionBackend backend = new SpringSchedulerExecutionBackend( + triggerTaskScheduler, executionLogRepository); + // 设置执行回调 + backend.setExecutionCallback(triggerExecutor); + return backend; } @Bean @ConditionalOnMissingBean public TriggerManager triggerManager(TriggerRepository triggerRepository, TriggerExecutionLogRepository executionLogRepository, ExecutionBackend executionBackend) { + log.info("TriggerAutoConfiguration triggerManager 创建触发器管理器"); return new TriggerManager(triggerRepository, executionLogRepository, executionBackend); } @Bean public TriggerCodeactToolFactory triggerCodeactToolFactory(TriggerManager triggerManager) { - log.info("TriggerAutoConfiguration#triggerCodeactToolFactory - reason=创建TriggerCodeactTool工厂"); + log.info("TriggerAutoConfiguration triggerCodeactToolFactory 创建TriggerCodeactTool工厂"); return new TriggerCodeactToolFactory(triggerManager); } @@ -100,16 +136,16 @@ public TriggerCodeactToolFactory triggerCodeactToolFactory(TriggerManager trigge */ @Bean public List triggerCodeactTools(TriggerCodeactToolFactory factory) { - log.info("TriggerAutoConfiguration#triggerCodeactTools - reason=开始创建Trigger工具"); + log.info("TriggerAutoConfiguration triggerCodeactTools 开始创建Trigger工具"); List tools = factory.createTools(); - log.info("TriggerAutoConfiguration#triggerCodeactTools - reason=Trigger工具创建完成, count={}", tools.size()); + log.info("TriggerAutoConfiguration triggerCodeactTools Trigger工具创建完成, count={}", tools.size()); // 打印每个工具的详情 for (int i = 0; i < tools.size(); i++) { TriggerCodeactTool tool = tools.get(i); - log.info("TriggerAutoConfiguration#triggerCodeactTools - reason=创建Trigger工具, index={}, name={}, description={}", + log.info("TriggerAutoConfiguration triggerCodeactTools 创建Trigger工具, index={}, name={}, description={}", i + 1, tool.getToolDefinition().name(), tool.getToolDefinition().description()); } diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/config/TriggerProperties.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/config/TriggerProperties.java index 8df803c..cab8ff8 100644 --- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/config/TriggerProperties.java +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/config/TriggerProperties.java @@ -111,7 +111,17 @@ public static class ExecutionConfig { /** * 执行超时时间(毫秒),0表示不限制 */ - private long executionTimeout = 0; + private long executionTimeout = 30000; + + /** + * 是否允许IO操作 + */ + private boolean allowIO = false; + + /** + * 是否允许本地访问 + */ + private boolean allowNativeAccess = false; public int getDefaultMaxRetries() { return defaultMaxRetries; @@ -137,6 +147,22 @@ public void setExecutionTimeout(long executionTimeout) { this.executionTimeout = executionTimeout; } + public boolean isAllowIO() { + return allowIO; + } + + public void setAllowIO(boolean allowIO) { + this.allowIO = allowIO; + } + + public boolean isAllowNativeAccess() { + return allowNativeAccess; + } + + public void setAllowNativeAccess(boolean allowNativeAccess) { + this.allowNativeAccess = allowNativeAccess; + } + } } diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/executor/TriggerExecutionCallback.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/executor/TriggerExecutionCallback.java new file mode 100644 index 0000000..3f03162 --- /dev/null +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/executor/TriggerExecutionCallback.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.assistant.agent.extension.trigger.executor; + +import com.alibaba.assistant.agent.extension.trigger.model.TriggerDefinition; +import com.alibaba.assistant.agent.extension.trigger.model.TriggerExecutionResult; + +/** + * 触发器执行回调接口 + * 用于解耦调度后端和执行逻辑 + * + * @author canfeng + * @since 1.0.0 + */ +@FunctionalInterface +public interface TriggerExecutionCallback { + + /** + * 执行触发器 + * + * @param definition 触发器定义 + * @return 执行结果 + */ + TriggerExecutionResult execute(TriggerDefinition definition); + +} + diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/executor/TriggerExecutor.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/executor/TriggerExecutor.java new file mode 100644 index 0000000..76a8316 --- /dev/null +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/executor/TriggerExecutor.java @@ -0,0 +1,326 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.assistant.agent.extension.trigger.executor; + +import com.alibaba.assistant.agent.common.enums.Language; +import com.alibaba.assistant.agent.common.tools.CodeactTool; +import com.alibaba.assistant.agent.core.context.CodeContext; +import com.alibaba.assistant.agent.core.executor.GraalCodeExecutor; +import com.alibaba.assistant.agent.core.executor.RuntimeEnvironmentManager; +import com.alibaba.assistant.agent.core.executor.python.PythonEnvironmentManager; +import com.alibaba.assistant.agent.core.model.ExecutionRecord; +import com.alibaba.assistant.agent.core.model.GeneratedCode; +import com.alibaba.assistant.agent.core.tool.CodeactToolRegistry; +import com.alibaba.assistant.agent.core.tool.DefaultCodeactToolRegistry; +import com.alibaba.assistant.agent.extension.trigger.model.SessionSnapshot; +import com.alibaba.assistant.agent.extension.trigger.model.TriggerDefinition; +import com.alibaba.assistant.agent.extension.trigger.model.TriggerExecutionResult; +import com.alibaba.assistant.agent.extension.trigger.repository.SessionSnapshotRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 触发器执行器 + * + * 核心设计:复用AssistantAgent的GraalCodeExecutor实现代码执行 + * + * 职责: + * 1. 恢复会话快照上下文 + * 2. 基于GraalCodeExecutor执行条件函数和动作函数 + * 3. 执行放弃条件函数 + * + * 应用层可以: + * 1. 直接使用默认实现 + * 2. 继承并扩展 getAvailableTools 方法提供自定义工具 + * + * @author canfeng + * @since 1.0.0 + */ +public class TriggerExecutor implements TriggerExecutionCallback { + + private static final Logger log = LoggerFactory.getLogger(TriggerExecutor.class); + + private final SessionSnapshotRepository snapshotRepository; + + private final RuntimeEnvironmentManager environmentManager; + + private final boolean allowIO; + + private final boolean allowNativeAccess; + + private final long executionTimeoutMs; + + public TriggerExecutor(SessionSnapshotRepository snapshotRepository) { + this(snapshotRepository, false, false, 30000); + } + + public TriggerExecutor(SessionSnapshotRepository snapshotRepository, + boolean allowIO, boolean allowNativeAccess, long executionTimeoutMs) { + this.snapshotRepository = snapshotRepository; + this.environmentManager = new PythonEnvironmentManager(); + this.allowIO = allowIO; + this.allowNativeAccess = allowNativeAccess; + this.executionTimeoutMs = executionTimeoutMs; + } + + /** + * 执行触发器 + * + * @param definition 触发器定义 + * @return 执行结果 + */ + @Override + public TriggerExecutionResult execute(TriggerDefinition definition) { + String triggerId = definition.getTriggerId(); + log.info("TriggerExecutor execute 开始执行触发器, triggerId={}", triggerId); + + long startTime = System.currentTimeMillis(); + TriggerExecutionResult result = new TriggerExecutionResult(); + result.setTriggerId(triggerId); + + try { + // 1. 恢复会话快照 + SessionSnapshot snapshot = restoreSnapshot(definition); + log.debug("TriggerExecutor execute 恢复会话快照成功, snapshotId={}", + definition.getSessionSnapshotId()); + + // 2. 构建CodeContext和CodeactToolRegistry + CodeContext codeContext = buildCodeContext(snapshot, definition); + CodeactToolRegistry toolRegistry = buildToolRegistry(snapshot); + + // 3. 创建GraalCodeExecutor(复用框架能力) + GraalCodeExecutor executor = new GraalCodeExecutor( + environmentManager, + codeContext, + Collections.emptyList(), // ToolCallback列表 + null, // OverAllState + toolRegistry, + allowIO, + allowNativeAccess, + executionTimeoutMs + ); + + // 4. 执行条件函数(如果有) + if (hasConditionFunction(definition)) { + boolean conditionResult = executeConditionFunction(executor, definition); + result.setConditionPassed(conditionResult); + + if (!conditionResult) { + log.info("TriggerExecutor execute 条件未满足, triggerId={}", triggerId); + result.setExecutionSuccess(true); + result.setExecutionTime(System.currentTimeMillis() - startTime); + return result; + } + } + else { + // 没有条件函数,默认条件满足 + result.setConditionPassed(true); + } + + // 5. 执行动作函数 + Object actionResult = executeActionFunction(executor, definition); + result.setExecutionResult(actionResult); + result.setExecutionSuccess(true); + + // 6. 执行放弃条件函数(如果有) + if (hasAbandonFunction(definition)) { + boolean shouldAbandon = executeAbandonFunction(executor, definition); + result.setShouldAbandon(shouldAbandon); + log.info("TriggerExecutor execute 放弃条件判断, triggerId={}, shouldAbandon={}", + triggerId, shouldAbandon); + } + + log.info("TriggerExecutor execute 触发器执行成功, triggerId={}", triggerId); + + } + catch (Exception e) { + log.error("TriggerExecutor execute 触发器执行失败, triggerId={}", triggerId, e); + result.setExecutionSuccess(false); + result.setErrorMessage(e.getMessage()); + } + finally { + result.setExecutionTime(System.currentTimeMillis() - startTime); + } + + return result; + } + + /** + * 恢复会话快照 + */ + protected SessionSnapshot restoreSnapshot(TriggerDefinition definition) { + String snapshotId = definition.getSessionSnapshotId(); + + // 如果有快照ID,从存储中恢复 + if (snapshotId != null && !snapshotId.isEmpty()) { + return snapshotRepository.findById(snapshotId) + .orElseThrow(() -> new IllegalStateException("会话快照不存在: " + snapshotId)); + } + + // 如果没有快照ID,使用definition中的函数代码快照创建临时快照 + SessionSnapshot snapshot = new SessionSnapshot(); + snapshot.setFunctionCode(definition.getFunctionCodeSnapshot()); + return snapshot; + } + + /** + * 构建CodeContext + * 从SessionSnapshot恢复函数代码到CodeContext + */ + protected CodeContext buildCodeContext(SessionSnapshot snapshot, TriggerDefinition definition) { + CodeContext codeContext = new CodeContext(Language.PYTHON); + + // 从快照恢复函数代码 + Map functionCode = snapshot.getFunctionCode(); + if (functionCode == null || functionCode.isEmpty()) { + // 使用definition中的函数代码快照 + functionCode = definition.getFunctionCodeSnapshot(); + } + + if (functionCode != null) { + for (Map.Entry entry : functionCode.entrySet()) { + String functionName = entry.getKey(); + String code = entry.getValue(); + + GeneratedCode generatedCode = new GeneratedCode( + functionName, + Language.PYTHON, + code, + "Restored from trigger snapshot" + ); + codeContext.registerFunction(generatedCode); + + log.debug("TriggerExecutor buildCodeContext 注册函数: functionName={}", functionName); + } + } + + return codeContext; + } + + /** + * 构建CodeactToolRegistry + * 子类可以覆盖此方法提供自定义工具 + */ + protected CodeactToolRegistry buildToolRegistry(SessionSnapshot snapshot) { + DefaultCodeactToolRegistry registry = new DefaultCodeactToolRegistry(); + + // 获取可用工具并注册 + List tools = getAvailableTools(snapshot); + for (CodeactTool tool : tools) { + registry.register(tool); + log.debug("TriggerExecutor buildToolRegistry 注册工具: toolName={}", + tool.getToolDefinition().name()); + } + + return registry; + } + + /** + * 获取可用的CodeactTool列表 + * + * 子类可以覆盖此方法提供自定义工具 + * + * @param snapshot 会话快照 + * @return 可用工具列表 + */ + protected List getAvailableTools(SessionSnapshot snapshot) { + // 默认返回空列表,子类可覆盖提供具体工具 + return Collections.emptyList(); + } + + /** + * 检查是否有条件函数 + */ + private boolean hasConditionFunction(TriggerDefinition definition) { + return definition.getConditionFunction() != null && !definition.getConditionFunction().isEmpty(); + } + + /** + * 检查是否有放弃条件函数 + */ + private boolean hasAbandonFunction(TriggerDefinition definition) { + return definition.getAbandonFunction() != null && !definition.getAbandonFunction().isEmpty(); + } + + /** + * 执行条件函数 + */ + private boolean executeConditionFunction(GraalCodeExecutor executor, TriggerDefinition definition) { + String functionName = definition.getConditionFunction(); + log.debug("TriggerExecutor executeConditionFunction 执行条件函数, functionName={}", functionName); + + ExecutionRecord record = executor.execute(functionName, new HashMap<>()); + + if (!record.isSuccess()) { + log.warn("TriggerExecutor executeConditionFunction 条件函数执行失败, functionName={}, error={}", + functionName, record.getErrorMessage()); + return false; + } + + Object result = record.getResult(); + // 结果是字符串形式的 "True" 或 "False" + return "True".equalsIgnoreCase(String.valueOf(result)) || Boolean.TRUE.equals(result); + } + + /** + * 执行动作函数 + */ + private Object executeActionFunction(GraalCodeExecutor executor, TriggerDefinition definition) { + String functionName = definition.getExecuteFunction(); + log.debug("TriggerExecutor executeActionFunction 执行动作函数, functionName={}", functionName); + + // 获取执行参数 + Map args = definition.getParameters(); + if (args == null) { + args = new HashMap<>(); + } + + ExecutionRecord record = executor.execute(functionName, args); + + if (!record.isSuccess()) { + throw new RuntimeException("动作函数执行失败: " + record.getErrorMessage()); + } + + return record.getResult(); + } + + /** + * 执行放弃条件函数 + */ + private boolean executeAbandonFunction(GraalCodeExecutor executor, TriggerDefinition definition) { + String functionName = definition.getAbandonFunction(); + log.debug("TriggerExecutor executeAbandonFunction 执行放弃条件函数, functionName={}", functionName); + + ExecutionRecord record = executor.execute(functionName, new HashMap<>()); + + if (!record.isSuccess()) { + log.warn("TriggerExecutor executeAbandonFunction 放弃条件函数执行失败, functionName={}, error={}", + functionName, record.getErrorMessage()); + return false; + } + + Object result = record.getResult(); + return "True".equalsIgnoreCase(String.valueOf(result)) || Boolean.TRUE.equals(result); + } + +} + diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/SessionSnapshot.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/SessionSnapshot.java new file mode 100644 index 0000000..9023637 --- /dev/null +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/SessionSnapshot.java @@ -0,0 +1,243 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.assistant.agent.extension.trigger.model; + +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +/** + * 会话快照 + * 用于在触发器执行时恢复上下文,实现脱离Agent体系的独立执行 + * + * @author canfeng + * @since 1.0.0 + */ +public class SessionSnapshot { + + /** + * 快照唯一标识 + */ + private String snapshotId; + + /** + * 原会话ID + */ + private String sessionId; + + /** + * 用户ID + */ + private String userId; + + /** + * 租户ID + */ + private String tenantId; + + /** + * 渠道类型(如USER、DINGTALK_GROUP等) + */ + private String channelType; + + /** + * 渠道ID + */ + private String channelId; + + /** + * 函数代码快照 + * key: 函数名, value: 函数代码 + */ + private Map functionCode; + + /** + * 上下文变量快照 + * 用于恢复执行时的变量环境 + */ + private Map contextVariables; + + /** + * 创建时间 + */ + private Instant createdAt; + + /** + * 过期时间 + */ + private Instant expireAt; + + /** + * 扩展元数据 + */ + private Map metadata; + + public SessionSnapshot() { + this.functionCode = new HashMap<>(); + this.contextVariables = new HashMap<>(); + this.metadata = new HashMap<>(); + this.createdAt = Instant.now(); + } + + // Static factory methods + + /** + * 创建会话快照 + */ + public static SessionSnapshot create(String sessionId, String userId, String tenantId) { + SessionSnapshot snapshot = new SessionSnapshot(); + snapshot.setSnapshotId(generateSnapshotId()); + snapshot.setSessionId(sessionId); + snapshot.setUserId(userId); + snapshot.setTenantId(tenantId); + return snapshot; + } + + private static String generateSnapshotId() { + return "snapshot_" + java.util.UUID.randomUUID().toString().replace("-", ""); + } + + // Getters and Setters + + public String getSnapshotId() { + return snapshotId; + } + + public void setSnapshotId(String snapshotId) { + this.snapshotId = snapshotId; + } + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getTenantId() { + return tenantId; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getChannelType() { + return channelType; + } + + public void setChannelType(String channelType) { + this.channelType = channelType; + } + + public String getChannelId() { + return channelId; + } + + public void setChannelId(String channelId) { + this.channelId = channelId; + } + + public Map getFunctionCode() { + return functionCode; + } + + public void setFunctionCode(Map functionCode) { + this.functionCode = functionCode; + } + + /** + * 添加函数代码 + */ + public void addFunctionCode(String functionName, String code) { + this.functionCode.put(functionName, code); + } + + public Map getContextVariables() { + return contextVariables; + } + + public void setContextVariables(Map contextVariables) { + this.contextVariables = contextVariables; + } + + /** + * 添加上下文变量 + */ + public void addContextVariable(String key, Object value) { + this.contextVariables.put(key, value); + } + + public Instant getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Instant createdAt) { + this.createdAt = createdAt; + } + + public Instant getExpireAt() { + return expireAt; + } + + public void setExpireAt(Instant expireAt) { + this.expireAt = expireAt; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + /** + * 检查快照是否过期 + */ + public boolean isExpired() { + if (expireAt == null) { + return false; + } + return Instant.now().isAfter(expireAt); + } + + @Override + public String toString() { + return "SessionSnapshot{" + + "snapshotId='" + snapshotId + '\'' + + ", sessionId='" + sessionId + '\'' + + ", userId='" + userId + '\'' + + ", tenantId='" + tenantId + '\'' + + ", channelType='" + channelType + '\'' + + ", functionCodeCount=" + (functionCode != null ? functionCode.size() : 0) + + ", createdAt=" + createdAt + + ", expireAt=" + expireAt + + '}'; + } + +} + diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/TriggerDefinition.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/TriggerDefinition.java index c6ae2dc..2ef2729 100644 --- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/TriggerDefinition.java +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/TriggerDefinition.java @@ -99,6 +99,27 @@ public class TriggerDefinition { */ private String executeFunction; + /** + * 放弃条件函数名(可选,用于判断是否停止监听) + */ + private String abandonFunction; + + /** + * 函数代码快照(执行时恢复) + * key: 函数名, value: 函数代码 + */ + private Map functionCodeSnapshot; + + /** + * 是否需要用户确认才能激活 + */ + private boolean requireConfirmation = true; + + /** + * 确认卡片类型(用于前端渲染) + */ + private String confirmCardType; + /** * 执行参数 */ @@ -147,9 +168,11 @@ public class TriggerDefinition { public TriggerDefinition() { this.parameters = new HashMap<>(); this.metadata = new HashMap<>(); + this.functionCodeSnapshot = new HashMap<>(); this.createdAt = Instant.now(); this.updatedAt = Instant.now(); this.status = TriggerStatus.PENDING_ACTIVATE; + this.requireConfirmation = true; } // Getters and Setters @@ -266,6 +289,38 @@ public void setExecuteFunction(String executeFunction) { this.executeFunction = executeFunction; } + public String getAbandonFunction() { + return abandonFunction; + } + + public void setAbandonFunction(String abandonFunction) { + this.abandonFunction = abandonFunction; + } + + public Map getFunctionCodeSnapshot() { + return functionCodeSnapshot; + } + + public void setFunctionCodeSnapshot(Map functionCodeSnapshot) { + this.functionCodeSnapshot = functionCodeSnapshot; + } + + public boolean isRequireConfirmation() { + return requireConfirmation; + } + + public void setRequireConfirmation(boolean requireConfirmation) { + this.requireConfirmation = requireConfirmation; + } + + public String getConfirmCardType() { + return confirmCardType; + } + + public void setConfirmCardType(String confirmCardType) { + this.confirmCardType = confirmCardType; + } + public Map getParameters() { return parameters; } diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/TriggerExecutionResult.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/TriggerExecutionResult.java new file mode 100644 index 0000000..c9c78fe --- /dev/null +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/model/TriggerExecutionResult.java @@ -0,0 +1,172 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.assistant.agent.extension.trigger.model; + +/** + * 触发器执行结果 + * + * @author canfeng + * @since 1.0.0 + */ +public class TriggerExecutionResult { + + /** + * 触发器ID + */ + private String triggerId; + + /** + * 条件是否满足 + */ + private Boolean conditionPassed; + + /** + * 执行是否成功 + */ + private Boolean executionSuccess; + + /** + * 是否应该放弃(停止监听) + */ + private Boolean shouldAbandon; + + /** + * 执行结果 + */ + private Object executionResult; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 执行耗时(毫秒) + */ + private Long executionTime; + + public TriggerExecutionResult() { + } + + // Static factory methods + + /** + * 创建条件未满足的结果 + */ + public static TriggerExecutionResult conditionNotMet(String triggerId) { + TriggerExecutionResult result = new TriggerExecutionResult(); + result.setTriggerId(triggerId); + result.setConditionPassed(false); + result.setExecutionSuccess(true); + return result; + } + + /** + * 创建成功结果 + */ + public static TriggerExecutionResult success(String triggerId, Object executionResult) { + TriggerExecutionResult result = new TriggerExecutionResult(); + result.setTriggerId(triggerId); + result.setConditionPassed(true); + result.setExecutionSuccess(true); + result.setExecutionResult(executionResult); + return result; + } + + /** + * 创建失败结果 + */ + public static TriggerExecutionResult failure(String triggerId, String errorMessage) { + TriggerExecutionResult result = new TriggerExecutionResult(); + result.setTriggerId(triggerId); + result.setExecutionSuccess(false); + result.setErrorMessage(errorMessage); + return result; + } + + // Getters and Setters + + public String getTriggerId() { + return triggerId; + } + + public void setTriggerId(String triggerId) { + this.triggerId = triggerId; + } + + public Boolean getConditionPassed() { + return conditionPassed; + } + + public void setConditionPassed(Boolean conditionPassed) { + this.conditionPassed = conditionPassed; + } + + public Boolean getExecutionSuccess() { + return executionSuccess; + } + + public void setExecutionSuccess(Boolean executionSuccess) { + this.executionSuccess = executionSuccess; + } + + public Boolean getShouldAbandon() { + return shouldAbandon; + } + + public void setShouldAbandon(Boolean shouldAbandon) { + this.shouldAbandon = shouldAbandon; + } + + public Object getExecutionResult() { + return executionResult; + } + + public void setExecutionResult(Object executionResult) { + this.executionResult = executionResult; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public Long getExecutionTime() { + return executionTime; + } + + public void setExecutionTime(Long executionTime) { + this.executionTime = executionTime; + } + + @Override + public String toString() { + return "TriggerExecutionResult{" + + "triggerId='" + triggerId + '\'' + + ", conditionPassed=" + conditionPassed + + ", executionSuccess=" + executionSuccess + + ", shouldAbandon=" + shouldAbandon + + ", executionTime=" + executionTime + + ", errorMessage='" + errorMessage + '\'' + + '}'; + } + +} + diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/repository/InMemorySessionSnapshotRepository.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/repository/InMemorySessionSnapshotRepository.java new file mode 100644 index 0000000..624c8b8 --- /dev/null +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/repository/InMemorySessionSnapshotRepository.java @@ -0,0 +1,109 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.assistant.agent.extension.trigger.repository; + +import com.alibaba.assistant.agent.extension.trigger.model.SessionSnapshot; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 内存会话快照存储实现 + * 用于开发测试,生产环境应使用数据库实现 + * + * @author canfeng + * @since 1.0.0 + */ +public class InMemorySessionSnapshotRepository implements SessionSnapshotRepository { + + private static final Logger log = LoggerFactory.getLogger(InMemorySessionSnapshotRepository.class); + + private final Map storage = new ConcurrentHashMap<>(); + + @Override + public void save(SessionSnapshot snapshot) { + if (snapshot == null || snapshot.getSnapshotId() == null) { + throw new IllegalArgumentException("Snapshot and snapshotId cannot be null"); + } + storage.put(snapshot.getSnapshotId(), snapshot); + log.debug("InMemorySessionSnapshotRepository save 保存快照成功, snapshotId={}", snapshot.getSnapshotId()); + } + + @Override + public Optional findById(String snapshotId) { + SessionSnapshot snapshot = storage.get(snapshotId); + log.debug("InMemorySessionSnapshotRepository findById 查找快照, snapshotId={}, found={}", + snapshotId, snapshot != null); + return Optional.ofNullable(snapshot); + } + + @Override + public List findBySessionId(String sessionId) { + List snapshots = storage.values().stream() + .filter(s -> sessionId.equals(s.getSessionId())) + .collect(Collectors.toList()); + log.debug("InMemorySessionSnapshotRepository findBySessionId 查找会话快照, sessionId={}, count={}", + sessionId, snapshots.size()); + return snapshots; + } + + @Override + public void deleteById(String snapshotId) { + SessionSnapshot removed = storage.remove(snapshotId); + log.debug("InMemorySessionSnapshotRepository deleteById 删除快照, snapshotId={}, success={}", + snapshotId, removed != null); + } + + @Override + public int deleteExpired(Instant expireTime) { + List expiredIds = storage.values().stream() + .filter(s -> s.getExpireAt() != null && s.getExpireAt().isBefore(expireTime)) + .map(SessionSnapshot::getSnapshotId) + .collect(Collectors.toList()); + + for (String id : expiredIds) { + storage.remove(id); + } + + log.debug("InMemorySessionSnapshotRepository deleteExpired 删除过期快照, expireTime={}, count={}", + expireTime, expiredIds.size()); + return expiredIds.size(); + } + + /** + * 获取当前存储的快照数量(用于测试) + */ + public int size() { + return storage.size(); + } + + /** + * 清空所有快照(用于测试) + */ + public void clear() { + storage.clear(); + log.debug("InMemorySessionSnapshotRepository clear 清空所有快照"); + } + +} + diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/repository/SessionSnapshotRepository.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/repository/SessionSnapshotRepository.java new file mode 100644 index 0000000..0b29585 --- /dev/null +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/trigger/repository/SessionSnapshotRepository.java @@ -0,0 +1,83 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.assistant.agent.extension.trigger.repository; + +import com.alibaba.assistant.agent.extension.trigger.model.SessionSnapshot; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; + +/** + * 会话快照存储接口(SPI) + * 应用层需要实现此接口提供持久化能力 + * + * @author canfeng + * @since 1.0.0 + */ +public interface SessionSnapshotRepository { + + /** + * 保存会话快照 + * + * @param snapshot 会话快照 + */ + void save(SessionSnapshot snapshot); + + /** + * 根据ID查找会话快照 + * + * @param snapshotId 快照ID + * @return 会话快照(可选) + */ + Optional findById(String snapshotId); + + /** + * 根据会话ID查找快照列表 + * + * @param sessionId 会话ID + * @return 快照列表 + */ + List findBySessionId(String sessionId); + + /** + * 删除会话快照 + * + * @param snapshotId 快照ID + */ + void deleteById(String snapshotId); + + /** + * 删除过期的快照 + * + * @param expireTime 过期时间点 + * @return 删除的快照数量 + */ + int deleteExpired(Instant expireTime); + + /** + * 检查快照是否存在 + * + * @param snapshotId 快照ID + * @return 是否存在 + */ + default boolean exists(String snapshotId) { + return findById(snapshotId).isPresent(); + } + +} + From fdff268ee9c36c1c9a10f56afb2dac38af4ad426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=8B=E9=A3=8E?= Date: Tue, 13 Jan 2026 23:06:54 +0800 Subject: [PATCH 2/7] =?UTF-8?q?chore(pom.xml):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=89=88=E6=9C=AC=E5=8F=B7=E8=87=B30.1.1-SNA?= =?UTF-8?q?PSHOT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新多个子模块及父项目的pom.xml中的版本号,从0.1.0升级到0.1.1-SNAPSHOT,以便进行后续开发迭代。 --- assistant-agent-autoconfigure/pom.xml | 2 +- assistant-agent-common/pom.xml | 2 +- assistant-agent-core/pom.xml | 2 +- assistant-agent-evaluation/pom.xml | 2 +- assistant-agent-extensions/pom.xml | 2 +- assistant-agent-prompt-builder/pom.xml | 2 +- assistant-agent-start/pom.xml | 2 +- pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assistant-agent-autoconfigure/pom.xml b/assistant-agent-autoconfigure/pom.xml index 8d1f21a..7905fc2 100644 --- a/assistant-agent-autoconfigure/pom.xml +++ b/assistant-agent-autoconfigure/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.0 + 0.1.1-SNAPSHOT assistant-agent-autoconfigure diff --git a/assistant-agent-common/pom.xml b/assistant-agent-common/pom.xml index 1c12eed..af97707 100644 --- a/assistant-agent-common/pom.xml +++ b/assistant-agent-common/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.0 + 0.1.1-SNAPSHOT assistant-agent-common diff --git a/assistant-agent-core/pom.xml b/assistant-agent-core/pom.xml index de1b627..bdfcfd5 100644 --- a/assistant-agent-core/pom.xml +++ b/assistant-agent-core/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.0 + 0.1.1-SNAPSHOT assistant-agent-core diff --git a/assistant-agent-evaluation/pom.xml b/assistant-agent-evaluation/pom.xml index 6a26ae8..2fa6556 100644 --- a/assistant-agent-evaluation/pom.xml +++ b/assistant-agent-evaluation/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.0 + 0.1.1-SNAPSHOT assistant-agent-evaluation diff --git a/assistant-agent-extensions/pom.xml b/assistant-agent-extensions/pom.xml index 2a61454..fc2a186 100644 --- a/assistant-agent-extensions/pom.xml +++ b/assistant-agent-extensions/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.0 + 0.1.1-SNAPSHOT assistant-agent-extensions diff --git a/assistant-agent-prompt-builder/pom.xml b/assistant-agent-prompt-builder/pom.xml index 490c7aa..7c1d15a 100644 --- a/assistant-agent-prompt-builder/pom.xml +++ b/assistant-agent-prompt-builder/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.0 + 0.1.1-SNAPSHOT assistant-agent-prompt-builder diff --git a/assistant-agent-start/pom.xml b/assistant-agent-start/pom.xml index 2563188..650fac2 100644 --- a/assistant-agent-start/pom.xml +++ b/assistant-agent-start/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.0 + 0.1.1-SNAPSHOT assistant-agent-start diff --git a/pom.xml b/pom.xml index 669225a..d73e0c2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.alibaba.agent.assistant assistant-agent - 0.1.0 + 0.1.1-SNAPSHOT pom assistant-agent-autoconfigure From 5d8c38d60735ecff56c54099f228e95038ee90d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=8B=E9=A3=8E?= Date: Wed, 14 Jan 2026 12:55:10 +0800 Subject: [PATCH 3/7] =?UTF-8?q?chore(pom.xml):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=89=88=E6=9C=AC=E5=8F=B7=E8=87=B30.1.2-SNA?= =?UTF-8?q?PSHOT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assistant-agent-autoconfigure/pom.xml | 2 +- assistant-agent-common/pom.xml | 2 +- assistant-agent-core/pom.xml | 2 +- assistant-agent-evaluation/pom.xml | 2 +- assistant-agent-extensions/pom.xml | 2 +- assistant-agent-prompt-builder/pom.xml | 2 +- assistant-agent-start/pom.xml | 2 +- pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assistant-agent-autoconfigure/pom.xml b/assistant-agent-autoconfigure/pom.xml index 7905fc2..6be8a02 100644 --- a/assistant-agent-autoconfigure/pom.xml +++ b/assistant-agent-autoconfigure/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT assistant-agent-autoconfigure diff --git a/assistant-agent-common/pom.xml b/assistant-agent-common/pom.xml index af97707..5bd58b3 100644 --- a/assistant-agent-common/pom.xml +++ b/assistant-agent-common/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT assistant-agent-common diff --git a/assistant-agent-core/pom.xml b/assistant-agent-core/pom.xml index bdfcfd5..aefbb0b 100644 --- a/assistant-agent-core/pom.xml +++ b/assistant-agent-core/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT assistant-agent-core diff --git a/assistant-agent-evaluation/pom.xml b/assistant-agent-evaluation/pom.xml index 2fa6556..4cb70d9 100644 --- a/assistant-agent-evaluation/pom.xml +++ b/assistant-agent-evaluation/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT assistant-agent-evaluation diff --git a/assistant-agent-extensions/pom.xml b/assistant-agent-extensions/pom.xml index fc2a186..90d7b91 100644 --- a/assistant-agent-extensions/pom.xml +++ b/assistant-agent-extensions/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT assistant-agent-extensions diff --git a/assistant-agent-prompt-builder/pom.xml b/assistant-agent-prompt-builder/pom.xml index 7c1d15a..f5254ad 100644 --- a/assistant-agent-prompt-builder/pom.xml +++ b/assistant-agent-prompt-builder/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT assistant-agent-prompt-builder diff --git a/assistant-agent-start/pom.xml b/assistant-agent-start/pom.xml index 650fac2..0b87073 100644 --- a/assistant-agent-start/pom.xml +++ b/assistant-agent-start/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT assistant-agent-start diff --git a/pom.xml b/pom.xml index d73e0c2..2f2b355 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.alibaba.agent.assistant assistant-agent - 0.1.1-SNAPSHOT + 0.1.2-SNAPSHOT pom assistant-agent-autoconfigure From a04b48c74d1d4a2c3efa977e6eb275bf3f48bbe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=8B=E9=A3=8E?= Date: Thu, 15 Jan 2026 11:14:14 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat(McpDynamicToolFactory):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0McpServerAwareToolCallback=E6=8E=A5=E5=8F=A3=E4=BB=A5?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E5=8A=A8=E6=80=81=E5=B7=A5=E5=85=B7=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E7=81=B5=E6=B4=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dynamic/mcp/McpDynamicToolFactory.java | 38 ++++++++--- .../mcp/McpServerAwareToolCallback.java | 68 +++++++++++++++++++ 2 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/dynamic/mcp/McpServerAwareToolCallback.java diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/dynamic/mcp/McpDynamicToolFactory.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/dynamic/mcp/McpDynamicToolFactory.java index d3a0d88..aadac5a 100644 --- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/dynamic/mcp/McpDynamicToolFactory.java +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/dynamic/mcp/McpDynamicToolFactory.java @@ -133,8 +133,11 @@ public List createTools(DynamicToolFactoryContext context) { /** * 从 ToolCallback 创建 CodeactTool。 * - *

类名使用配置的 server 名称转换后的结果。 - * 方法名使用工具原始名称归一化后的结果。 + *

类名和方法名的确定逻辑: + *

    + *
  1. 如果 callback 实现了 {@link McpServerAwareToolCallback},使用其提供的 serverName 和 toolName
  2. + *
  3. 否则使用配置的 serverSpecs 或默认前缀
  4. + *
*/ private CodeactTool createToolFromCallback(ToolCallback callback, ObjectMapper objectMapper, NameNormalizer nameNormalizer) { @@ -148,13 +151,30 @@ private CodeactTool createToolFromCallback(ToolCallback callback, ObjectMapper o String targetClassName = nameNormalizer.normalizeClassName(defaultTargetClassNamePrefix); String targetClassDescription = defaultTargetClassDescription; - // 如果有自定义的 serverSpecs,尝试匹配 - for (Map.Entry entry : serverSpecs.entrySet()) { - McpServerSpec spec = entry.getValue(); - // 使用 serverSpec 中的名称 - targetClassName = nameNormalizer.normalizeClassName(spec.getServerName()); - targetClassDescription = spec.getDescription(); - break; // 目前只支持单个 server,使用第一个 + // 优先检查是否实现了 McpServerAwareToolCallback 接口 + if (callback instanceof McpServerAwareToolCallback serverAwareCallback) { + String serverName = serverAwareCallback.getServerName(); + String displayName = serverAwareCallback.getServerDisplayName(); + + if (serverName != null && !serverName.isEmpty()) { + targetClassName = nameNormalizer.normalizeClassName(serverName); + targetClassDescription = displayName != null ? displayName : serverName; + // 使用原始工具名而不是 ToolDefinition 中的名称 + toolName = serverAwareCallback.getToolName(); + + logger.debug("McpDynamicToolFactory#createToolFromCallback - reason=从McpServerAwareToolCallback获取服务器信息, " + + "serverName={}, toolName={}", serverName, toolName); + } + } + // 如果有自定义的 serverSpecs,尝试匹配(作为后备方案) + else if (!serverSpecs.isEmpty()) { + for (Map.Entry entry : serverSpecs.entrySet()) { + McpServerSpec spec = entry.getValue(); + // 使用 serverSpec 中的名称 + targetClassName = nameNormalizer.normalizeClassName(spec.getServerName()); + targetClassDescription = spec.getDescription(); + break; // 目前只支持单个 server,使用第一个 + } } // 方法名:使用工具原始名称归一化后的结果 diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/dynamic/mcp/McpServerAwareToolCallback.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/dynamic/mcp/McpServerAwareToolCallback.java new file mode 100644 index 0000000..5a4eafb --- /dev/null +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/dynamic/mcp/McpServerAwareToolCallback.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.assistant.agent.extension.dynamic.mcp; + +import org.springframework.ai.tool.ToolCallback; + +/** + * 服务器感知的 MCP 工具回调接口。 + * + *

实现此接口的 ToolCallback 可以提供 MCP Server 的元数据信息, + * 使得 {@link McpDynamicToolFactory} 能够正确地将工具分组到不同的类中。 + * + *

使用场景: + *

    + *
  • 多 MCP Server 环境下,每个 Server 的工具需要归属到不同的 Python 类
  • + *
  • 需要自定义 Server 显示名称或描述
  • + *
+ * + * @author Assistant Agent Team + * @since 1.0.0 + */ +public interface McpServerAwareToolCallback extends ToolCallback { + + /** + * 获取 MCP Server 名称。 + * + *

此名称将用于生成 Python 类名(经过归一化处理)。 + * 例如 "aone-app-center" 会被转换为 "AoneAppCenter" 类。 + * + * @return MCP Server 名称,不能为 null + */ + String getServerName(); + + /** + * 获取 MCP Server 显示名称(可选)。 + * + *

此名称用于生成类的描述文档。如果返回 null,将使用 serverName。 + * + * @return MCP Server 显示名称,可以为 null + */ + default String getServerDisplayName() { + return getServerName(); + } + + /** + * 获取原始工具名称。 + * + *

MCP 协议中定义的原始工具名称,如 "getCodeInfo"、"searchApplications" 等。 + * + * @return 原始工具名称,不能为 null + */ + String getToolName(); + +} + From 950bede22720f3a10ed943af25802e395a1116b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=8B=E9=A3=8E?= Date: Thu, 15 Jan 2026 19:38:41 +0800 Subject: [PATCH 5/7] =?UTF-8?q?docs(CodeactAgentConfig):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E9=85=8D=E7=BD=AE=E8=AF=B4=E6=98=8E=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=B7=A5=E4=BD=9C=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assistant/agent/start/config/CodeactAgentConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/config/CodeactAgentConfig.java b/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/config/CodeactAgentConfig.java index 092d2be..2d68ccd 100644 --- a/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/config/CodeactAgentConfig.java +++ b/assistant-agent-start/src/main/java/com/alibaba/assistant/agent/start/config/CodeactAgentConfig.java @@ -70,11 +70,16 @@ public class CodeactAgentConfig { - 通过代码调用工具(search、reply、notification等) - 处理查询、计算、触发器创建等多种任务 + 【工作模式】 + 你的工作分为两个阶段: + 1. React阶段(思考):快速判断任务意图,决定需要执行什么操作 + 2. Codeact阶段(执行):通过write_code编写代码,通过execute_code执行代码 + 【可用工具】 1. write_code: 编写普通的Python函数 2. write_condition_code: 编写触发器条件判断函数(返回bool值) 3. execute_code: 执行已编写的函数 - 4. search/reply/notification: 可在代码中调用的辅助工具 + 4. 其它工具: 可以在思考过后调用的其他工具 【核心原则】 - 代码优先:优先通过编写代码来解决问题,而不是直接使用React阶段的工具 From 48ef0eb8290c36110ce1c3024878817ed357cd01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=80=E4=B8=83?= Date: Thu, 15 Jan 2026 19:37:01 +0800 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9EHookPhases?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/common/hook/AgentPhase.java | 79 +++++++ .../agent/common/hook/HookPhaseUtils.java | 220 ++++++++++++++++++ .../agent/common/hook/HookPhases.java | 85 +++++++ .../experience/hook/FastIntentReactHook.java | 3 + .../learning/hook/AfterAgentLearningHook.java | 4 +- 5 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/AgentPhase.java create mode 100644 assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/HookPhaseUtils.java create mode 100644 assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/HookPhases.java diff --git a/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/AgentPhase.java b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/AgentPhase.java new file mode 100644 index 0000000..90dfcf8 --- /dev/null +++ b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/AgentPhase.java @@ -0,0 +1,79 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.assistant.agent.common.hook; + +/** + * Agent 执行阶段枚举 + * + *

用于标识 Hook 应该在哪个 Agent 阶段执行。CodeactAgent 架构中有两个主要阶段: + *

    + *
  • React 阶段:主 Agent 决策层,LLM 决定使用什么工具完成任务
  • + *
  • Codeact 阶段:代码生成子 Agent 层,LLM 生成具体代码
  • + *
+ * + *

使用示例: + *

{@code
+ * @HookPhases(AgentPhase.REACT)
+ * public class FastIntentReactHook extends AgentHook { ... }
+ * 
+ * @HookPhases(AgentPhase.CODEACT)
+ * public class CodeGenExperienceHook extends ModelHook { ... }
+ * 
+ * @HookPhases({AgentPhase.REACT, AgentPhase.CODEACT})
+ * public class CommonEvaluationHook extends ModelHook { ... }
+ * }
+ * + * @author Assistant Agent Team + * @since 1.0.0 + * @see HookPhases + * @see HookPhaseUtils + */ +public enum AgentPhase { + + /** + * React 阶段 - 主 Agent 决策层 + * + *

对应 CodeactAgent.builder().hooks() 参数。 + * 在此阶段,主 Agent(ReactAgent/CodeactAgent)负责: + *

    + *
  • 理解用户意图
  • + *
  • 决定调用哪些工具(write_code, execute_code, reply 等)
  • + *
  • 协调整体任务执行流程
  • + *
+ */ + REACT, + + /** + * Codeact 阶段 - 代码生成子 Agent 层 + * + *

对应 CodeactAgent.builder().subAgentHooks() 参数。 + * 在此阶段,CodeGeneratorSubAgent 负责: + *

    + *
  • 根据需求描述生成 Python 函数代码
  • + *
  • 调用 LLM 进行代码生成
  • + *
  • 代码质量评估和优化
  • + *
+ */ + CODEACT, + + /** + * 两个阶段都适用 + * + *

标记为 ALL 的 Hook 会同时注册到 React 阶段和 Codeact 阶段。 + * 适用于通用的评估、日志、监控等跨阶段的 Hook。 + */ + ALL +} diff --git a/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/HookPhaseUtils.java b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/HookPhaseUtils.java new file mode 100644 index 0000000..fd3000a --- /dev/null +++ b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/HookPhaseUtils.java @@ -0,0 +1,220 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.assistant.agent.common.hook; + +import com.alibaba.cloud.ai.graph.agent.hook.Hook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Hook 阶段过滤工具类 + * + *

提供按 {@link AgentPhase} 过滤和分组 Hook 的工具方法。 + * 用于在 Agent 注册时自动将 Hook 分配到正确的阶段。 + * + *

使用示例

+ *
{@code
+ * // 在 AgentRegistry 中使用
+ * @Autowired
+ * public AgentRegistry(List allHooks, ...) {
+ *     // 自动按阶段分组
+ *     Map> grouped = HookPhaseUtils.groupByPhase(allHooks);
+ *     this.reactHooks = grouped.get(AgentPhase.REACT);
+ *     this.codeactHooks = grouped.get(AgentPhase.CODEACT);
+ * }
+ * 
+ * // 或者分别过滤
+ * List reactHooks = HookPhaseUtils.filterByPhase(allHooks, AgentPhase.REACT);
+ * List codeactHooks = HookPhaseUtils.filterByPhase(allHooks, AgentPhase.CODEACT);
+ * }
+ * + * @author Assistant Agent Team + * @since 1.0.0 + * @see AgentPhase + * @see HookPhases + */ +public final class HookPhaseUtils { + + private static final Logger log = LoggerFactory.getLogger(HookPhaseUtils.class); + + private HookPhaseUtils() { + // 工具类,禁止实例化 + } + + /** + * 获取 Hook 适用的阶段 + * + *

读取 Hook 类上的 {@link HookPhases} 注解。如果没有注解,默认返回 {@link AgentPhase#REACT}。 + * + * @param hook Hook 实例 + * @return Hook 适用的阶段数组 + */ + public static AgentPhase[] getHookPhases(Hook hook) { + if (hook == null) { + return new AgentPhase[]{ AgentPhase.REACT }; + } + + HookPhases annotation = hook.getClass().getAnnotation(HookPhases.class); + if (annotation != null) { + return annotation.value(); + } + + // 默认返回 REACT(向后兼容) + return new AgentPhase[]{ AgentPhase.REACT }; + } + + /** + * 判断 Hook 是否适用于指定阶段 + * + * @param hook Hook 实例 + * @param phase 目标阶段 + * @return 如果 Hook 适用于指定阶段返回 true + */ + public static boolean isApplicableToPhase(Hook hook, AgentPhase phase) { + if (hook == null || phase == null) { + return false; + } + + AgentPhase[] phases = getHookPhases(hook); + for (AgentPhase p : phases) { + if (p == AgentPhase.ALL || p == phase) { + return true; + } + } + return false; + } + + /** + * 过滤出指定阶段的 Hook 列表 + * + * @param hooks 所有 Hook 列表 + * @param phase 目标阶段 + * @return 适用于指定阶段的 Hook 列表(不可变) + */ + public static List filterByPhase(List hooks, AgentPhase phase) { + if (hooks == null || hooks.isEmpty() || phase == null) { + return Collections.emptyList(); + } + + List filtered = hooks.stream() + .filter(hook -> isApplicableToPhase(hook, phase)) + .collect(Collectors.toList()); + + log.debug("HookPhaseUtils#filterByPhase phase={}, total={}, filtered={}", + phase, hooks.size(), filtered.size()); + + return filtered; + } + + /** + * 将 Hook 列表按阶段分组 + * + *

注意:如果一个 Hook 标记为 {@link AgentPhase#ALL}, + * 它会同时出现在 REACT 和 CODEACT 两个分组中。 + * + * @param hooks 所有 Hook 列表 + * @return 按阶段分组的 Map,key 为 AgentPhase,value 为该阶段的 Hook 列表 + */ + public static Map> groupByPhase(List hooks) { + Map> result = new EnumMap<>(AgentPhase.class); + result.put(AgentPhase.REACT, new ArrayList<>()); + result.put(AgentPhase.CODEACT, new ArrayList<>()); + result.put(AgentPhase.ALL, new ArrayList<>()); + + if (hooks == null || hooks.isEmpty()) { + return result; + } + + for (Hook hook : hooks) { + AgentPhase[] phases = getHookPhases(hook); + for (AgentPhase phase : phases) { + if (phase == AgentPhase.ALL) { + // ALL 表示两个阶段都适用 + result.get(AgentPhase.REACT).add(hook); + result.get(AgentPhase.CODEACT).add(hook); + result.get(AgentPhase.ALL).add(hook); + } else { + result.get(phase).add(hook); + } + } + } + + log.info("HookPhaseUtils#groupByPhase 分组完成: total={}, react={}, codeact={}, all={}", + hooks.size(), + result.get(AgentPhase.REACT).size(), + result.get(AgentPhase.CODEACT).size(), + result.get(AgentPhase.ALL).size()); + + return result; + } + + /** + * 获取 React 阶段的 Hook 列表 + * + *

便捷方法,等同于 {@code filterByPhase(hooks, AgentPhase.REACT)} + * + * @param hooks 所有 Hook 列表 + * @return React 阶段的 Hook 列表 + */ + public static List getReactHooks(List hooks) { + return filterByPhase(hooks, AgentPhase.REACT); + } + + /** + * 获取 Codeact 阶段的 Hook 列表 + * + *

便捷方法,等同于 {@code filterByPhase(hooks, AgentPhase.CODEACT)} + * + * @param hooks 所有 Hook 列表 + * @return Codeact 阶段的 Hook 列表 + */ + public static List getCodeactHooks(List hooks) { + return filterByPhase(hooks, AgentPhase.CODEACT); + } + + /** + * 打印 Hook 分组信息(用于调试) + * + * @param hooks 所有 Hook 列表 + */ + public static void logHookPhases(List hooks) { + if (hooks == null || hooks.isEmpty()) { + log.info("HookPhaseUtils#logHookPhases 无 Hook 需要分组"); + return; + } + + log.info("HookPhaseUtils#logHookPhases 开始打印 Hook 阶段信息,总数: {}", hooks.size()); + + for (Hook hook : hooks) { + AgentPhase[] phases = getHookPhases(hook); + StringBuilder phaseStr = new StringBuilder(); + for (int i = 0; i < phases.length; i++) { + if (i > 0) { + phaseStr.append(", "); + } + phaseStr.append(phases[i].name()); + } + log.info(" - Hook: {}, Phases: [{}]", hook.getName(), phaseStr); + } + } +} diff --git a/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/HookPhases.java b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/HookPhases.java new file mode 100644 index 0000000..0e0f92c --- /dev/null +++ b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/hook/HookPhases.java @@ -0,0 +1,85 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.assistant.agent.common.hook; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标注 Hook 适用的 Agent 执行阶段 + * + *

此注解用于声明一个 Hook 应该在 CodeactAgent 的哪个阶段执行。 + * 与 spring-ai-alibaba 中的 {@code @HookPositions} 注解配合使用, + * 形成完整的 Hook 元数据体系: + *

    + *
  • {@code @HookPositions} - 指定 Hook 的执行时机(BEFORE_AGENT, AFTER_AGENT, BEFORE_MODEL, AFTER_MODEL)
  • + *
  • {@code @HookPhases} - 指定 Hook 的执行阶段(REACT, CODEACT, ALL)
  • + *
+ * + *

使用示例

+ *
{@code
+ * // React 阶段的 Hook(用于主 Agent)
+ * @HookPhases(AgentPhase.REACT)
+ * @HookPositions(HookPosition.BEFORE_AGENT)
+ * public class FastIntentReactHook extends AgentHook {
+ *     // ...
+ * }
+ * 
+ * // Codeact 阶段的 Hook(用于代码生成子 Agent)
+ * @HookPhases(AgentPhase.CODEACT)
+ * @HookPositions(HookPosition.BEFORE_MODEL)
+ * public class CodeGenerationExperienceHook extends ModelHook {
+ *     // ...
+ * }
+ * 
+ * // 两个阶段都适用的 Hook
+ * @HookPhases({AgentPhase.REACT, AgentPhase.CODEACT})
+ * @HookPositions(HookPosition.AFTER_MODEL)
+ * public class CommonEvaluationHook extends ModelHook {
+ *     // ...
+ * }
+ * 
+ * // 使用 ALL 简化声明
+ * @HookPhases(AgentPhase.ALL)
+ * public class UniversalLoggingHook extends AgentHook {
+ *     // ...
+ * }
+ * }
+ * + *

默认行为

+ *

如果 Hook 类没有添加此注解,默认被视为 {@link AgentPhase#REACT} 阶段的 Hook, + * 以保持向后兼容性。 + * + * @author Assistant Agent Team + * @since 1.0.0 + * @see AgentPhase + * @see HookPhaseUtils + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface HookPhases { + + /** + * 指定 Hook 适用的 Agent 阶段 + * + *

可以指定一个或多个阶段。如果指定多个阶段,Hook 会被注册到所有指定的阶段。 + * + * @return Hook 适用的 Agent 阶段数组,默认为 {@link AgentPhase#REACT} + */ + AgentPhase[] value() default { AgentPhase.REACT }; +} diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/hook/FastIntentReactHook.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/hook/FastIntentReactHook.java index 65f267c..9d66e2f 100644 --- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/hook/FastIntentReactHook.java +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/hook/FastIntentReactHook.java @@ -12,6 +12,8 @@ import com.alibaba.cloud.ai.graph.KeyStrategy; import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.assistant.agent.common.hook.AgentPhase; +import com.alibaba.assistant.agent.common.hook.HookPhases; import com.alibaba.cloud.ai.graph.agent.hook.AgentHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; @@ -42,6 +44,7 @@ *

  • 设置 jump_to=tool 以跳过本轮 model 调用,直接进入 tool 执行
  • * */ +@HookPhases(AgentPhase.REACT) @HookPositions(HookPosition.BEFORE_AGENT) public class FastIntentReactHook extends AgentHook { diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/learning/hook/AfterAgentLearningHook.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/learning/hook/AfterAgentLearningHook.java index 7ea2afc..01e24d3 100644 --- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/learning/hook/AfterAgentLearningHook.java +++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/learning/hook/AfterAgentLearningHook.java @@ -16,6 +16,8 @@ package com.alibaba.assistant.agent.extension.learning.hook; +import com.alibaba.assistant.agent.common.hook.AgentPhase; +import com.alibaba.assistant.agent.common.hook.HookPhases; import com.alibaba.assistant.agent.extension.learning.internal.DefaultLearningContext; import com.alibaba.assistant.agent.extension.learning.internal.DefaultLearningTask; import com.alibaba.assistant.agent.extension.learning.model.LearningContext; @@ -23,7 +25,6 @@ import com.alibaba.assistant.agent.extension.learning.model.LearningTask; import com.alibaba.assistant.agent.extension.learning.model.LearningTriggerContext; import com.alibaba.assistant.agent.extension.learning.model.LearningTriggerSource; -import com.alibaba.assistant.agent.extension.learning.model.*; import com.alibaba.assistant.agent.extension.learning.spi.LearningExecutor; import com.alibaba.assistant.agent.extension.learning.spi.LearningStrategy; import com.alibaba.cloud.ai.graph.OverAllState; @@ -46,6 +47,7 @@ * @author Assistant Agent Team * @since 1.0.0 */ +@HookPhases(AgentPhase.REACT) @HookPositions(HookPosition.AFTER_AGENT) public class AfterAgentLearningHook extends AgentHook { From 6fc9aec72aad69fc689c4952a8029bb92129310f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=8B=E9=A3=8E?= Date: Thu, 15 Jan 2026 19:40:07 +0800 Subject: [PATCH 7/7] =?UTF-8?q?chore(pom.xml):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E7=89=88=E6=9C=AC=E5=8F=B7=E8=87=B30.1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assistant-agent-autoconfigure/pom.xml | 2 +- assistant-agent-common/pom.xml | 2 +- assistant-agent-core/pom.xml | 2 +- assistant-agent-evaluation/pom.xml | 2 +- assistant-agent-extensions/pom.xml | 2 +- assistant-agent-prompt-builder/pom.xml | 2 +- assistant-agent-start/pom.xml | 2 +- pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assistant-agent-autoconfigure/pom.xml b/assistant-agent-autoconfigure/pom.xml index 6be8a02..25c7711 100644 --- a/assistant-agent-autoconfigure/pom.xml +++ b/assistant-agent-autoconfigure/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.2-SNAPSHOT + 0.1.2 assistant-agent-autoconfigure diff --git a/assistant-agent-common/pom.xml b/assistant-agent-common/pom.xml index 5bd58b3..76611e9 100644 --- a/assistant-agent-common/pom.xml +++ b/assistant-agent-common/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.2-SNAPSHOT + 0.1.2 assistant-agent-common diff --git a/assistant-agent-core/pom.xml b/assistant-agent-core/pom.xml index aefbb0b..2b5a906 100644 --- a/assistant-agent-core/pom.xml +++ b/assistant-agent-core/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.2-SNAPSHOT + 0.1.2 assistant-agent-core diff --git a/assistant-agent-evaluation/pom.xml b/assistant-agent-evaluation/pom.xml index 4cb70d9..ff0bfda 100644 --- a/assistant-agent-evaluation/pom.xml +++ b/assistant-agent-evaluation/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.2-SNAPSHOT + 0.1.2 assistant-agent-evaluation diff --git a/assistant-agent-extensions/pom.xml b/assistant-agent-extensions/pom.xml index 90d7b91..4a41beb 100644 --- a/assistant-agent-extensions/pom.xml +++ b/assistant-agent-extensions/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.2-SNAPSHOT + 0.1.2 assistant-agent-extensions diff --git a/assistant-agent-prompt-builder/pom.xml b/assistant-agent-prompt-builder/pom.xml index f5254ad..841f324 100644 --- a/assistant-agent-prompt-builder/pom.xml +++ b/assistant-agent-prompt-builder/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.2-SNAPSHOT + 0.1.2 assistant-agent-prompt-builder diff --git a/assistant-agent-start/pom.xml b/assistant-agent-start/pom.xml index 0b87073..0d3511b 100644 --- a/assistant-agent-start/pom.xml +++ b/assistant-agent-start/pom.xml @@ -6,7 +6,7 @@ com.alibaba.agent.assistant assistant-agent - 0.1.2-SNAPSHOT + 0.1.2 assistant-agent-start diff --git a/pom.xml b/pom.xml index 2f2b355..c939b22 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.alibaba.agent.assistant assistant-agent - 0.1.2-SNAPSHOT + 0.1.2 pom assistant-agent-autoconfigure