Files
X-Agents/core/agents/skills/executor.py
DESKTOP-72TV0V4\caoxiaozhu 249e7e577a feat: 新增 core/agents 模块和 nanobot
- 新增 agents 模块,包含 agent、api、skills 等子模块
- 新增 nanobot 项目,支持多渠道集成
- 添加启动脚本 start-all.bat 和 start-all.sh

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 21:29:12 +08:00

179 lines
4.8 KiB
Python

"""Skill executor for executing skills."""
import logging
import re
from dataclasses import dataclass
from typing import Any
from loguru import logger
from agents.skills.loader import Skill, SkillsLoader
logger = logging.getLogger(__name__)
@dataclass
class SkillContext:
"""Execution context for a skill."""
skill_id: str
skill_name: str
input_data: dict[str, Any]
user_message: str
class SkillExecutor:
"""Executes skills based on user input."""
def __init__(self, skills_loader: SkillsLoader):
"""Initialize skill executor.
Args:
skills_loader: SkillsLoader instance for loading skills
"""
self.loader = skills_loader
self._skills_prompt_cache: dict[str, str] = {}
async def find_matching_skills(self, user_message: str) -> list[Skill]:
"""Find skills that match the user message.
Args:
user_message: User's input message
Returns:
List of matching skills (currently returns all active skills)
"""
# Get all active skills
skills = await self.loader.list_skills()
active_skills = [s for s in skills if s.status == "active"]
return active_skills
async def execute_skill(
self,
skill_id: str,
context: SkillContext,
) -> str | None:
"""Execute a skill with given context.
Args:
skill_id: ID of skill to execute
context: Execution context
Returns:
Execution result as string, or None if failed
"""
skill = await self.loader.load_skill_with_content(skill_id)
if not skill:
logger.warning(f"Skill not found: {skill_id}")
return None
if skill.status != "active":
logger.warning(f"Skill is not active: {skill_id}")
return None
# Extract prompt/instructions from skill content
prompt = self._extract_skill_prompt(skill)
# Replace placeholders in prompt with context
prompt = self._inject_context(prompt, context)
return prompt
def _extract_skill_prompt(self, skill: Skill) -> str:
"""Extract main prompt/instructions from skill content.
Args:
skill: Skill object with content
Returns:
Extracted prompt
"""
content = skill.content
# Skip frontmatter if present
lines = content.split("\n")
start_line = 0
if content.startswith("---"):
for i in range(1, len(lines)):
if lines[i].strip() == "---":
start_line = i + 1
break
# Join remaining content
main_content = "\n".join(lines[start_line:])
# Remove markdown headers but keep content
prompt = re.sub(r"^#+\s+", "", main_content, flags=re.MULTILINE)
return prompt.strip()
def _inject_context(self, prompt: str, context: SkillContext) -> str:
"""Inject context into prompt template.
Args:
prompt: Prompt template
context: Execution context
Returns:
Prompt with context injected
"""
# Replace common placeholders
replacements = {
"{{user_message}}": context.user_message,
"{{skill_name}}": context.skill_name,
"{{input}}": str(context.input_data),
}
result = prompt
for placeholder, value in replacements.items():
result = result.replace(placeholder, value)
return result
async def get_skill_system_prompt(self, skill_id: str) -> str | None:
"""Get system prompt for a skill to be used in LLM context.
Args:
skill_id: Skill ID
Returns:
System prompt for the skill, or None if not found
"""
# Check cache
if skill_id in self._skills_prompt_cache:
return self._skills_prompt_cache[skill_id]
skill = await self.loader.load_skill_with_content(skill_id)
if not skill or skill.status != "active":
return None
# Extract prompt
prompt = self._extract_skill_prompt(skill)
# Cache it
self._skills_prompt_cache[skill_id] = prompt
return prompt
def build_skills_context(self, skills: list[Skill]) -> str:
"""Build context string from multiple skills.
Args:
skills: List of skills
Returns:
Combined context string
"""
if not skills:
return ""
context_parts = ["## Available Skills\n"]
for skill in skills:
context_parts.append(f"### {skill.name}")
context_parts.append(f"{skill.description}\n")
return "\n".join(context_parts)
def clear_cache(self) -> None:
"""Clear prompt cache."""
self._skills_prompt_cache.clear()