refactor(action): pass launcher through meta action flow instead of inferring command by file extension

This commit is contained in:
2026-03-14 21:27:38 +08:00
parent 603b0835c5
commit cba9ff4f0b
10 changed files with 62 additions and 57 deletions

View File

@@ -329,7 +329,8 @@ public class ActionCore extends PartnerCore<ActionCore> {
MetaAction.Type type = BUILTIN_LOCATION.equals(split[0]) ? MetaAction.Type.BUILTIN : MetaAction.Type.MCP; MetaAction.Type type = BUILTIN_LOCATION.equals(split[0]) ? MetaAction.Type.BUILTIN : MetaAction.Type.MCP;
return new MetaAction( return new MetaAction(
split[1], split[1],
metaActionInfo.isIo(), metaActionInfo.getIo(),
metaActionInfo.getLauncher(),
type, type,
split[0] split[0]
); );

View File

@@ -10,6 +10,7 @@ public class GeneratedData {
private List<String> dependencies; private List<String> dependencies;
private String code; private String code;
private String codeType; private String codeType;
private String launcher;
private boolean serialize; private boolean serialize;
private JSONObject responseSchema; private JSONObject responseSchema;
} }

View File

@@ -13,9 +13,13 @@ data class MetaAction(
* 是否IO密集用于决定使用何种线程池 * 是否IO密集用于决定使用何种线程池
*/ */
val io: Boolean = false, val io: Boolean = false,
/**
* 启动器/解释器,对于原生 MCP Tool 、Dynamic Action 来说可忽略,目前仅用于 ORIGIN 类型
*/
val launcher: String? = null,
/** /**
* 行动程序类型,可分为 MCP、ORIGIN、BUILTIN 三种, * 行动程序类型,可分为 MCP、ORIGIN、BUILTIN 三种,
* 分别对应读取到的 MCP Tool、生成的临时行动程序、本地内置行动 * 分别对应读取到的 MCP Tool、生成的临时行动程序、内置行动
*/ */
val type: Type, val type: Type,
/** /**

View File

@@ -1,25 +1,44 @@
package work.slhaf.partner.core.action.entity; package work.slhaf.partner.core.action.entity
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data data class MetaActionInfo(
public class MetaActionInfo { /**
private boolean io; * 是否 IO 密集
*/
val io: Boolean,
/**
* 所需的启动器/解释器
*/
val launcher: String?,
/**
* 参数描述
*/
val params: Map<String, String>,
/**
* 行动功能描述
*/
val description: String,
/**
* 行动标签
*/
val tags: Set<String>,
/**
* 前置行动依赖
*/
val preActions: Set<String>,
/**
* 后置行动依赖
*/
val postActions: Set<String>,
private Map<String, Object> params;
private String description;
private List<String> tags;
private List<String> preActions;
private List<String> postActions;
/** /**
* 是否严格依赖前置行动的成功执行若为true且前置行动失败则不执行该行动后置任务多为触发式。默认即执行。 * 是否严格依赖前置行动的成功执行若为true且前置行动失败则不执行该行动后置任务多为触发式。默认即执行。
*/ */
private boolean strictDependencies; val strictDependencies: Boolean,
/**
private JSONObject responseSchema; * 响应格式说明
} */
val responseSchema: JSONObject,
)

View File

