diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 00000000..4ea72a91 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml new file mode 100644 index 00000000..7ef04e2e --- /dev/null +++ b/.idea/copilot.data.migration.ask.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml new file mode 100644 index 00000000..8648f940 --- /dev/null +++ b/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Partner-Main/src/main/java/work/slhaf/partner/common/Constant.java b/Partner-Main/src/main/java/work/slhaf/partner/common/Constant.java new file mode 100644 index 00000000..0707b3d3 --- /dev/null +++ b/Partner-Main/src/main/java/work/slhaf/partner/common/Constant.java @@ -0,0 +1,11 @@ +package work.slhaf.partner.common; + +public final class Constant { + + public static final class Path { + public static final String DATA = "./data"; + public static final String MEMORY_DATA = DATA + "/memory"; + public static final String ACTION_PROGRAM = DATA + "/action"; + } + +} diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/PartnerCore.java b/Partner-Main/src/main/java/work/slhaf/partner/core/PartnerCore.java index eee0652c..9b75d06d 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/PartnerCore.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/PartnerCore.java @@ -13,10 +13,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import static work.slhaf.partner.common.Constant.Path.MEMORY_DATA; + @Slf4j public abstract class PartnerCore> extends PersistableObject { - private static final String STORAGE_DIR = "./data/memory/"; private final String id = ((PartnerAgentConfigManager) AgentConfigManager.INSTANCE).getConfig().getAgentId(); public PartnerCore() throws IOException, ClassNotFoundException { @@ -53,7 +54,7 @@ public abstract class PartnerCore> extends PersistableO public void serialize() throws IOException { //先写入到临时文件,如果正常写入则覆盖原文件 Path filePath = getFilePath(id + "-temp"); - Files.createDirectories(Path.of(STORAGE_DIR)); + Files.createDirectories(Path.of(MEMORY_DATA)); try { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile())); oos.writeObject(this); @@ -78,12 +79,12 @@ public abstract class PartnerCore> extends PersistableO } private Path getFilePath(String s) { - return Paths.get(STORAGE_DIR, s + "-" + getCoreKey() + ".memory"); + return Paths.get(MEMORY_DATA, s + "-" + getCoreKey() + ".memory"); } private void createStorageDirectory() { try { - Files.createDirectories(Paths.get(STORAGE_DIR)); + Files.createDirectories(Paths.get(MEMORY_DATA)); } catch (IOException e) { log.error("[{}]创建存储目录失败: {}", getCoreKey(), e.getMessage()); } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/ActionCapability.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/ActionCapability.java index 03d4f0c2..79003548 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/action/ActionCapability.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/ActionCapability.java @@ -1,24 +1,24 @@ package work.slhaf.partner.core.action; import work.slhaf.partner.api.agent.factory.capability.annotation.Capability; -import work.slhaf.partner.core.action.entity.ActionInfo; +import work.slhaf.partner.core.action.entity.ActionData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustData; import java.util.List; @Capability(value = "action") public interface ActionCapability { - void putPreparedAction(String uuid, ActionInfo actionInfo); + void putPreparedAction(String uuid, ActionData actionData); - List popPreparedAction(String userId); + List popPreparedAction(String userId); - List popPendingAction(String userId); + List popPendingAction(String userId); - List listPreparedAction(String userId); + List listPreparedAction(String userId); - List listPendingAction(String userId); + List listPendingAction(String userId); - void putPendingActions(String userId, ActionInfo actionInfo); + void putPendingActions(String userId, ActionData actionData); List selectTendencyCache(String input); diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/ActionCore.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/ActionCore.java index 8dd95d58..9497147b 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/action/ActionCore.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/ActionCore.java @@ -1,25 +1,35 @@ package work.slhaf.partner.core.action; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore; import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod; import work.slhaf.partner.common.vector.VectorClient; import work.slhaf.partner.core.PartnerCore; -import work.slhaf.partner.core.action.entity.ActionInfo; +import work.slhaf.partner.core.action.entity.ActionData; +import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.cache.ActionCacheData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData; +import work.slhaf.partner.core.action.exception.ActionInitFailedException; +import work.slhaf.partner.core.action.exception.ActionLoadFailedException; +import java.io.File; import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; -import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import static work.slhaf.partner.common.Constant.Path.ACTION_PROGRAM; + @SuppressWarnings("FieldMayBeFinal") @CapabilityCore(value = "action") @Slf4j @@ -28,64 +38,68 @@ public class ActionCore extends PartnerCore { /** * 对应本次交互即将执行或将要放置在行动池的预备任务,因此将以本次交互的uuid为键,其起到的作用相当于暂时的模块上下文 */ - private HashMap> preparedActions = new HashMap<>(); + private HashMap> preparedActions = new HashMap<>(); /** * 待确认任务,以userId区分不同用户,因为需要跨请求确认 */ - private HashMap> pendingActions = new HashMap<>(); + private HashMap> pendingActions = new HashMap<>(); /** * 语义缓存与行为倾向映射 */ private List actionCache = new ArrayList<>(); - private Lock cacheLock = new ReentrantLock(); + private final Lock cacheLock = new ReentrantLock(); - private Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + private final ExecutorService platformExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + private final ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor(); + + private final LinkedHashMap existedMetaActions = new LinkedHashMap<>(); public ActionCore() throws IOException, ClassNotFoundException { + new ActionWatchService().launch(); } @CapabilityMethod - public synchronized void putPendingActions(String userId, ActionInfo actionInfo) { + public synchronized void putPendingActions(String userId, ActionData actionData) { pendingActions.computeIfAbsent(userId, k -> { - List temp = new ArrayList<>(); - temp.add(actionInfo); + List temp = new ArrayList<>(); + temp.add(actionData); return temp; }); } @CapabilityMethod - public synchronized List popPendingAction(String userId) { - List infos = pendingActions.get(userId); + public synchronized List popPendingAction(String userId) { + List infos = pendingActions.get(userId); pendingActions.remove(userId); return infos; } @CapabilityMethod - public synchronized void putPreparedAction(String uuid, ActionInfo actionInfo) { + public synchronized void putPreparedAction(String uuid, ActionData actionData) { preparedActions.computeIfAbsent(uuid, k -> { - List temp = new ArrayList<>(); - temp.add(actionInfo); + List temp = new ArrayList<>(); + temp.add(actionData); return temp; }); } @CapabilityMethod - public synchronized List popPreparedAction(String userId) { - List infos = preparedActions.get(userId); + public synchronized List popPreparedAction(String userId) { + List infos = preparedActions.get(userId); preparedActions.remove(userId); return infos; } @CapabilityMethod - public List listPreparedAction(String userId) { + public List listPreparedAction(String userId) { return preparedActions.get(userId); } @CapabilityMethod - public List listPendingAction(String userId) { + public List listPendingAction(String userId) { return pendingActions.get(userId); } @@ -136,9 +150,9 @@ public class ActionCore extends PartnerCore { } } - executor.execute(() -> adjustMatchAndPassed(matchAndPassed, inputVector, input, vectorClient)); - executor.execute(() -> adjustMatchNotPassed(matchNotPassed, vectorClient)); - executor.execute(() -> adjustNotMatchPassed(notMatchPassed, inputVector, input, vectorClient)); + platformExecutor.execute(() -> adjustMatchAndPassed(matchAndPassed, inputVector, input, vectorClient)); + platformExecutor.execute(() -> adjustMatchNotPassed(matchNotPassed, vectorClient)); + platformExecutor.execute(() -> adjustNotMatchPassed(notMatchPassed, inputVector, input, vectorClient)); } /** @@ -148,7 +162,8 @@ public class ActionCore extends PartnerCore { * @param inputVector 本次输入内容的语义向量 * @param vectorClient 向量客户端 */ - private void adjustMatchAndPassed(List matchAndPassed, float[] inputVector, String input, VectorClient vectorClient) { + private void adjustMatchAndPassed(List matchAndPassed, float[] inputVector, String + input, VectorClient vectorClient) { matchAndPassed.forEach(adjustData -> { //获取原始缓存条目 String tendency = adjustData.getTendency(); @@ -204,7 +219,8 @@ public class ActionCore extends PartnerCore { * @param input 本次输入内容 * @param vectorClient 向量客户端 */ - private void adjustNotMatchPassed(List notMatchPassed, float[] inputVector, String input, VectorClient vectorClient) { + private void adjustNotMatchPassed(List notMatchPassed, float[] inputVector, String + input, VectorClient vectorClient) { notMatchPassed.forEach(adjustData -> { //获取原始缓存条目 String tendency = adjustData.getTendency(); @@ -232,4 +248,169 @@ public class ActionCore extends PartnerCore { protected String getCoreKey() { return "action-core"; } + + @SuppressWarnings("unchecked") + private class ActionWatchService { + + private HashMap registeredPaths = new HashMap<>(); + + public void launch() { + Path path = Path.of(ACTION_PROGRAM); + scanActions(path.toFile()); + launchActionDirectoryWatcher(path); + } + + private void launchActionDirectoryWatcher(Path path) { + WatchService watchService; + try { + watchService = FileSystems.getDefault().newWatchService(); + setupShutdownHook(watchService); + registerParentToWatch(path, watchService); + registerSubToWatch(path, watchService); + virtualExecutor.execute(registerWatchTask(path, watchService)); + } catch (IOException e) { + throw new ActionInitFailedException("行动程序目录监听器启动失败", e); + } + } + + private void setupShutdownHook(WatchService watchService) { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + watchService.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + })); + } + + private Runnable registerWatchTask(Path path, WatchService watchService) { + return () -> { + log.info("[{}] 行动程序目录监听器已启动", getCoreKey()); + while (true) { + WatchKey key; + try { + key = watchService.take(); + List> events = key.pollEvents(); + for (WatchEvent e : events) { + WatchEvent event = (WatchEvent) e; + WatchEvent.Kind kind = event.kind(); + Path context = event.context(); + log.info("[{}] 行动程序目录变更事件: {} - {}", getCoreKey(), kind.name(), context.toString()); + Path thisDir = (Path) key.watchable(); + //根据事件发生的目录进行分流,分为父目录事件和子程序事件 + if (thisDir.equals(path)) { + handleParentDirEvent(kind, thisDir, context, watchService); + } else { + handleSubDirEvent(kind, thisDir); + } + } + } catch (InterruptedException e) { + log.info("监听线程被中断,准备退出..."); + Thread.currentThread().interrupt(); // 恢复中断标志 + break; + } catch (ClosedWatchServiceException e) { + log.info("WatchService 已关闭,监听线程退出。"); + break; + } + } + }; + } + + private void handleSubDirEvent(WatchEvent.Kind kind, Path thisDir) { + // path为触发本次行动的文件的路径(当前位于某个action目录下) + // 先判定发生的目录前缀是否匹配(action、desc),否则忽略 + if (kind == StandardWatchEventKinds.ENTRY_CREATE || kind == StandardWatchEventKinds.ENTRY_MODIFY) { + // CREATE、MODIFY 事件将触发一次检测,看当前thisDir中action和desc是否都具备,如果通过则尝试加载(put)。 + boolean complete = checkComplete(thisDir); + if (!complete) return; + try { + MetaActionInfo newActionInfo = new MetaActionInfo(thisDir.toFile()); + existedMetaActions.put(thisDir.toString(), newActionInfo); + } catch (ActionLoadFailedException e) { + log.warn("[{}] 行动信息重新加载失败,触发行为: {}", getCoreKey(), kind.name()); + } + } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { + // DELETE 事件将会把该 MetaActionInfo 从记录中移除 + existedMetaActions.remove(thisDir.toString()); + } + } + + private boolean checkComplete(Path thisDir) { + File[] files = thisDir.toFile().listFiles(); + if (files == null) { + log.error("[{}]当前目录无法访问: [{}]", getCoreKey(), thisDir); + return false; + } + boolean existedAction = false; + boolean existedDesc = false; + for (File file : files) { + String fileName = file.getName(); + String nameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.')); + if (nameWithoutExt.equals("action")) existedAction = true; + else if (nameWithoutExt.equals("desc")) existedDesc = true; + } + return existedAction && existedDesc; + } + + private void handleParentDirEvent(WatchEvent.Kind kind, Path thisDir, Path context, WatchService watchService) { + Path path = Path.of(thisDir.toString(), context.toString()); + // MODIFY 事件不进行处理 + if (kind == StandardWatchEventKinds.ENTRY_CREATE) { + try { + path.register(watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY); + } catch (IOException e) { + log.error("[{}] 新增行动程序目录监听失败: {}", getCoreKey(), path, e); + } + } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { + WatchKey remove = registeredPaths.remove(path); + remove.cancel(); + } + } + + private void registerSubToWatch(Path path, WatchService watchService) throws IOException { + Files.walkFileTree(path, new SimpleFileVisitor<>() { + @Override + public @NotNull FileVisitResult preVisitDirectory(@NotNull Path dir, @NotNull BasicFileAttributes attrs) throws IOException { + if (dir.getFileName().startsWith(".")) return FileVisitResult.CONTINUE; + WatchKey key = dir.register(watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY); + registeredPaths.put(dir, key); + return FileVisitResult.CONTINUE; + } + }); + } + + private void registerParentToWatch(Path path, WatchService watchService) throws IOException { + WatchKey key = path.register(watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_MODIFY); + registeredPaths.put(path, key); + } + + private void scanActions(File file) { + if (!file.exists() || file.isFile()) { + throw new ActionInitFailedException("未找到行动程序目录: " + file.getAbsolutePath()); + } + File[] files = file.listFiles(); + if (files == null) { + throw new ActionInitFailedException("目录无法访问: " + file.getAbsolutePath()); + } + for (File f : files) { + try { + MetaActionInfo actionInfo = new MetaActionInfo(f); + existedMetaActions.put(f.getName(), actionInfo); + log.info("[{}] 行动程序[{}]已加载", getCoreKey(), actionInfo.getKey()); + } catch (ActionLoadFailedException e) { + log.warn("[{}] 行动程序未加载: {}", getCoreKey(), e.getMessage()); + } + } + + } + } } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionInfo.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionData.java similarity index 54% rename from Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionInfo.java rename to Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionData.java index d3a3318d..18ddcddc 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionInfo.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionData.java @@ -4,8 +4,11 @@ import lombok.Data; import java.util.List; +/** + * 行动模块传递的行动数据,包含行动uuid、倾向、状态、行动链、结果、发起原因、行动描述等信息。 + */ @Data -public abstract class ActionInfo { +public abstract class ActionData { protected String uuid; protected String tendency; protected ActionStatus status; @@ -13,4 +16,8 @@ public abstract class ActionInfo { protected String Result; protected String reason; protected String description; + + public enum ActionStatus { + SUCCESS, FAILED, EXECUTING, WAITING, PREPARE + } } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionStatus.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionStatus.java deleted file mode 100644 index 31256076..00000000 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package work.slhaf.partner.core.action.entity; - -public enum ActionStatus { - SUCCESS, FAILED, EXECUTING, WAITING, PREPARE -} diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionType.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionType.java deleted file mode 100644 index 2354929e..00000000 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ActionType.java +++ /dev/null @@ -1,5 +0,0 @@ -package work.slhaf.partner.core.action.entity; - -public enum ActionType { - IMMEDIATE, PLANNING -} diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ImmediateActionInfo.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ImmediateActionData.java similarity index 55% rename from Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ImmediateActionInfo.java rename to Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ImmediateActionData.java index 5e65d871..388ea0b1 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ImmediateActionInfo.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ImmediateActionData.java @@ -3,7 +3,10 @@ package work.slhaf.partner.core.action.entity; import lombok.Data; import lombok.EqualsAndHashCode; +/** + * 即时行动数据类,继承自{@link ActionData} + */ @EqualsAndHashCode(callSuper = true) @Data -public class ImmediateActionInfo extends ActionInfo { +public class ImmediateActionData extends ActionData { } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaAction.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaAction.java index bdf2451b..349bebb8 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaAction.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaAction.java @@ -2,25 +2,97 @@ package work.slhaf.partner.core.action.entity; import lombok.Data; import org.jetbrains.annotations.NotNull; +import work.slhaf.partner.common.Constant; +import java.io.File; +import java.nio.file.Path; +import java.util.concurrent.Callable; + +import static work.slhaf.partner.common.Constant.Path.ACTION_PROGRAM; + +/** + * 行动链中的单一元素,实现{@link Runnable}接口,封装了调用外部行动程序的必要信息,可被执行 + */ @Data -public class MetaAction implements Comparable { - //行动key +public class MetaAction implements Comparable, Callable { + + /** + * 行动key,用于标识与定位行动程序 + */ private String key; - //行动参数 + /** + * 行动程序可接受的参数,由调用处设置 + */ private String[] params; - //行动回应 - private String response; - //执行顺序,升序排列 + /** + * 行动结果,包括执行状态和相应内容(执行结果或者错误信息) + */ + private Result result; + /** + * 执行顺序,升序排列 + */ private int order; + /** + * 是否IO密集,用于决定使用何种线程池 + */ private boolean io; + /** + * 行动程序类型,可分为PLUGIN(jar文件)、SCRIPT(Python程序)、MCP(MCP服务) + */ + private MetaActionType type; @Override public int compareTo(@NotNull MetaAction metaAction) { return this.order - metaAction.order; } - private void execute() { + @Override + public Void call() { + File action = loadFromFile(); + if (!action.exists()) { + result = new Result(); + result.setSuccess(false); + result.setData("Action file not found: " + action.getAbsolutePath()); + return null; + } + try { + switch (type) { + case PLUGIN -> executePlugin(action); + case MCP -> executeMcp(action); + case SCRIPT -> executeScript(action); + } + } catch (Exception e) { + result = new Result(); + result.setSuccess(false); + result.setData(e.getMessage()); + } + return null; + } + + private File loadFromFile() { + return switch (type) { + case PLUGIN -> Path.of(Constant.Path.ACTION_PROGRAM, key, "action.jar").toFile(); + case SCRIPT -> Path.of(ACTION_PROGRAM, key, "action.py").toFile(); + case MCP -> Path.of(ACTION_PROGRAM, key, "action.json").toFile(); + }; + } + + private void executePlugin(File actionFile) { } + + private void executeMcp(File actionFile) { + + } + + private void executeScript(File actionFile) { + + } + + @Data + public static class Result { + private boolean success; + private String data; + } + } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaActionInfo.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaActionInfo.java new file mode 100644 index 00000000..c9bbd04d --- /dev/null +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaActionInfo.java @@ -0,0 +1,62 @@ +package work.slhaf.partner.core.action.entity; + +import cn.hutool.core.bean.BeanUtil; +import com.alibaba.fastjson2.JSONObject; +import lombok.Data; +import org.apache.commons.io.FileUtils; +import work.slhaf.partner.core.action.exception.ActionLoadFailedException; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +public class MetaActionInfo { + private String key; + private boolean io; + private MetaActionType type; + + private Map params; + private String description; + private List tags; + + private List preActions; + private List postActions; + /** + * 是否严格依赖前置行动的成功执行,若为true且前置行动失败则不执行该行动,后置任务多为触发式。默认即执行。 + */ + private boolean strictDependencies; + + private JSONObject responseSchema; + + public MetaActionInfo(File actionDir) { + if (actionDir.isFile()) { + throw new ActionLoadFailedException("Action directory expected, but file found: " + actionDir.getAbsolutePath()); + } + File[] files = actionDir.listFiles(); + if (files == null || files.length == 0) { + throw new ActionLoadFailedException("Action directory is empty: " + actionDir.getAbsolutePath()); + } + //加载desc.json + File desc = Path.of(actionDir.getPath(), "desc.json").toFile(); + if (!desc.exists() || desc.isDirectory()) { + throw new ActionLoadFailedException("Action desc.json not found: " + desc.getAbsolutePath()); + } + try { + String s = FileUtils.readFileToString(desc, StandardCharsets.UTF_8); + MetaActionInfo temp = JSONObject.parseObject(s, MetaActionInfo.class); + BeanUtil.copyProperties(temp, this); + } catch (Exception e) { + throw new ActionLoadFailedException("Failed to load action desc.json: " + desc.getAbsolutePath(), e); + } + //进行必要的字段校验和初始化 + if (type == null) throw new ActionLoadFailedException("Action type missing in desc.json"); + if (params == null) params = new HashMap<>(); + if (preActions == null) preActions = new ArrayList<>(); + if (postActions == null) postActions = new ArrayList<>(); + } +} diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaActionType.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaActionType.java new file mode 100644 index 00000000..a260141a --- /dev/null +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/MetaActionType.java @@ -0,0 +1,5 @@ +package work.slhaf.partner.core.action.entity; + +public enum MetaActionType { + PLUGIN, MCP, SCRIPT +} \ No newline at end of file diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ScheduledActionInfo.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ScheduledActionData.java similarity index 56% rename from Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ScheduledActionInfo.java rename to Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ScheduledActionData.java index d2f1283b..00eaaaea 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ScheduledActionInfo.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/entity/ScheduledActionData.java @@ -3,9 +3,12 @@ package work.slhaf.partner.core.action.entity; import lombok.Data; import lombok.EqualsAndHashCode; +/** + * 计划行动数据类,继承自{@link ActionData},扩展了属性{@link ScheduledActionData#type}和{@link ScheduledActionData#scheduleContent},用于标识计划类型(单次还是周期性任务)和计划内容 + */ @EqualsAndHashCode(callSuper = true) @Data -public class ScheduledActionInfo extends ActionInfo { +public class ScheduledActionData extends ActionData { private ScheduledType type; private String scheduleContent; //如果为周期,则对应cron表达式,如果为一次性,则对应为LocalDateTime字符串 diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/exception/ActionInitFailedException.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/exception/ActionInitFailedException.java new file mode 100644 index 00000000..4e3e2849 --- /dev/null +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/exception/ActionInitFailedException.java @@ -0,0 +1,13 @@ +package work.slhaf.partner.core.action.exception; + +import work.slhaf.partner.api.agent.runtime.exception.AgentLaunchFailedException; + +public class ActionInitFailedException extends AgentLaunchFailedException { + public ActionInitFailedException(String message, Throwable cause) { + super(message, cause); + } + + public ActionInitFailedException(String message) { + super(message); + } +} diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/action/exception/ActionLoadFailedException.java b/Partner-Main/src/main/java/work/slhaf/partner/core/action/exception/ActionLoadFailedException.java new file mode 100644 index 00000000..7cded2a6 --- /dev/null +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/action/exception/ActionLoadFailedException.java @@ -0,0 +1,13 @@ +package work.slhaf.partner.core.action.exception; + +import work.slhaf.partner.api.agent.runtime.exception.AgentRuntimeException; + +public class ActionLoadFailedException extends AgentRuntimeException { + public ActionLoadFailedException(String message) { + super(message); + } + + public ActionLoadFailedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/Partner-Main/src/main/java/work/slhaf/partner/core/memory/MemoryCore.java b/Partner-Main/src/main/java/work/slhaf/partner/core/memory/MemoryCore.java index 46924c05..98c6d4b6 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/core/memory/MemoryCore.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/core/memory/MemoryCore.java @@ -198,9 +198,9 @@ public class MemoryCore extends PartnerCore { //尝试更新缓存 updateCache(topicPath, memoryResult); } catch (Exception e) { - log.error("[CoordinatedManager] selectMemory error: ", e); - log.error("[CoordinatedManager] 路径: {}", topicPathStr); - log.error("[CoordinatedManager] 主题树: {}", getTopicTree()); + log.error("[{}] selectMemory error: ", getCoreKey(), e); + log.error("[{}] 路径: {}", getCoreKey(), topicPathStr); + log.error("[{}] 主题树: {}", getCoreKey(), getTopicTree()); memoryResult = new MemoryResult(); memoryResult.setRelatedMemorySliceResult(new ArrayList<>()); memoryResult.setMemorySliceResult(new CopyOnWriteArrayList<>()); @@ -211,7 +211,7 @@ public class MemoryCore extends PartnerCore { @CapabilityMethod public void updateActivatedSlices(String userId, List memorySlices) { cache.activatedSlices.put(userId, memorySlices); - log.debug("[CoordinatedManager] 已更新激活切片, userId: {}", userId); + log.debug("[{}] 已更新激活切片, userId: {}", getCoreKey(), userId); } @CapabilityMethod diff --git a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/ActionDispatcher.java b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/ActionDispatcher.java index 8b278967..d07b95f3 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/ActionDispatcher.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/ActionDispatcher.java @@ -6,9 +6,9 @@ import work.slhaf.partner.api.agent.factory.module.annotation.Init; import work.slhaf.partner.api.agent.factory.module.annotation.InjectModule; import work.slhaf.partner.common.thread.InteractionThreadPoolExecutor; import work.slhaf.partner.core.action.ActionCapability; -import work.slhaf.partner.core.action.entity.ActionInfo; -import work.slhaf.partner.core.action.entity.ImmediateActionInfo; -import work.slhaf.partner.core.action.entity.ScheduledActionInfo; +import work.slhaf.partner.core.action.entity.ActionData; +import work.slhaf.partner.core.action.entity.ImmediateActionData; +import work.slhaf.partner.core.action.entity.ScheduledActionData; import work.slhaf.partner.module.common.module.PostRunningModule; import work.slhaf.partner.module.modules.action.dispatcher.executor.ActionExecutor; import work.slhaf.partner.module.modules.action.dispatcher.scheduler.ActionScheduler; @@ -42,14 +42,14 @@ public class ActionDispatcher extends PostRunningModule { //对于将触发的PLANNING action,理想做法是将执行工具做成执行链的形式,模型的自对话流程、是否通知用户都做成与普通工具同等的通用可选能力,避免绑定固定流程 executor.execute(() -> { String userId = context.getUserId(); - List preparedActions = actionCapability.popPreparedAction(userId); + List preparedActions = actionCapability.popPreparedAction(userId); //分类成PLANNING和IMMEDIATE两类 - List scheduledActions = new ArrayList<>(); - List immediateActions = new ArrayList<>(); - for (ActionInfo preparedAction : preparedActions) { - if (preparedAction instanceof ScheduledActionInfo actionInfo) { + List scheduledActions = new ArrayList<>(); + List immediateActions = new ArrayList<>(); + for (ActionData preparedAction : preparedActions) { + if (preparedAction instanceof ScheduledActionData actionInfo) { scheduledActions.add(actionInfo); - } else if (preparedAction instanceof ImmediateActionInfo actionInfo) { + } else if (preparedAction instanceof ImmediateActionData actionInfo) { immediateActions.add(actionInfo); } } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/executor/ActionExecutor.java b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/executor/ActionExecutor.java index 435b65e7..8f97e60e 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/executor/ActionExecutor.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/executor/ActionExecutor.java @@ -4,18 +4,18 @@ import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapabili import work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule; import work.slhaf.partner.api.agent.runtime.interaction.flow.abstracts.AgentRunningSubModule; import work.slhaf.partner.core.action.ActionCapability; -import work.slhaf.partner.core.action.entity.ImmediateActionInfo; +import work.slhaf.partner.core.action.entity.ImmediateActionData; import java.util.List; @AgentSubModule -public class ActionExecutor extends AgentRunningSubModule, Void> { +public class ActionExecutor extends AgentRunningSubModule, Void> { @InjectCapability private ActionCapability actionCapability; @Override - public Void execute(List immediateActions) { + public Void execute(List immediateActions) { return null; } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/scheduler/ActionScheduler.java b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/scheduler/ActionScheduler.java index 3c7d0878..a6fc5b07 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/scheduler/ActionScheduler.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/dispatcher/scheduler/ActionScheduler.java @@ -2,14 +2,14 @@ package work.slhaf.partner.module.modules.action.dispatcher.scheduler; import work.slhaf.partner.api.agent.factory.module.annotation.AgentSubModule; import work.slhaf.partner.api.agent.runtime.interaction.flow.abstracts.AgentRunningSubModule; -import work.slhaf.partner.core.action.entity.ScheduledActionInfo; +import work.slhaf.partner.core.action.entity.ScheduledActionData; import java.util.List; @AgentSubModule -public class ActionScheduler extends AgentRunningSubModule, Void> { +public class ActionScheduler extends AgentRunningSubModule, Void> { @Override - public Void execute(List data) { + public Void execute(List data) { return null; } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/ActionPlanner.java b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/ActionPlanner.java index 2629ac05..59088261 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/ActionPlanner.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/ActionPlanner.java @@ -9,10 +9,9 @@ import work.slhaf.partner.api.chat.pojo.Message; import work.slhaf.partner.common.thread.InteractionThreadPoolExecutor; import work.slhaf.partner.common.vector.VectorClient; import work.slhaf.partner.core.action.ActionCapability; -import work.slhaf.partner.core.action.entity.ActionInfo; -import work.slhaf.partner.core.action.entity.ActionStatus; -import work.slhaf.partner.core.action.entity.ImmediateActionInfo; -import work.slhaf.partner.core.action.entity.ScheduledActionInfo; +import work.slhaf.partner.core.action.entity.ActionData; +import work.slhaf.partner.core.action.entity.ImmediateActionData; +import work.slhaf.partner.core.action.entity.ScheduledActionData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData; import work.slhaf.partner.core.cognation.CognationCapability; @@ -140,10 +139,10 @@ public class ActionPlanner extends PreRunningModule { return; } String contextUuid = context.getUuid(); - List pendingActions = actionCapability.popPendingAction(context.getUserId()); - for (ActionInfo actionInfo : pendingActions) { - if (uuids.contains(actionInfo.getUuid())) { - actionCapability.putPreparedAction(contextUuid, actionInfo); + List pendingActions = actionCapability.popPendingAction(context.getUserId()); + for (ActionData actionData : pendingActions) { + if (uuids.contains(actionData.getUuid())) { + actionCapability.putPreparedAction(contextUuid, actionData); } } } @@ -151,11 +150,11 @@ public class ActionPlanner extends PreRunningModule { private void setupActionInfo(List evaluatorResults, PartnerRunningFlowContext context) { for (EvaluatorResult evaluatorResult : evaluatorResults) { - ActionInfo actionInfo = assemblyHelper.buildMetaActionInfo(evaluatorResult); + ActionData actionData = assemblyHelper.buildMetaActionInfo(evaluatorResult); if (evaluatorResult.isNeedConfirm()) { - actionCapability.putPendingActions(context.getUserId(), actionInfo); + actionCapability.putPendingActions(context.getUserId(), actionData); } else { - actionCapability.putPreparedAction(context.getUuid(), actionInfo); + actionCapability.putPreparedAction(context.getUuid(), actionData); } } } @@ -170,31 +169,31 @@ public class ActionPlanner extends PreRunningModule { } private void setupPendingActions(HashMap map, String userId) { - List actionInfos = actionCapability.listPendingAction(userId); - if (actionInfos == null || actionInfos.isEmpty()) { + List actionData = actionCapability.listPendingAction(userId); + if (actionData == null || actionData.isEmpty()) { map.put("[待确认行动] <待确认行动信息>", "无待确认行动"); return; } - for (int i = 0; i < actionInfos.size(); i++) { - map.put("[待确认行动 " + (i + 1) + " ]", generateActionStr(actionInfos.get(i))); + for (int i = 0; i < actionData.size(); i++) { + map.put("[待确认行动 " + (i + 1) + " ]", generateActionStr(actionData.get(i))); } } private void setupPreparedActions(HashMap map, String uuid) { - List actionInfos = actionCapability.listPreparedAction(uuid); - if (actionInfos == null || actionInfos.isEmpty()) { + List actionData = actionCapability.listPreparedAction(uuid); + if (actionData == null || actionData.isEmpty()) { map.put("[预备行动] <预备行动信息>", "无预备行动"); return; } - for (int i = 0; i < actionInfos.size(); i++) { - map.put("[预备行动 " + (i + 1) + " ]", generateActionStr(actionInfos.get(i))); + for (int i = 0; i < actionData.size(); i++) { + map.put("[预备行动 " + (i + 1) + " ]", generateActionStr(actionData.get(i))); } } - private String generateActionStr(ActionInfo actionInfo) { - return "<行动倾向>" + " : " + actionInfo.getTendency() + - "<行动原因>" + " : " + actionInfo.getReason() + - "<工具描述>" + " : " + actionInfo.getDescription(); + private String generateActionStr(ActionData actionData) { + return "<行动倾向>" + " : " + actionData.getTendency() + + "<行动原因>" + " : " + actionData.getReason() + + "<工具描述>" + " : " + actionData.getDescription(); } @Override @@ -229,20 +228,20 @@ public class ActionPlanner extends PreRunningModule { return input; } - private ActionInfo buildMetaActionInfo(EvaluatorResult evaluatorResult) { + private ActionData buildMetaActionInfo(EvaluatorResult evaluatorResult) { return switch (evaluatorResult.getType()) { case PLANNING -> { - ScheduledActionInfo actionInfo = new ScheduledActionInfo(); + ScheduledActionData actionInfo = new ScheduledActionData(); actionInfo.setActionChain(evaluatorResult.getActionChain()); actionInfo.setScheduleContent(evaluatorResult.getScheduleContent()); - actionInfo.setStatus(ActionStatus.PREPARE); + actionInfo.setStatus(ActionData.ActionStatus.PREPARE); actionInfo.setUuid(UUID.randomUUID().toString()); yield actionInfo; } case IMMEDIATE -> { - ImmediateActionInfo actionInfo = new ImmediateActionInfo(); + ImmediateActionData actionInfo = new ImmediateActionData(); actionInfo.setActionChain(evaluatorResult.getActionChain()); - actionInfo.setStatus(ActionStatus.PREPARE); + actionInfo.setStatus(ActionData.ActionStatus.PREPARE); actionInfo.setUuid(UUID.randomUUID().toString()); yield actionInfo; } @@ -252,8 +251,8 @@ public class ActionPlanner extends PreRunningModule { private ConfirmerInput buildConfirmerInput(PartnerRunningFlowContext context) { ConfirmerInput confirmerInput = new ConfirmerInput(); confirmerInput.setInput(context.getInput()); - List pendingActions = actionCapability.listPendingAction(context.getUserId()); - confirmerInput.setActionInfos(pendingActions); + List pendingActions = actionCapability.listPendingAction(context.getUserId()); + confirmerInput.setActionData(pendingActions); return confirmerInput; } } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/confirmer/entity/ConfirmerInput.java b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/confirmer/entity/ConfirmerInput.java index 54f0247c..40b0b5ac 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/confirmer/entity/ConfirmerInput.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/confirmer/entity/ConfirmerInput.java @@ -2,13 +2,13 @@ package work.slhaf.partner.module.modules.action.planner.confirmer.entity; import lombok.Data; import work.slhaf.partner.api.chat.pojo.Message; -import work.slhaf.partner.core.action.entity.ActionInfo; +import work.slhaf.partner.core.action.entity.ActionData; import java.util.List; @Data public class ConfirmerInput { private String input; - private List actionInfos; + private List actionData; private List recentMessages; } diff --git a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/evaluator/entity/EvaluatorResult.java b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/evaluator/entity/EvaluatorResult.java index 1f7232b7..76289180 100644 --- a/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/evaluator/entity/EvaluatorResult.java +++ b/Partner-Main/src/main/java/work/slhaf/partner/module/modules/action/planner/evaluator/entity/EvaluatorResult.java @@ -1,7 +1,6 @@ package work.slhaf.partner.module.modules.action.planner.evaluator.entity; import lombok.Data; -import work.slhaf.partner.core.action.entity.ActionType; import work.slhaf.partner.core.action.entity.MetaAction; import java.util.List; @@ -14,4 +13,8 @@ public class EvaluatorResult { private String scheduleContent; private List actionChain; private String tendency; + + public enum ActionType { + IMMEDIATE, PLANNING + } }