fischer-agentkit/tests/unit/test_skill_loader_provenanc...

182 lines
6.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""SkillLoader v7 provenance + 危险能力告警单元测试"""
import os
import tempfile
from unittest.mock import patch
import yaml
from agentkit.skills.base import Skill, SkillConfig
from agentkit.skills.loader import SkillLoader
from agentkit.skills.registry import SkillRegistry
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 _FakeEntryPoint:
"""模拟 importlib.metadata.EntryPoint"""
def __init__(self, name: str, skill: Skill):
self.name = name
self._skill = skill
def load(self):
return self._skill
def _make_skill(name: str = "ep_skill", capabilities=None, tools=None) -> Skill:
config = SkillConfig(
name=name,
agent_type="test",
task_mode="llm_generate",
prompt={"identity": "test"},
capabilities=capabilities,
tools=tools,
)
return Skill(config)
class TestSkillLoaderProvenance:
def test_load_from_file_sets_yaml_provenance(self):
registry = SkillRegistry()
loader = SkillLoader(skill_registry=registry)
with tempfile.TemporaryDirectory() as tmpdir:
path = _write_yaml(
tmpdir,
"s.yaml",
{
"name": "s",
"agent_type": "t",
"task_mode": "llm_generate",
"prompt": {"identity": "x"},
},
)
skill = loader.load_from_file(path)
assert skill.config.provenance == f"yaml:{path}"
def test_load_from_skill_md_sets_provenance(self):
registry = SkillRegistry()
loader = SkillLoader(skill_registry=registry)
skill_md = """\
---
name: md-skill
description: "test"
agent_type: test
execution_mode: react
---
# Trigger
- test
# Steps
1. step
# Pitfalls
- none
# Verification
- ok
"""
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, "SKILL.md")
with open(path, "w", encoding="utf-8") as f:
f.write(skill_md)
skill = loader.load_from_skill_md(path)
assert skill.config.provenance == f"skill_md:{path}"
def test_load_from_entry_points_sets_provenance(self):
registry = SkillRegistry()
loader = SkillLoader(skill_registry=registry)
fake_ep = _FakeEntryPoint("my_ep", _make_skill("ep_skill"))
with patch("agentkit.skills.loader.sys.version_info", (3, 12, 0)):
with patch("importlib.metadata.entry_points", return_value=[fake_ep]):
skills = loader.load_from_entry_points()
assert len(skills) == 1
assert skills[0].config.provenance == "entry_point:my_ep"
def test_entry_points_dangerous_capability_warning(self, caplog):
"""entry_points 加载声明 shell 能力的 Skill 时触发 warning"""
import logging
registry = SkillRegistry()
loader = SkillLoader(skill_registry=registry)
dangerous_skill = _make_skill(
"dangerous_skill", capabilities=[{"tag": "shell"}, {"tag": "code_execution"}]
)
fake_ep = _FakeEntryPoint("dangerous_ep", dangerous_skill)
with patch("agentkit.skills.loader.sys.version_info", (3, 12, 0)):
with patch("importlib.metadata.entry_points", return_value=[fake_ep]):
with caplog.at_level(logging.WARNING):
skills = loader.load_from_entry_points()
assert len(skills) == 1
assert skills[0].config.provenance == "entry_point:dangerous_ep"
# warning 包含 skill 名与危险能力
warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
assert any(
"dangerous_skill" in r.getMessage() and "shell" in r.getMessage() for r in warnings
)
def test_entry_points_dangerous_tools_warning(self, caplog):
"""entry_points 加载绑定 shell 工具但未声明 capabilities 的 Skill 时触发 warning"""
import logging
registry = SkillRegistry()
loader = SkillLoader(skill_registry=registry)
# 有危险 tools 但无 capabilities 声明——旧逻辑会漏检
dangerous_skill = _make_skill("stealthy_skill", capabilities=None, tools=["shell"])
fake_ep = _FakeEntryPoint("stealthy_ep", dangerous_skill)
with patch("agentkit.skills.loader.sys.version_info", (3, 12, 0)):
with patch("importlib.metadata.entry_points", return_value=[fake_ep]):
with caplog.at_level(logging.WARNING):
skills = loader.load_from_entry_points()
assert len(skills) == 1
warnings = [r for r in caplog.records if r.levelno == logging.WARNING]
assert any(
"stealthy_skill" in r.getMessage() and "shell" in r.getMessage() for r in warnings
)
def test_entry_points_no_capabilities_no_warning(self, caplog):
import logging
registry = SkillRegistry()
loader = SkillLoader(skill_registry=registry)
safe_skill = _make_skill("safe_skill", capabilities=None)
fake_ep = _FakeEntryPoint("safe_ep", safe_skill)
with patch("agentkit.skills.loader.sys.version_info", (3, 12, 0)):
with patch("importlib.metadata.entry_points", return_value=[fake_ep]):
with caplog.at_level(logging.WARNING):
skills = loader.load_from_entry_points()
assert len(skills) == 1
# 不应有危险能力 warning只可能有其他 warning
dangerous_warnings = [
r
for r in caplog.records
if r.levelno == logging.WARNING and "dangerous capabilities" in r.getMessage()
]
assert dangerous_warnings == []
def test_yaml_provenance_overridden_by_loader(self):
"""YAML 中已有 provenance 字段时,加载路径覆盖它(加载路径是权威来源)"""
registry = SkillRegistry()
loader = SkillLoader(skill_registry=registry)
with tempfile.TemporaryDirectory() as tmpdir:
path = _write_yaml(
tmpdir,
"s.yaml",
{
"name": "s",
"agent_type": "t",
"task_mode": "llm_generate",
"prompt": {"identity": "x"},
"provenance": "user_supplied:should_be_overridden",
},
)
skill = loader.load_from_file(path)
assert skill.config.provenance == f"yaml:{path}"
assert "user_supplied" not in skill.config.provenance