@@ -11,18 +11,10 @@ import java.util.concurrent.atomic.AtomicInteger;
public class CommandExecutionService { public class CommandExecutionService {
public String[] buildCommands(String ext, Map<String, Object> params, String absolutePath) { public String[] buildCommands(String launcher, Map<String, Object> params, String absolutePath) {
String command = switch (ext) {
case "py" -> "python";
case "sh" -> "bash";
default -> null;
};
if (command == null) {
return null;
}
int paramSize = params == null ? 0 : params.size(); int paramSize = params == null ? 0 : params.size();
String[] commands = new String[paramSize + 2]; String[] commands = new String[paramSize + 2];
commands[0] = command; commands[0] = launcher;
commands[1] = absolutePath; commands[1] = absolutePath;
AtomicInteger paramCount = new AtomicInteger(2); AtomicInteger paramCount = new AtomicInteger(2);
if (params != null) { if (params != null) {

View File

@@ -1,6 +1,5 @@
package work.slhaf.partner.core.action.runner.execution; package work.slhaf.partner.core.action.runner.execution;
import cn.hutool.core.io.FileUtil;
import work.slhaf.partner.core.action.entity.MetaAction; import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.core.action.runner.RunnerClient;
@@ -17,18 +16,7 @@ public class OriginExecutionService {
public RunnerClient.RunnerResponse run(MetaAction metaAction) { public RunnerClient.RunnerResponse run(MetaAction metaAction) {
RunnerClient.RunnerResponse response = new RunnerClient.RunnerResponse(); RunnerClient.RunnerResponse response = new RunnerClient.RunnerResponse();
File file = new File(metaAction.getLocation()); File file = new File(metaAction.getLocation());
String ext = FileUtil.getSuffix(file); String[] commands = commandExecutionService.buildCommands(metaAction.getLauncher(), metaAction.getParams(), file.getAbsolutePath());
if (ext == null || ext.isEmpty()) {
response.setOk(false);
response.setData("未知文件类型");
return response;
}
String[] commands = commandExecutionService.buildCommands(ext, metaAction.getParams(), file.getAbsolutePath());
if (commands == null || commands.length == 0) {
response.setOk(false);
response.setData("不支持的文件类型: " + file.getName());
return response;
}
CommandExecutionService.Result execResult = commandExecutionService.exec(commands); CommandExecutionService.Result execResult = commandExecutionService.exec(commands);
response.setOk(execResult.isOk()); response.setOk(execResult.isOk());
response.setData(execResult.getTotal()); response.setData(execResult.getTotal());

View File

@@ -1,6 +1,5 @@
package work.slhaf.partner.core.action.runner.mcp; package work.slhaf.partner.core.action.runner.mcp;
import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.common.McpTransportContext;
@@ -292,8 +291,8 @@ public class DynamicActionMcpManager implements AutoCloseable {
Map<String, Object> additional = Map.of( Map<String, Object> additional = Map.of(
"pre", info.getPreActions(), "pre", info.getPreActions(),
"post", info.getPostActions(), "post", info.getPostActions(),
"strict_pre", info.isStrictDependencies(), "strict_pre", info.getStrictDependencies(),
"io", info.isIo() "io", info.getIo()
); );
McpSchema.Tool tool = McpSchema.Tool.builder() McpSchema.Tool tool = McpSchema.Tool.builder()
.name(name) .name(name)
@@ -305,18 +304,17 @@ public class DynamicActionMcpManager implements AutoCloseable {
.build(); .build();
return McpStatelessServerFeatures.AsyncToolSpecification.builder() return McpStatelessServerFeatures.AsyncToolSpecification.builder()
.tool(tool) .tool(tool)
.callHandler(buildToolHandler(program)) .callHandler(buildToolHandler(program, info.getLauncher()))
.build(); .build();
} }
private BiFunction<McpTransportContext, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> buildToolHandler(File program) { private BiFunction<McpTransportContext, McpSchema.CallToolRequest, Mono<McpSchema.CallToolResult>> buildToolHandler(File program, String launcher) {
return (mcpTransportContext, callToolRequest) -> { return (mcpTransportContext, callToolRequest) -> {
Map<String, Object> arguments = callToolRequest.arguments(); Map<String, Object> arguments = callToolRequest.arguments();
if (arguments == null) { if (arguments == null) {
arguments = Map.of(); arguments = Map.of();
} }
String ext = FileUtil.getSuffix(program); String[] commands = commandExecutionService.buildCommands(launcher, arguments, program.getAbsolutePath());
String[] commands = commandExecutionService.buildCommands(ext, arguments, program.getAbsolutePath());
if (commands == null) { if (commands == null) {
return Mono.just(McpSchema.CallToolResult.builder() return Mono.just(McpSchema.CallToolResult.builder()
.addTextContent("未知文件类型: " + program.getName()) .addTextContent("未知文件类型: " + program.getName())

View File

@@ -46,6 +46,7 @@ public class DynamicActionGenerator extends AbstractAgentModule.Sub<GeneratorInp
MetaAction tempAction = new MetaAction( MetaAction tempAction = new MetaAction(
input.getActionName(), input.getActionName(),
true, true,
generatorData.getLauncher(),
MetaAction.Type.ORIGIN, MetaAction.Type.ORIGIN,
location location
); );
@@ -63,6 +64,7 @@ public class DynamicActionGenerator extends AbstractAgentModule.Sub<GeneratorInp
return result; return result;
} }
// TODO persist serialize. For now, it can be scheduled as a cycling-triggered StateAction or as a special MetaAction
private void waitingSerialize() { private void waitingSerialize() {
throw new UnsupportedOperationException("Unimplemented method 'waitingSerialize'"); throw new UnsupportedOperationException("Unimplemented method 'waitingSerialize'");
} }

View File

@@ -11,7 +11,7 @@ public class RepairerInput {
private String userId; private String userId;
private List<Message> recentMessages; private List<Message> recentMessages;
private Map<String, Object> params; private Map<String, String> params;
private String actionDescription; private String actionDescription;
private List<HistoryAction> historyActionResults; private List<HistoryAction> historyActionResults;
} }

View File

@@ -380,12 +380,12 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
for (String actionKey : actionKeys) { for (String actionKey : actionKeys) {
// 根据 actionKey 加载行动信息,并检查是否存在必需前置依赖 // 根据 actionKey 加载行动信息,并检查是否存在必需前置依赖
MetaActionInfo metaActionInfo = actionCapability.loadMetaActionInfo(actionKey); MetaActionInfo metaActionInfo = actionCapability.loadMetaActionInfo(actionKey);
List<String> preActions = metaActionInfo.getPreActions(); Set<String> preActions = metaActionInfo.getPreActions();
boolean preActionsExist = preActions != null && !preActions.isEmpty(); boolean preActionsExist = preActions.isEmpty();
if (!preActionsExist) { if (!preActionsExist) {
continue; continue;
} }
if (!metaActionInfo.isStrictDependencies()) { if (!metaActionInfo.getStrictDependencies()) {
continue; continue;
} }
if (checkDependenciesExist(lastOrder, preActions, primaryActionChain)) { if (checkDependenciesExist(lastOrder, preActions, primaryActionChain)) {
@@ -398,8 +398,8 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
fixed.set(true); fixed.set(true);
List<String> actionsInChain = primaryActionChain.computeIfAbsent(lastOrder, List<String> actionsInChain = primaryActionChain.computeIfAbsent(lastOrder,
list -> new ArrayList<>()); list -> new ArrayList<>());
preActions = new ArrayList<>(preActions); preActions = new HashSet<>(preActions);
preActions.removeAll(actionsInChain); actionsInChain.forEach(preActions::remove);
actionsInChain.addAll(preActions); actionsInChain.addAll(preActions);
tempOrders.add(lastOrder); tempOrders.add(lastOrder);
} }
@@ -418,7 +418,7 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
} }
} }
private boolean checkDependenciesExist(int lastOrder, List<String> preActions, private boolean checkDependenciesExist(int lastOrder, Set<String> preActions,
Map<Integer, List<String>> primaryActionChain) { Map<Integer, List<String>> primaryActionChain) {
if (!primaryActionChain.containsKey(lastOrder)) { if (!primaryActionChain.containsKey(lastOrder)) {
return false; return false;