"""SkillLoader 单元测试""" import os import tempfile import pytest import yaml from agentkit.skills.base import Skill, SkillConfig from agentkit.skills.loader import SkillLoader from agentkit.skills.registry import SkillRegistry from agentkit.tools.base import Tool from agentkit.tools.registry import ToolRegistry class DummyTool(Tool): """测试用 Tool 实现""" def __init__(self, name: str = "dummy_tool", **kwargs): super().__init__(name=name, description="dummy", **kwargs) async def execute(self, **kwargs): return {"result": "ok"} def _write_yaml(directory: str, filename: str, data: dict) -> str: path = os.path.join(directory, filename) with open(path, "w", encoding="utf-8") as f: yaml.dump(data, f, allow_unicode=True) return path class TestSkillLoader: """SkillLoader 从 YAML 批量加载测试""" def test_load_from_directory_with_multiple_yaml_files(self): registry = SkillRegistry() loader = SkillLoader(skill_registry=registry) with tempfile.TemporaryDirectory() as tmpdir: _write_yaml(tmpdir, "skill_a.yaml", { "name": "skill_a", "agent_type": "type_a", "task_mode": "llm_generate", "prompt": {"identity": "技能 A"}, }) _write_yaml(tmpdir, "skill_b.yaml", { "name": "skill_b", "agent_type": "type_b", "task_mode": "llm_generate", "prompt": {"identity": "技能 B"}, }) skills = loader.load_from_directory(tmpdir) assert len(skills) == 2 names = [s.name for s in skills] assert "skill_a" in names assert "skill_b" in names def test_skip_invalid_yaml_files_and_log_warning(self, caplog): registry = SkillRegistry() loader = SkillLoader(skill_registry=registry) with tempfile.TemporaryDirectory() as tmpdir: # 有效 YAML _write_yaml(tmpdir, "valid.yaml", { "name": "valid_skill", "agent_type": "valid", "task_mode": "llm_generate", "prompt": {"identity": "有效技能"}, }) # 无效 YAML(缺少必要字段) invalid_path = os.path.join(tmpdir, "invalid.yaml") with open(invalid_path, "w", encoding="utf-8") as f: f.write("just_a_string_not_a_mapping") with caplog.at_level("WARNING"): skills = loader.load_from_directory(tmpdir) assert len(skills) == 1 assert skills[0].name == "valid_skill" def test_empty_directory_returns_empty_list(self): registry = SkillRegistry() loader = SkillLoader(skill_registry=registry) with tempfile.TemporaryDirectory() as tmpdir: skills = loader.load_from_directory(tmpdir) assert skills == [] def test_loaded_skills_are_auto_registered(self): registry = SkillRegistry() loader = SkillLoader(skill_registry=registry) with tempfile.TemporaryDirectory() as tmpdir: _write_yaml(tmpdir, "auto_reg.yaml", { "name": "auto_registered", "agent_type": "auto", "task_mode": "llm_generate", "prompt": {"identity": "自动注册"}, }) loader.load_from_directory(tmpdir) assert registry.has_skill("auto_registered") def test_load_from_single_file(self): registry = SkillRegistry() loader = SkillLoader(skill_registry=registry) with tempfile.TemporaryDirectory() as tmpdir: path = _write_yaml(tmpdir, "single.yaml", { "name": "single_skill", "agent_type": "single", "task_mode": "llm_generate", "prompt": {"identity": "单文件技能"}, }) skill = loader.load_from_file(path) assert skill.name == "single_skill" assert registry.has_skill("single_skill") def test_tool_binding_during_load(self): """当提供 tool_registry 时,加载 Skill 应自动绑定配置中声明的工具""" tool_registry = ToolRegistry() dummy_tool = DummyTool(name="my_tool") tool_registry.register(dummy_tool) skill_registry = SkillRegistry() loader = SkillLoader( skill_registry=skill_registry, tool_registry=tool_registry, ) with tempfile.TemporaryDirectory() as tmpdir: _write_yaml(tmpdir, "with_tools.yaml", { "name": "tooled_skill", "agent_type": "tooled", "task_mode": "tool_call", "tools": ["my_tool"], }) skills = loader.load_from_directory(tmpdir) assert len(skills) == 1 skill = skills[0] assert len(skill.tools) == 1 assert skill.tools[0].name == "my_tool" def test_load_from_file_invalid_yaml_raises_error(self): registry = SkillRegistry() loader = SkillLoader(skill_registry=registry) with tempfile.TemporaryDirectory() as tmpdir: invalid_path = os.path.join(tmpdir, "bad.yaml") with open(invalid_path, "w", encoding="utf-8") as f: f.write("not_a_mapping") with pytest.raises(Exception): loader.load_from_file(invalid_path) def test_load_from_directory_skips_non_yaml_files(self): registry = SkillRegistry() loader = SkillLoader(skill_registry=registry) with tempfile.TemporaryDirectory() as tmpdir: _write_yaml(tmpdir, "skill.yaml", { "name": "yaml_skill", "agent_type": "yaml", "task_mode": "llm_generate", "prompt": {"identity": "YAML 技能"}, }) # 非 YAML 文件 txt_path = os.path.join(tmpdir, "readme.txt") with open(txt_path, "w") as f: f.write("not a yaml") skills = loader.load_from_directory(tmpdir) assert len(skills) == 1 assert skills[0].name == "yaml_skill"