推进 ActionDispatcher 模块、完善行动程序规范与加载逻辑

- 明确行动程序的存储形式与加载规则,分为执行程序和描述文件,前者负责逻辑,后者提供必要的描述性信息;
- 将 ActionInfo 重命名为 ActionData,更新相关接口和实现,增强代码一致性和可读性;
- 添加异常处理类以支持行动程序、描述信息的初始化和加载失败的场景;
- 实现行动程序目录的监控功能,支持行动程序的动态加载与管理;
- 明确了 ActionDispatcher 两个子模块的输入输出规范
This commit is contained in:
2025-10-23 23:24:30 +08:00
parent 21b3a0e846
commit 4757425a15
24 changed files with 487 additions and 106 deletions

6
.idea/copilot.data.migration.agent.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

6
.idea/copilot.data.migration.ask.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AskMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

6
.idea/copilot.data.migration.edit.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EditMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@@ -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";
}
}

View File

@@ -13,10 +13,11 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import static work.slhaf.partner.common.Constant.Path.MEMORY_DATA;
@Slf4j @Slf4j
public abstract class PartnerCore<T extends PartnerCore<T>> extends PersistableObject { public abstract class PartnerCore<T extends PartnerCore<T>> extends PersistableObject {
private static final String STORAGE_DIR = "./data/memory/";
private final String id = ((PartnerAgentConfigManager) AgentConfigManager.INSTANCE).getConfig().getAgentId(); private final String id = ((PartnerAgentConfigManager) AgentConfigManager.INSTANCE).getConfig().getAgentId();
public PartnerCore() throws IOException, ClassNotFoundException { public PartnerCore() throws IOException, ClassNotFoundException {
@@ -53,7 +54,7 @@ public abstract class PartnerCore<T extends PartnerCore<T>> extends PersistableO
public void serialize() throws IOException { public void serialize() throws IOException {
//先写入到临时文件,如果正常写入则覆盖原文件 //先写入到临时文件,如果正常写入则覆盖原文件
Path filePath = getFilePath(id + "-temp"); Path filePath = getFilePath(id + "-temp");
Files.createDirectories(Path.of(STORAGE_DIR)); Files.createDirectories(Path.of(MEMORY_DATA));
try { try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile())); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath.toFile()));
oos.writeObject(this); oos.writeObject(this);
@@ -78,12 +79,12 @@ public abstract class PartnerCore<T extends PartnerCore<T>> extends PersistableO
} }
private Path getFilePath(String s) { private Path getFilePath(String s) {
return Paths.get(STORAGE_DIR, s + "-" + getCoreKey() + ".memory"); return Paths.get(MEMORY_DATA, s + "-" + getCoreKey() + ".memory");
} }
private void createStorageDirectory() { private void createStorageDirectory() {
try { try {
Files.createDirectories(Paths.get(STORAGE_DIR)); Files.createDirectories(Paths.get(MEMORY_DATA));
} catch (IOException e) { } catch (IOException e) {
log.error("[{}]创建存储目录失败: {}", getCoreKey(), e.getMessage()); log.error("[{}]创建存储目录失败: {}", getCoreKey(), e.getMessage());
} }

View File

@@ -1,24 +1,24 @@
package work.slhaf.partner.core.action; package work.slhaf.partner.core.action;
import work.slhaf.partner.api.agent.factory.capability.annotation.Capability; 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 work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
import java.util.List; import java.util.List;
@Capability(value = "action") @Capability(value = "action")
public interface ActionCapability { public interface ActionCapability {
void putPreparedAction(String uuid, ActionInfo actionInfo); void putPreparedAction(String uuid, ActionData actionData);
List<ActionInfo> popPreparedAction(String userId); List<ActionData> popPreparedAction(String userId);
List<ActionInfo> popPendingAction(String userId); List<ActionData> popPendingAction(String userId);
List<ActionInfo> listPreparedAction(String userId); List<ActionData> listPreparedAction(String userId);
List<ActionInfo> listPendingAction(String userId); List<ActionData> listPendingAction(String userId);
void putPendingActions(String userId, ActionInfo actionInfo); void putPendingActions(String userId, ActionData actionData);
List<String> selectTendencyCache(String input); List<String> selectTendencyCache(String input);

View File

@@ -1,25 +1,35 @@
package work.slhaf.partner.core.action; package work.slhaf.partner.core.action;
import lombok.extern.slf4j.Slf4j; 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.CapabilityCore;
import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod; import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod;
import work.slhaf.partner.common.vector.VectorClient; import work.slhaf.partner.common.vector.VectorClient;
import work.slhaf.partner.core.PartnerCore; 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.ActionCacheData;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData; 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.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static work.slhaf.partner.common.Constant.Path.ACTION_PROGRAM;
@SuppressWarnings("FieldMayBeFinal") @SuppressWarnings("FieldMayBeFinal")
@CapabilityCore(value = "action") @CapabilityCore(value = "action")
@Slf4j @Slf4j
@@ -28,64 +38,68 @@ public class ActionCore extends PartnerCore<ActionCore> {
/** /**
* 对应本次交互即将执行或将要放置在行动池的预备任务因此将以本次交互的uuid为键其起到的作用相当于暂时的模块上下文 * 对应本次交互即将执行或将要放置在行动池的预备任务因此将以本次交互的uuid为键其起到的作用相当于暂时的模块上下文
*/ */
private HashMap<String, List<ActionInfo>> preparedActions = new HashMap<>(); private HashMap<String, List<ActionData>> preparedActions = new HashMap<>();
/** /**
* 待确认任务以userId区分不同用户因为需要跨请求确认 * 待确认任务以userId区分不同用户因为需要跨请求确认
*/ */
private HashMap<String, List<ActionInfo>> pendingActions = new HashMap<>(); private HashMap<String, List<ActionData>> pendingActions = new HashMap<>();
/** /**
* 语义缓存与行为倾向映射 * 语义缓存与行为倾向映射
*/ */
private List<ActionCacheData> actionCache = new ArrayList<>(); private List<ActionCacheData> 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<String, MetaActionInfo> existedMetaActions = new LinkedHashMap<>();
public ActionCore() throws IOException, ClassNotFoundException { public ActionCore() throws IOException, ClassNotFoundException {
new ActionWatchService().launch();
} }
@CapabilityMethod @CapabilityMethod
public synchronized void putPendingActions(String userId, ActionInfo actionInfo) { public synchronized void putPendingActions(String userId, ActionData actionData) {
pendingActions.computeIfAbsent(userId, k -> { pendingActions.computeIfAbsent(userId, k -> {
List<ActionInfo> temp = new ArrayList<>(); List<ActionData> temp = new ArrayList<>();
temp.add(actionInfo); temp.add(actionData);
return temp; return temp;
}); });
} }
@CapabilityMethod @CapabilityMethod
public synchronized List<ActionInfo> popPendingAction(String userId) { public synchronized List<ActionData> popPendingAction(String userId) {
List<ActionInfo> infos = pendingActions.get(userId); List<ActionData> infos = pendingActions.get(userId);
pendingActions.remove(userId); pendingActions.remove(userId);
return infos; return infos;
} }
@CapabilityMethod @CapabilityMethod
public synchronized void putPreparedAction(String uuid, ActionInfo actionInfo) { public synchronized void putPreparedAction(String uuid, ActionData actionData) {
preparedActions.computeIfAbsent(uuid, k -> { preparedActions.computeIfAbsent(uuid, k -> {
List<ActionInfo> temp = new ArrayList<>(); List<ActionData> temp = new ArrayList<>();
temp.add(actionInfo); temp.add(actionData);
return temp; return temp;
}); });
} }
@CapabilityMethod @CapabilityMethod
public synchronized List<ActionInfo> popPreparedAction(String userId) { public synchronized List<ActionData> popPreparedAction(String userId) {
List<ActionInfo> infos = preparedActions.get(userId); List<ActionData> infos = preparedActions.get(userId);
preparedActions.remove(userId); preparedActions.remove(userId);
return infos; return infos;
} }
@CapabilityMethod @CapabilityMethod
public List<ActionInfo> listPreparedAction(String userId) { public List<ActionData> listPreparedAction(String userId) {
return preparedActions.get(userId); return preparedActions.get(userId);
} }
@CapabilityMethod @CapabilityMethod
public List<ActionInfo> listPendingAction(String userId) { public List<ActionData> listPendingAction(String userId) {
return pendingActions.get(userId); return pendingActions.get(userId);
} }
@@ -136,9 +150,9 @@ public class ActionCore extends PartnerCore<ActionCore> {
} }
} }
executor.execute(() -> adjustMatchAndPassed(matchAndPassed, inputVector, input, vectorClient)); platformExecutor.execute(() -> adjustMatchAndPassed(matchAndPassed, inputVector, input, vectorClient));
executor.execute(() -> adjustMatchNotPassed(matchNotPassed, vectorClient)); platformExecutor.execute(() -> adjustMatchNotPassed(matchNotPassed, vectorClient));
executor.execute(() -> adjustNotMatchPassed(notMatchPassed, inputVector, input, vectorClient)); platformExecutor.execute(() -> adjustNotMatchPassed(notMatchPassed, inputVector, input, vectorClient));
} }
/** /**
@@ -148,7 +162,8 @@ public class ActionCore extends PartnerCore<ActionCore> {
* @param inputVector 本次输入内容的语义向量 * @param inputVector 本次输入内容的语义向量
* @param vectorClient 向量客户端 * @param vectorClient 向量客户端
*/ */
private void adjustMatchAndPassed(List<CacheAdjustMetaData> matchAndPassed, float[] inputVector, String input, VectorClient vectorClient) { private void adjustMatchAndPassed(List<CacheAdjustMetaData> matchAndPassed, float[] inputVector, String
input, VectorClient vectorClient) {
matchAndPassed.forEach(adjustData -> { matchAndPassed.forEach(adjustData -> {
//获取原始缓存条目 //获取原始缓存条目
String tendency = adjustData.getTendency(); String tendency = adjustData.getTendency();
@@ -204,7 +219,8 @@ public class ActionCore extends PartnerCore<ActionCore> {
* @param input 本次输入内容 * @param input 本次输入内容
* @param vectorClient 向量客户端 * @param vectorClient 向量客户端
*/ */
private void adjustNotMatchPassed(List<CacheAdjustMetaData> notMatchPassed, float[] inputVector, String input, VectorClient vectorClient) { private void adjustNotMatchPassed(List<CacheAdjustMetaData> notMatchPassed, float[] inputVector, String
input, VectorClient vectorClient) {
notMatchPassed.forEach(adjustData -> { notMatchPassed.forEach(adjustData -> {
//获取原始缓存条目 //获取原始缓存条目
String tendency = adjustData.getTendency(); String tendency = adjustData.getTendency();
@@ -232,4 +248,169 @@ public class ActionCore extends PartnerCore<ActionCore> {
protected String getCoreKey() { protected String getCoreKey() {
return "action-core"; return "action-core";
} }
@SuppressWarnings("unchecked")
private class ActionWatchService {
private HashMap<Path, WatchKey> 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<WatchEvent<?>> events = key.pollEvents();
for (WatchEvent<?> e : events) {
WatchEvent<Path> event = (WatchEvent<Path>) e;
WatchEvent.Kind<Path> 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<Path> 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<Path> 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());
}
}
}
}
} }

View File

@@ -4,8 +4,11 @@ import lombok.Data;
import java.util.List; import java.util.List;
/**
* 行动模块传递的行动数据包含行动uuid倾向状态行动链结果发起原因行动描述等信息
*/
@Data @Data
public abstract class ActionInfo { public abstract class ActionData {
protected String uuid; protected String uuid;
protected String tendency; protected String tendency;
protected ActionStatus status; protected ActionStatus status;
@@ -13,4 +16,8 @@ public abstract class ActionInfo {
protected String Result; protected String Result;
protected String reason; protected String reason;
protected String description; protected String description;
public enum ActionStatus {
SUCCESS, FAILED, EXECUTING, WAITING, PREPARE
}
} }

View File

@@ -1,5 +0,0 @@
package work.slhaf.partner.core.action.entity;
public enum ActionStatus {
SUCCESS, FAILED, EXECUTING, WAITING, PREPARE
}

View File

@@ -1,5 +0,0 @@
package work.slhaf.partner.core.action.entity;
public enum ActionType {
IMMEDIATE, PLANNING
}

View File

@@ -3,7 +3,10 @@ package work.slhaf.partner.core.action.entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
/**
* 即时行动数据类继承自{@link ActionData}
*/
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
public class ImmediateActionInfo extends ActionInfo { public class ImmediateActionData extends ActionData {
} }

View File

@@ -2,25 +2,97 @@ package work.slhaf.partner.core.action.entity;
import lombok.Data; import lombok.Data;
import org.jetbrains.annotations.NotNull; 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 @Data
public class MetaAction implements Comparable<MetaAction> { public class MetaAction implements Comparable<MetaAction>, Callable<Void> {
//行动key
/**
* 行动key用于标识与定位行动程序
*/
private String key; private String key;
//行动参数 /**
* 行动程序可接受的参数,由调用处设置
*/
private String[] params; private String[] params;
//行动回应 /**
private String response; * 行动结果,包括执行状态和相应内容(执行结果或者错误信息)
//执行顺序,升序排列 */
private Result result;
/**
* 执行顺序,升序排列
*/
private int order; private int order;
/**
* 是否IO密集用于决定使用何种线程池
*/
private boolean io; private boolean io;
/**
* 行动程序类型可分为PLUGIN(jar文件)、SCRIPT(Python程序)、MCP(MCP服务)
*/
private MetaActionType type;
@Override @Override
public int compareTo(@NotNull MetaAction metaAction) { public int compareTo(@NotNull MetaAction metaAction) {
return this.order - metaAction.order; 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;
}
} }

View File

@@ -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<String, String> params;
private String description;
private List<String> tags;
private List<String> preActions;
private List<String> 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<>();
}
}

View File

@@ -0,0 +1,5 @@
package work.slhaf.partner.core.action.entity;
public enum MetaActionType {
PLUGIN, MCP, SCRIPT
}

View File

@@ -3,9 +3,12 @@ package work.slhaf.partner.core.action.entity;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
/**
* 计划行动数据类继承自{@link ActionData}扩展了属性{@link ScheduledActionData#type}{@link ScheduledActionData#scheduleContent}用于标识计划类型(单次还是周期性任务)和计划内容
*/
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
public class ScheduledActionInfo extends ActionInfo { public class ScheduledActionData extends ActionData {
private ScheduledType type; private ScheduledType type;
private String scheduleContent; //如果为周期则对应cron表达式如果为一次性则对应为LocalDateTime字符串 private String scheduleContent; //如果为周期则对应cron表达式如果为一次性则对应为LocalDateTime字符串

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -198,9 +198,9 @@ public class MemoryCore extends PartnerCore<MemoryCore> {
//尝试更新缓存 //尝试更新缓存
updateCache(topicPath, memoryResult); updateCache(topicPath, memoryResult);
} catch (Exception e) { } catch (Exception e) {
log.error("[CoordinatedManager] selectMemory error: ", e); log.error("[{}] selectMemory error: ", getCoreKey(), e);
log.error("[CoordinatedManager] 路径: {}", topicPathStr); log.error("[{}] 路径: {}", getCoreKey(), topicPathStr);
log.error("[CoordinatedManager] 主题树: {}", getTopicTree()); log.error("[{}] 主题树: {}", getCoreKey(), getTopicTree());
memoryResult = new MemoryResult(); memoryResult = new MemoryResult();
memoryResult.setRelatedMemorySliceResult(new ArrayList<>()); memoryResult.setRelatedMemorySliceResult(new ArrayList<>());
memoryResult.setMemorySliceResult(new CopyOnWriteArrayList<>()); memoryResult.setMemorySliceResult(new CopyOnWriteArrayList<>());
@@ -211,7 +211,7 @@ public class MemoryCore extends PartnerCore<MemoryCore> {
@CapabilityMethod @CapabilityMethod
public void updateActivatedSlices(String userId, List<EvaluatedSlice> memorySlices) { public void updateActivatedSlices(String userId, List<EvaluatedSlice> memorySlices) {
cache.activatedSlices.put(userId, memorySlices); cache.activatedSlices.put(userId, memorySlices);
log.debug("[CoordinatedManager] 已更新激活切片, userId: {}", userId); log.debug("[{}] 已更新激活切片, userId: {}", getCoreKey(), userId);
} }
@CapabilityMethod @CapabilityMethod

View File

@@ -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.api.agent.factory.module.annotation.InjectModule;
import work.slhaf.partner.common.thread.InteractionThreadPoolExecutor; import work.slhaf.partner.common.thread.InteractionThreadPoolExecutor;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.entity.ActionInfo; import work.slhaf.partner.core.action.entity.ActionData;
import work.slhaf.partner.core.action.entity.ImmediateActionInfo; import work.slhaf.partner.core.action.entity.ImmediateActionData;
import work.slhaf.partner.core.action.entity.ScheduledActionInfo; import work.slhaf.partner.core.action.entity.ScheduledActionData;
import work.slhaf.partner.module.common.module.PostRunningModule; 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.executor.ActionExecutor;
import work.slhaf.partner.module.modules.action.dispatcher.scheduler.ActionScheduler; import work.slhaf.partner.module.modules.action.dispatcher.scheduler.ActionScheduler;
@@ -42,14 +42,14 @@ public class ActionDispatcher extends PostRunningModule {
//对于将触发的PLANNING action理想做法是将执行工具做成执行链的形式模型的自对话流程、是否通知用户都做成与普通工具同等的通用可选能力避免绑定固定流程 //对于将触发的PLANNING action理想做法是将执行工具做成执行链的形式模型的自对话流程、是否通知用户都做成与普通工具同等的通用可选能力避免绑定固定流程
executor.execute(() -> { executor.execute(() -> {
String userId = context.getUserId(); String userId = context.getUserId();
List<ActionInfo> preparedActions = actionCapability.popPreparedAction(userId); List<ActionData> preparedActions = actionCapability.popPreparedAction(userId);
//分类成PLANNING和IMMEDIATE两类 //分类成PLANNING和IMMEDIATE两类
List<ScheduledActionInfo> scheduledActions = new ArrayList<>(); List<ScheduledActionData> scheduledActions = new ArrayList<>();
List<ImmediateActionInfo> immediateActions = new ArrayList<>(); List<ImmediateActionData> immediateActions = new ArrayList<>();
for (ActionInfo preparedAction : preparedActions) { for (ActionData preparedAction : preparedActions) {
if (preparedAction instanceof ScheduledActionInfo actionInfo) { if (preparedAction instanceof ScheduledActionData actionInfo) {
scheduledActions.add(actionInfo); scheduledActions.add(actionInfo);
} else if (preparedAction instanceof ImmediateActionInfo actionInfo) { } else if (preparedAction instanceof ImmediateActionData actionInfo) {
immediateActions.add(actionInfo); immediateActions.add(actionInfo);
} }
} }

View File

@@ -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.factory.module.annotation.AgentSubModule;
import work.slhaf.partner.api.agent.runtime.interaction.flow.abstracts.AgentRunningSubModule; import work.slhaf.partner.api.agent.runtime.interaction.flow.abstracts.AgentRunningSubModule;
import work.slhaf.partner.core.action.ActionCapability; 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; import java.util.List;
@AgentSubModule @AgentSubModule
public class ActionExecutor extends AgentRunningSubModule<List<ImmediateActionInfo>, Void> { public class ActionExecutor extends AgentRunningSubModule<List<ImmediateActionData>, Void> {
@InjectCapability @InjectCapability
private ActionCapability actionCapability; private ActionCapability actionCapability;
@Override @Override
public Void execute(List<ImmediateActionInfo> immediateActions) { public Void execute(List<ImmediateActionData> immediateActions) {
return null; return null;
} }

View File

@@ -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.factory.module.annotation.AgentSubModule;
import work.slhaf.partner.api.agent.runtime.interaction.flow.abstracts.AgentRunningSubModule; 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; import java.util.List;
@AgentSubModule @AgentSubModule
public class ActionScheduler extends AgentRunningSubModule<List<ScheduledActionInfo>, Void> { public class ActionScheduler extends AgentRunningSubModule<List<ScheduledActionData>, Void> {
@Override @Override
public Void execute(List<ScheduledActionInfo> data) { public Void execute(List<ScheduledActionData> data) {
return null; return null;
} }

View File

@@ -9,10 +9,9 @@ import work.slhaf.partner.api.chat.pojo.Message;
import work.slhaf.partner.common.thread.InteractionThreadPoolExecutor; import work.slhaf.partner.common.thread.InteractionThreadPoolExecutor;
import work.slhaf.partner.common.vector.VectorClient; import work.slhaf.partner.common.vector.VectorClient;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.entity.ActionInfo; import work.slhaf.partner.core.action.entity.ActionData;
import work.slhaf.partner.core.action.entity.ActionStatus; import work.slhaf.partner.core.action.entity.ImmediateActionData;
import work.slhaf.partner.core.action.entity.ImmediateActionInfo; import work.slhaf.partner.core.action.entity.ScheduledActionData;
import work.slhaf.partner.core.action.entity.ScheduledActionInfo;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData;
import work.slhaf.partner.core.cognation.CognationCapability; import work.slhaf.partner.core.cognation.CognationCapability;
@@ -140,10 +139,10 @@ public class ActionPlanner extends PreRunningModule {
return; return;
} }
String contextUuid = context.getUuid(); String contextUuid = context.getUuid();
List<ActionInfo> pendingActions = actionCapability.popPendingAction(context.getUserId()); List<ActionData> pendingActions = actionCapability.popPendingAction(context.getUserId());
for (ActionInfo actionInfo : pendingActions) { for (ActionData actionData : pendingActions) {
if (uuids.contains(actionInfo.getUuid())) { if (uuids.contains(actionData.getUuid())) {
actionCapability.putPreparedAction(contextUuid, actionInfo); actionCapability.putPreparedAction(contextUuid, actionData);
} }
} }
} }
@@ -151,11 +150,11 @@ public class ActionPlanner extends PreRunningModule {
private void setupActionInfo(List<EvaluatorResult> evaluatorResults, PartnerRunningFlowContext context) { private void setupActionInfo(List<EvaluatorResult> evaluatorResults, PartnerRunningFlowContext context) {
for (EvaluatorResult evaluatorResult : evaluatorResults) { for (EvaluatorResult evaluatorResult : evaluatorResults) {
ActionInfo actionInfo = assemblyHelper.buildMetaActionInfo(evaluatorResult); ActionData actionData = assemblyHelper.buildMetaActionInfo(evaluatorResult);
if (evaluatorResult.isNeedConfirm()) { if (evaluatorResult.isNeedConfirm()) {
actionCapability.putPendingActions(context.getUserId(), actionInfo); actionCapability.putPendingActions(context.getUserId(), actionData);
} else { } else {
actionCapability.putPreparedAction(context.getUuid(), actionInfo); actionCapability.putPreparedAction(context.getUuid(), actionData);
} }
} }
} }
@@ -170,31 +169,31 @@ public class ActionPlanner extends PreRunningModule {
} }
private void setupPendingActions(HashMap<String, String> map, String userId) { private void setupPendingActions(HashMap<String, String> map, String userId) {
List<ActionInfo> actionInfos = actionCapability.listPendingAction(userId); List<ActionData> actionData = actionCapability.listPendingAction(userId);
if (actionInfos == null || actionInfos.isEmpty()) { if (actionData == null || actionData.isEmpty()) {
map.put("[待确认行动] <待确认行动信息>", "无待确认行动"); map.put("[待确认行动] <待确认行动信息>", "无待确认行动");
return; return;
} }
for (int i = 0; i < actionInfos.size(); i++) { for (int i = 0; i < actionData.size(); i++) {
map.put("[待确认行动 " + (i + 1) + " ]", generateActionStr(actionInfos.get(i))); map.put("[待确认行动 " + (i + 1) + " ]", generateActionStr(actionData.get(i)));
} }
} }
private void setupPreparedActions(HashMap<String, String> map, String uuid) { private void setupPreparedActions(HashMap<String, String> map, String uuid) {
List<ActionInfo> actionInfos = actionCapability.listPreparedAction(uuid); List<ActionData> actionData = actionCapability.listPreparedAction(uuid);
if (actionInfos == null || actionInfos.isEmpty()) { if (actionData == null || actionData.isEmpty()) {
map.put("[预备行动] <预备行动信息>", "无预备行动"); map.put("[预备行动] <预备行动信息>", "无预备行动");
return; return;
} }
for (int i = 0; i < actionInfos.size(); i++) { for (int i = 0; i < actionData.size(); i++) {
map.put("[预备行动 " + (i + 1) + " ]", generateActionStr(actionInfos.get(i))); map.put("[预备行动 " + (i + 1) + " ]", generateActionStr(actionData.get(i)));
} }
} }
private String generateActionStr(ActionInfo actionInfo) { private String generateActionStr(ActionData actionData) {
return "<行动倾向>" + " : " + actionInfo.getTendency() + return "<行动倾向>" + " : " + actionData.getTendency() +
"<行动原因>" + " : " + actionInfo.getReason() + "<行动原因>" + " : " + actionData.getReason() +
"<工具描述>" + " : " + actionInfo.getDescription(); "<工具描述>" + " : " + actionData.getDescription();
} }
@Override @Override
@@ -229,20 +228,20 @@ public class ActionPlanner extends PreRunningModule {
return input; return input;
} }
private ActionInfo buildMetaActionInfo(EvaluatorResult evaluatorResult) { private ActionData buildMetaActionInfo(EvaluatorResult evaluatorResult) {
return switch (evaluatorResult.getType()) { return switch (evaluatorResult.getType()) {
case PLANNING -> { case PLANNING -> {
ScheduledActionInfo actionInfo = new ScheduledActionInfo(); ScheduledActionData actionInfo = new ScheduledActionData();
actionInfo.setActionChain(evaluatorResult.getActionChain()); actionInfo.setActionChain(evaluatorResult.getActionChain());
actionInfo.setScheduleContent(evaluatorResult.getScheduleContent()); actionInfo.setScheduleContent(evaluatorResult.getScheduleContent());
actionInfo.setStatus(ActionStatus.PREPARE); actionInfo.setStatus(ActionData.ActionStatus.PREPARE);
actionInfo.setUuid(UUID.randomUUID().toString()); actionInfo.setUuid(UUID.randomUUID().toString());
yield actionInfo; yield actionInfo;
} }
case IMMEDIATE -> { case IMMEDIATE -> {
ImmediateActionInfo actionInfo = new ImmediateActionInfo(); ImmediateActionData actionInfo = new ImmediateActionData();
actionInfo.setActionChain(evaluatorResult.getActionChain()); actionInfo.setActionChain(evaluatorResult.getActionChain());
actionInfo.setStatus(ActionStatus.PREPARE); actionInfo.setStatus(ActionData.ActionStatus.PREPARE);
actionInfo.setUuid(UUID.randomUUID().toString()); actionInfo.setUuid(UUID.randomUUID().toString());
yield actionInfo; yield actionInfo;
} }
@@ -252,8 +251,8 @@ public class ActionPlanner extends PreRunningModule {
private ConfirmerInput buildConfirmerInput(PartnerRunningFlowContext context) { private ConfirmerInput buildConfirmerInput(PartnerRunningFlowContext context) {
ConfirmerInput confirmerInput = new ConfirmerInput(); ConfirmerInput confirmerInput = new ConfirmerInput();
confirmerInput.setInput(context.getInput()); confirmerInput.setInput(context.getInput());
List<ActionInfo> pendingActions = actionCapability.listPendingAction(context.getUserId()); List<ActionData> pendingActions = actionCapability.listPendingAction(context.getUserId());
confirmerInput.setActionInfos(pendingActions); confirmerInput.setActionData(pendingActions);
return confirmerInput; return confirmerInput;
} }
} }

View File

@@ -2,13 +2,13 @@ package work.slhaf.partner.module.modules.action.planner.confirmer.entity;
import lombok.Data; import lombok.Data;
import work.slhaf.partner.api.chat.pojo.Message; 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; import java.util.List;
@Data @Data
public class ConfirmerInput { public class ConfirmerInput {
private String input; private String input;
private List<ActionInfo> actionInfos; private List<ActionData> actionData;
private List<Message> recentMessages; private List<Message> recentMessages;
} }

View File

@@ -1,7 +1,6 @@
package work.slhaf.partner.module.modules.action.planner.evaluator.entity; package work.slhaf.partner.module.modules.action.planner.evaluator.entity;
import lombok.Data; import lombok.Data;
import work.slhaf.partner.core.action.entity.ActionType;
import work.slhaf.partner.core.action.entity.MetaAction; import work.slhaf.partner.core.action.entity.MetaAction;
import java.util.List; import java.util.List;
@@ -14,4 +13,8 @@ public class EvaluatorResult {
private String scheduleContent; private String scheduleContent;
private List<MetaAction> actionChain; private List<MetaAction> actionChain;
private String tendency; private String tendency;
public enum ActionType {
IMMEDIATE, PLANNING
}
} }