mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 16:53:04 +08:00
推进 ActionDispatcher 模块、完善行动程序规范与加载逻辑
- 明确行动程序的存储形式与加载规则,分为执行程序和描述文件,前者负责逻辑,后者提供必要的描述性信息; - 将 ActionInfo 重命名为 ActionData,更新相关接口和实现,增强代码一致性和可读性; - 添加异常处理类以支持行动程序、描述信息的初始化和加载失败的场景; - 实现行动程序目录的监控功能,支持行动程序的动态加载与管理; - 明确了 ActionDispatcher 两个子模块的输入输出规范
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<T extends PartnerCore<T>> 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<T extends PartnerCore<T>> 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<T extends PartnerCore<T>> 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());
|
||||
}
|
||||
|
||||
@@ -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<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);
|
||||
|
||||
|
||||
@@ -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<ActionCore> {
|
||||
/**
|
||||
* 对应本次交互即将执行或将要放置在行动池的预备任务,因此将以本次交互的uuid为键,其起到的作用相当于暂时的模块上下文
|
||||
*/
|
||||
private HashMap<String, List<ActionInfo>> preparedActions = new HashMap<>();
|
||||
private HashMap<String, List<ActionData>> preparedActions = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 待确认任务,以userId区分不同用户,因为需要跨请求确认
|
||||
*/
|
||||
private HashMap<String, List<ActionInfo>> pendingActions = new HashMap<>();
|
||||
private HashMap<String, List<ActionData>> pendingActions = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 语义缓存与行为倾向映射
|
||||
*/
|
||||
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 {
|
||||
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<ActionInfo> temp = new ArrayList<>();
|
||||
temp.add(actionInfo);
|
||||
List<ActionData> temp = new ArrayList<>();
|
||||
temp.add(actionData);
|
||||
return temp;
|
||||
});
|
||||
}
|
||||
|
||||
@CapabilityMethod
|
||||
public synchronized List<ActionInfo> popPendingAction(String userId) {
|
||||
List<ActionInfo> infos = pendingActions.get(userId);
|
||||
public synchronized List<ActionData> popPendingAction(String userId) {
|
||||
List<ActionData> 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<ActionInfo> temp = new ArrayList<>();
|
||||
temp.add(actionInfo);
|
||||
List<ActionData> temp = new ArrayList<>();
|
||||
temp.add(actionData);
|
||||
return temp;
|
||||
});
|
||||
}
|
||||
|
||||
@CapabilityMethod
|
||||
public synchronized List<ActionInfo> popPreparedAction(String userId) {
|
||||
List<ActionInfo> infos = preparedActions.get(userId);
|
||||
public synchronized List<ActionData> popPreparedAction(String userId) {
|
||||
List<ActionData> infos = preparedActions.get(userId);
|
||||
preparedActions.remove(userId);
|
||||
return infos;
|
||||
}
|
||||
|
||||
@CapabilityMethod
|
||||
public List<ActionInfo> listPreparedAction(String userId) {
|
||||
public List<ActionData> listPreparedAction(String userId) {
|
||||
return preparedActions.get(userId);
|
||||
}
|
||||
|
||||
@CapabilityMethod
|
||||
public List<ActionInfo> listPendingAction(String userId) {
|
||||
public List<ActionData> listPendingAction(String userId) {
|
||||
return pendingActions.get(userId);
|
||||
}
|
||||
|
||||
@@ -136,9 +150,9 @@ public class ActionCore extends PartnerCore<ActionCore> {
|
||||
}
|
||||
}
|
||||
|
||||
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<ActionCore> {
|
||||
* @param inputVector 本次输入内容的语义向量
|
||||
* @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 -> {
|
||||
//获取原始缓存条目
|
||||
String tendency = adjustData.getTendency();
|
||||
@@ -204,7 +219,8 @@ public class ActionCore extends PartnerCore<ActionCore> {
|
||||
* @param input 本次输入内容
|
||||
* @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 -> {
|
||||
//获取原始缓存条目
|
||||
String tendency = adjustData.getTendency();
|
||||
@@ -232,4 +248,169 @@ public class ActionCore extends PartnerCore<ActionCore> {
|
||||
protected String getCoreKey() {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package work.slhaf.partner.core.action.entity;
|
||||
|
||||
public enum ActionStatus {
|
||||
SUCCESS, FAILED, EXECUTING, WAITING, PREPARE
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package work.slhaf.partner.core.action.entity;
|
||||
|
||||
public enum ActionType {
|
||||
IMMEDIATE, PLANNING
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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<MetaAction> {
|
||||
//行动key
|
||||
public class MetaAction implements Comparable<MetaAction>, Callable<Void> {
|
||||
|
||||
/**
|
||||
* 行动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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package work.slhaf.partner.core.action.entity;
|
||||
|
||||
public enum MetaActionType {
|
||||
PLUGIN, MCP, SCRIPT
|
||||
}
|
||||
@@ -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字符串
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -198,9 +198,9 @@ public class MemoryCore extends PartnerCore<MemoryCore> {
|
||||
//尝试更新缓存
|
||||
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<MemoryCore> {
|
||||
@CapabilityMethod
|
||||
public void updateActivatedSlices(String userId, List<EvaluatedSlice> memorySlices) {
|
||||
cache.activatedSlices.put(userId, memorySlices);
|
||||
log.debug("[CoordinatedManager] 已更新激活切片, userId: {}", userId);
|
||||
log.debug("[{}] 已更新激活切片, userId: {}", getCoreKey(), userId);
|
||||
}
|
||||
|
||||
@CapabilityMethod
|
||||
|
||||
@@ -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<ActionInfo> preparedActions = actionCapability.popPreparedAction(userId);
|
||||
List<ActionData> preparedActions = actionCapability.popPreparedAction(userId);
|
||||
//分类成PLANNING和IMMEDIATE两类
|
||||
List<ScheduledActionInfo> scheduledActions = new ArrayList<>();
|
||||
List<ImmediateActionInfo> immediateActions = new ArrayList<>();
|
||||
for (ActionInfo preparedAction : preparedActions) {
|
||||
if (preparedAction instanceof ScheduledActionInfo actionInfo) {
|
||||
List<ScheduledActionData> scheduledActions = new ArrayList<>();
|
||||
List<ImmediateActionData> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<ImmediateActionInfo>, Void> {
|
||||
public class ActionExecutor extends AgentRunningSubModule<List<ImmediateActionData>, Void> {
|
||||
|
||||
@InjectCapability
|
||||
private ActionCapability actionCapability;
|
||||
|
||||
@Override
|
||||
public Void execute(List<ImmediateActionInfo> immediateActions) {
|
||||
public Void execute(List<ImmediateActionData> immediateActions) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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<List<ScheduledActionInfo>, Void> {
|
||||
public class ActionScheduler extends AgentRunningSubModule<List<ScheduledActionData>, Void> {
|
||||
@Override
|
||||
public Void execute(List<ScheduledActionInfo> data) {
|
||||
public Void execute(List<ScheduledActionData> data) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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<ActionInfo> pendingActions = actionCapability.popPendingAction(context.getUserId());
|
||||
for (ActionInfo actionInfo : pendingActions) {
|
||||
if (uuids.contains(actionInfo.getUuid())) {
|
||||
actionCapability.putPreparedAction(contextUuid, actionInfo);
|
||||
List<ActionData> 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<EvaluatorResult> 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<String, String> map, String userId) {
|
||||
List<ActionInfo> actionInfos = actionCapability.listPendingAction(userId);
|
||||
if (actionInfos == null || actionInfos.isEmpty()) {
|
||||
List<ActionData> 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<String, String> map, String uuid) {
|
||||
List<ActionInfo> actionInfos = actionCapability.listPreparedAction(uuid);
|
||||
if (actionInfos == null || actionInfos.isEmpty()) {
|
||||
List<ActionData> 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<ActionInfo> pendingActions = actionCapability.listPendingAction(context.getUserId());
|
||||
confirmerInput.setActionInfos(pendingActions);
|
||||
List<ActionData> pendingActions = actionCapability.listPendingAction(context.getUserId());
|
||||
confirmerInput.setActionData(pendingActions);
|
||||
return confirmerInput;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ActionInfo> actionInfos;
|
||||
private List<ActionData> actionData;
|
||||
private List<Message> recentMessages;
|
||||
}
|
||||
|
||||
@@ -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<MetaAction> actionChain;
|
||||
private String tendency;
|
||||
|
||||
public enum ActionType {
|
||||
IMMEDIATE, PLANNING
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user