179 lines
6.1 KiB
Python
179 lines
6.1 KiB
Python
"""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"
|