diff --git a/liteflow-react-agent/liteflow-react-agent-core/pom.xml b/liteflow-react-agent/liteflow-react-agent-core/pom.xml index 2fb830e93..6dcfdb585 100644 --- a/liteflow-react-agent/liteflow-react-agent-core/pom.xml +++ b/liteflow-react-agent/liteflow-react-agent-core/pom.xml @@ -45,16 +45,4 @@ test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - false - - - - diff --git a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillBoxFactory.java b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillBoxFactory.java index d6e4f7f93..4331db26f 100644 --- a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillBoxFactory.java +++ b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillBoxFactory.java @@ -48,7 +48,7 @@ public final class SkillBoxFactory { FileSystemSkillRepository repository = new FileSystemSkillRepository(root); List allSkills = repository.getAllSkills(); List selected = selectSkills(allSkills, allowedSkills, skillsConfig); - SkillToolManifest manifest = new SkillToolManifest(root, skillsConfig); + SkillToolResolver toolResolver = new SkillToolResolver(skillsConfig); SkillBox skillBox = createSkillBox(toolkit, workspaceDir); Map skillIdToName = new LinkedHashMap<>(); List skillNames = new ArrayList<>(); @@ -56,7 +56,7 @@ public final class SkillBoxFactory { for (AgentSkill skill : selected) { skillIdToName.put(skill.getSkillId(), skill.getName()); skillNames.add(skill.getName()); - List skillTools = manifest.instantiateTools(skill.getName()); + List skillTools = toolResolver.instantiateTools(skill); if (skillTools.isEmpty()) { skillBox.registerSkill(skill); } else { diff --git a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillToolManifest.java b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillToolManifest.java deleted file mode 100644 index 8390fce01..000000000 --- a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/skill/SkillToolManifest.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.yomahub.liteflow.agent.skill; - -import com.yomahub.liteflow.agent.exception.AgentConfigException; -import com.yomahub.liteflow.property.agent.SkillsConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -public class SkillToolManifest { - - private static final Logger LOG = LoggerFactory.getLogger(SkillToolManifest.class); - - private final SkillsConfig config; - private final Map>> toolClasses = new LinkedHashMap<>(); - - public SkillToolManifest(Path skillsRoot, SkillsConfig config) { - this.config = config; - scan(skillsRoot); - } - - public List instantiateTools(String skillName) { - List> classes = toolClasses.get(skillName); - if (classes == null || classes.isEmpty()) { - return List.of(); - } - List instances = new ArrayList<>(classes.size()); - for (Class clazz : classes) { - try { - instances.add(clazz.getDeclaredConstructor().newInstance()); - } catch (ReflectiveOperationException e) { - handleProblem("Skill '" + skillName + "' tool class '" + clazz.getName() - + "' instantiation failed", e); - } - } - return List.copyOf(instances); - } - - private void scan(Path skillsRoot) { - if (!Files.isDirectory(skillsRoot)) { - handleProblem("Skills root not found: " + skillsRoot, null); - return; - } - try (Stream dirs = Files.list(skillsRoot)) { - dirs.filter(Files::isDirectory) - .sorted() - .forEach(this::loadOne); - } catch (IOException e) { - handleProblem("Failed to scan skills dir: " + skillsRoot, e); - } - } - - private void loadOne(Path skillDir) { - Path skillMd = skillDir.resolve("SKILL.md"); - if (!Files.exists(skillMd)) { - handleProblem("Skill file not found: " + skillMd, null); - return; - } - try { - Map frontmatter = parseFrontmatter(Files.readString(skillMd)); - Object nameObj = frontmatter.get("name"); - if (nameObj == null) { - return; - } - Object toolsObj = frontmatter.get("tools"); - if (toolsObj == null) { - return; - } - String skillName = nameObj.toString().trim(); - List> resolved = new ArrayList<>(); - for (String className : toClassNameList(toolsObj)) { - try { - resolved.add(Class.forName(className)); - } catch (ClassNotFoundException e) { - handleProblem("Skill '" + skillName + "' references unknown tool class '" - + className + "'", e); - } - } - if (!resolved.isEmpty()) { - toolClasses.put(skillName, List.copyOf(resolved)); - LOG.info("Skill '{}' bound to tool classes: {}", skillName, - resolved.stream().map(Class::getName).toList()); - } - } catch (IOException e) { - handleProblem("Failed to read skill file: " + skillMd, e); - } - } - - static Map parseFrontmatter(String content) { - Map result = new LinkedHashMap<>(); - if (content == null || !content.startsWith("---")) { - return result; - } - int end = content.indexOf("\n---", 3); - if (end < 0) { - return result; - } - String[] lines = content.substring(3, end).split("\\R"); - String currentListKey = null; - List currentList = null; - for (String rawLine : lines) { - String line = rawLine.stripTrailing(); - String trimmed = line.trim(); - if (trimmed.isEmpty() || trimmed.startsWith("#")) { - continue; - } - if (currentListKey != null && trimmed.startsWith("-")) { - currentList.add(unquote(trimmed.substring(1).trim())); - continue; - } - currentListKey = null; - currentList = null; - int colon = trimmed.indexOf(':'); - if (colon < 0) { - continue; - } - String key = trimmed.substring(0, colon).trim(); - String value = trimmed.substring(colon + 1).trim(); - if (value.isEmpty()) { - currentListKey = key; - currentList = new ArrayList<>(); - result.put(key, currentList); - } else { - result.put(key, unquote(value)); - } - } - return result; - } - - private List toClassNameList(Object field) { - if (field instanceof List list) { - return list.stream() - .map(Object::toString) - .map(String::trim) - .filter(s -> !s.isEmpty()) - .toList(); - } - return Stream.of(field.toString().split(",")) - .map(String::trim) - .filter(s -> !s.isEmpty()) - .toList(); - } - - private static String unquote(String value) { - if (value.length() >= 2) { - char first = value.charAt(0); - char last = value.charAt(value.length() - 1); - if ((first == '"' && last == '"') || (first == '\'' && last == '\'')) { - return value.substring(1, value.length() - 1); - } - } - return value; - } - - private void handleProblem(String message, Exception e) { - if (config.isStrict()) { - if (e == null) { - throw new AgentConfigException(message); - } - throw new AgentConfigException(message, e); - } - if (e == null) { - LOG.warn(message); - } else { - LOG.warn("{}: {}", message, e.getMessage()); - } - } -} diff --git a/liteflow-react-agent/liteflow-react-agent-core/src/test/java/com/yomahub/liteflow/agent/component/ReActAgentComponentTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/unit/ReActAgentComponentTest.java similarity index 82% rename from liteflow-react-agent/liteflow-react-agent-core/src/test/java/com/yomahub/liteflow/agent/component/ReActAgentComponentTest.java rename to liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/unit/ReActAgentComponentTest.java index 37445478b..59433b6a2 100644 --- a/liteflow-react-agent/liteflow-react-agent-core/src/test/java/com/yomahub/liteflow/agent/component/ReActAgentComponentTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/unit/ReActAgentComponentTest.java @@ -1,7 +1,10 @@ -package com.yomahub.liteflow.agent.component; +package com.yomahub.liteflow.test.agent.unit; +import com.yomahub.liteflow.agent.component.ReActAgentComponent; +import com.yomahub.liteflow.agent.component.ReActAgentContext; import com.yomahub.liteflow.agent.model.ModelSpec; import com.yomahub.liteflow.slot.Slot; +import io.agentscope.core.message.Msg; import org.junit.jupiter.api.Test; import java.nio.file.Path; @@ -11,13 +14,13 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -class ReActAgentComponentTest { +public class ReActAgentComponentTest { @Test void effectiveSystemPromptPrependsFrameworkPromptAndAppendsComponentPrompt() { TestAgentComponent component = new TestAgentComponent(); - String prompt = component.effectiveSystemPrompt(); + String prompt = component.effectiveSystemPromptForTest(); assertTrue(prompt.contains("使用用户提问所用的语言")); assertTrue(prompt.contains("每次调用工具前")); @@ -71,7 +74,11 @@ class ReActAgentComponentTest { return "hello"; } - void handleReplyForTest(io.agentscope.core.message.Msg reply) { + String effectiveSystemPromptForTest() { + return effectiveSystemPrompt(); + } + + void handleReplyForTest(Msg reply) { handleReply(reply); } }