mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-04 06:29:22 +00:00
* Support inline skill definitions This commit adds inline skill loading without a need for a file. It also DRYs the skill loading feature. * Address code review suggestions
209 lines
7.4 KiB
Python
209 lines
7.4 KiB
Python
"""Integration tests for the skills system."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from crewai import Agent, Crew, Task
|
|
from crewai.crews.utils import _resolve_crew_skills
|
|
from crewai.skills.loader import (
|
|
activate_skill,
|
|
discover_skills,
|
|
format_skill_context,
|
|
)
|
|
from crewai.skills.models import INSTRUCTIONS, METADATA
|
|
from crewai.utilities.prompts import Prompts
|
|
|
|
|
|
def _create_skill_dir(parent: Path, name: str, body: str = "Body.") -> Path:
|
|
"""Helper to create a skill directory with SKILL.md."""
|
|
skill_dir = parent / name
|
|
skill_dir.mkdir()
|
|
(skill_dir / "SKILL.md").write_text(
|
|
f"---\nname: {name}\ndescription: Skill {name}\n---\n{body}"
|
|
)
|
|
return skill_dir
|
|
|
|
|
|
class TestSkillDiscoveryAndActivation:
|
|
"""End-to-end tests for discover + activate workflow."""
|
|
|
|
def test_discover_and_activate(self, tmp_path: Path) -> None:
|
|
_create_skill_dir(tmp_path, "my-skill", body="Use this skill.")
|
|
skills = discover_skills(tmp_path)
|
|
assert len(skills) == 1
|
|
assert skills[0].disclosure_level == METADATA
|
|
|
|
activated = activate_skill(skills[0])
|
|
assert activated.disclosure_level == INSTRUCTIONS
|
|
assert activated.instructions == "Use this skill."
|
|
|
|
context = format_skill_context(activated)
|
|
assert '<skill name="my-skill">' in context
|
|
assert "Use this skill." in context
|
|
|
|
def test_filter_by_skill_names(self, tmp_path: Path) -> None:
|
|
_create_skill_dir(tmp_path, "alpha")
|
|
_create_skill_dir(tmp_path, "beta")
|
|
_create_skill_dir(tmp_path, "gamma")
|
|
|
|
all_skills = discover_skills(tmp_path)
|
|
wanted = {"alpha", "gamma"}
|
|
filtered = [s for s in all_skills if s.name in wanted]
|
|
assert {s.name for s in filtered} == {"alpha", "gamma"}
|
|
|
|
def test_full_fixture_skill(self) -> None:
|
|
fixtures = Path(__file__).parent / "fixtures"
|
|
valid_dir = fixtures / "valid-skill"
|
|
if not valid_dir.exists():
|
|
pytest.skip("Fixture not found")
|
|
|
|
skills = discover_skills(fixtures)
|
|
valid_skills = [s for s in skills if s.name == "valid-skill"]
|
|
assert len(valid_skills) == 1
|
|
|
|
skill = valid_skills[0]
|
|
assert skill.frontmatter.license == "Apache-2.0"
|
|
assert skill.frontmatter.allowed_tools == ["web-search", "file-read"]
|
|
|
|
activated = activate_skill(skill)
|
|
assert "Instructions" in (activated.instructions or "")
|
|
|
|
def test_multiple_search_paths(self, tmp_path: Path) -> None:
|
|
path_a = tmp_path / "a"
|
|
path_a.mkdir()
|
|
_create_skill_dir(path_a, "skill-a")
|
|
|
|
path_b = tmp_path / "b"
|
|
path_b.mkdir()
|
|
_create_skill_dir(path_b, "skill-b")
|
|
|
|
all_skills = []
|
|
for search_path in [path_a, path_b]:
|
|
all_skills.extend(discover_skills(search_path))
|
|
names = {s.name for s in all_skills}
|
|
assert names == {"skill-a", "skill-b"}
|
|
|
|
def test_agent_preserves_metadata_for_discovered_skills(self, tmp_path: Path) -> None:
|
|
_create_skill_dir(tmp_path, "travel", body="Use this skill for travel planning.")
|
|
discovered = discover_skills(tmp_path)
|
|
|
|
agent = Agent(
|
|
role="Travel Advisor",
|
|
goal="Provide personalized travel suggestions.",
|
|
backstory="An experienced travel consultant.",
|
|
skills=discovered,
|
|
)
|
|
|
|
assert agent.skills is not None
|
|
assert agent.skills[0].disclosure_level == METADATA
|
|
assert agent.skills[0].instructions is None
|
|
|
|
result = Prompts(agent=agent, has_tools=False, use_system_prompt=True).task_execution()
|
|
system = getattr(result, "system", "") or result.prompt
|
|
assert '<skill name="travel">' in system
|
|
assert "Skill travel" in system
|
|
# METADATA-level skills must not leak full instructions into the prompt
|
|
assert "Use this skill for travel planning." not in system
|
|
|
|
def test_agent_accepts_inline_skill_string(self) -> None:
|
|
agent = Agent(
|
|
role="Reviewer",
|
|
goal="Review changes.",
|
|
backstory="An experienced reviewer.",
|
|
skills=[
|
|
"---\n"
|
|
"name: inline-review\n"
|
|
"description: Inline review guidance\n"
|
|
"---\n"
|
|
"Focus on behavior and missing tests."
|
|
],
|
|
)
|
|
|
|
assert agent.skills is not None
|
|
assert [skill.name for skill in agent.skills] == ["inline-review"]
|
|
assert [skill.disclosure_level for skill in agent.skills] == [INSTRUCTIONS]
|
|
assert [skill.instructions for skill in agent.skills] == [
|
|
"Focus on behavior and missing tests."
|
|
]
|
|
|
|
result = Prompts(agent=agent, has_tools=False, use_system_prompt=True).task_execution()
|
|
system = getattr(result, "system", "") or result.prompt
|
|
assert '<skill name="inline-review">' in system
|
|
assert "Focus on behavior and missing tests." in system
|
|
|
|
def test_agent_treats_plain_skill_string_as_path(self, tmp_path: Path) -> None:
|
|
_create_skill_dir(tmp_path, "path-skill", body="Use the path skill.")
|
|
|
|
agent = Agent(
|
|
role="Reviewer",
|
|
goal="Review changes.",
|
|
backstory="An experienced reviewer.",
|
|
skills=[str(tmp_path)],
|
|
)
|
|
|
|
assert agent.skills is not None
|
|
assert [skill.name for skill in agent.skills] == ["path-skill"]
|
|
assert [skill.instructions for skill in agent.skills] == ["Use the path skill."]
|
|
|
|
def test_crew_resolves_inline_skill_string(self) -> None:
|
|
agent = Agent(
|
|
role="Reviewer",
|
|
goal="Review changes.",
|
|
backstory="An experienced reviewer.",
|
|
)
|
|
task = Task(
|
|
description="Review the diff.",
|
|
expected_output="Findings.",
|
|
agent=agent,
|
|
)
|
|
crew = Crew(
|
|
agents=[agent],
|
|
tasks=[task],
|
|
skills=[
|
|
"---\n"
|
|
"name: crew-inline-review\n"
|
|
"description: Crew-level inline review guidance\n"
|
|
"---\n"
|
|
"Apply this to every agent."
|
|
],
|
|
)
|
|
|
|
skills = _resolve_crew_skills(crew)
|
|
|
|
assert skills is not None
|
|
assert [skill.name for skill in skills] == ["crew-inline-review"]
|
|
assert [skill.instructions for skill in skills] == ["Apply this to every agent."]
|
|
|
|
def test_crew_activates_preloaded_metadata_skill(self, tmp_path: Path) -> None:
|
|
_create_skill_dir(
|
|
tmp_path,
|
|
"crew-preloaded",
|
|
body="Apply this crew-level guidance to every agent.",
|
|
)
|
|
metadata_skill = discover_skills(tmp_path)[0]
|
|
agent = Agent(
|
|
role="Reviewer",
|
|
goal="Review changes.",
|
|
backstory="An experienced reviewer.",
|
|
)
|
|
task = Task(
|
|
description="Review the diff.",
|
|
expected_output="Findings.",
|
|
agent=agent,
|
|
)
|
|
crew = Crew(
|
|
agents=[agent],
|
|
tasks=[task],
|
|
skills=[metadata_skill],
|
|
)
|
|
|
|
skills = _resolve_crew_skills(crew)
|
|
|
|
assert skills is not None
|
|
assert [skill.name for skill in skills] == ["crew-preloaded"]
|
|
assert [skill.disclosure_level for skill in skills] == [INSTRUCTIONS]
|
|
assert [skill.instructions for skill in skills] == [
|
|
"Apply this crew-level guidance to every agent."
|
|
]
|