diff --git a/assistant-agent-autoconfigure/pom.xml b/assistant-agent-autoconfigure/pom.xml
index aa453a6..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.1
+ 0.1.2
assistant-agent-autoconfigure
diff --git a/assistant-agent-common/pom.xml b/assistant-agent-common/pom.xml
index e8a303b..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.1
+ 0.1.2
assistant-agent-common
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-core/pom.xml b/assistant-agent-core/pom.xml
index 8741fc0..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.1
+ 0.1.2
assistant-agent-core
diff --git a/assistant-agent-evaluation/pom.xml b/assistant-agent-evaluation/pom.xml
index 1dba843..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.1
+ 0.1.2
assistant-agent-evaluation
diff --git a/assistant-agent-extensions/pom.xml b/assistant-agent-extensions/pom.xml
index 7531d25..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.1
+ 0.1.2
assistant-agent-extensions
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 名称转换后的结果。
- * 方法名使用工具原始名称归一化后的结果。
+ *
类名和方法名的确定逻辑:
+ *
+ * - 如果 callback 实现了 {@link McpServerAwareToolCallback},使用其提供的 serverName 和 toolName
+ * - 否则使用配置的 serverSpecs 或默认前缀
+ *
*/
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();
+
+}
+
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 {
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 0353043..ea9f3ed 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 7ce76a5..bee68cb 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 9ac23df..37796c9 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 009830b..79d2b74 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 adaea44..898344d 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();
+ }
+
+}
+
diff --git a/assistant-agent-prompt-builder/pom.xml b/assistant-agent-prompt-builder/pom.xml
index dcc1415..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.1
+ 0.1.2
assistant-agent-prompt-builder
diff --git a/assistant-agent-start/pom.xml b/assistant-agent-start/pom.xml
index ef71bcb..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.1
+ 0.1.2
assistant-agent-start
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阶段的工具
diff --git a/pom.xml b/pom.xml
index d4b01f9..c939b22 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
4.0.0
com.alibaba.agent.assistant
assistant-agent
- 0.1.1
+ 0.1.2
pom
assistant-agent-autoconfigure