base_agent/mcp/skill_loader.py

83 lines
2.9 KiB
Python
Raw Normal View History

2026-04-15 07:16:50 +00:00
import os
import json
import re
from typing import Any, Dict, List, Tuple
from utils.logger import get_logger
import yaml
logger = get_logger("mcp.SkillLoader")
class SkillLoader:
"""加载和解析 SKILL.md 文件"""
@staticmethod
def load_skills_from_directory(directory: str) -> Dict[str, Any]:
skills = {}
for skill_directory in os.listdir(directory):
skill_md = f"{skill_directory}/SKILL.md"
if not os.path.isfile(skill_md):
logger.warning(f"未在{skill_directory}发现SKILL.md文件")
continue
skill_name = os.path.basename(skill_directory) # 去掉 .md 后缀
skill_info = SkillLoader.load_skill(skill_md)
if not skill_info:
continue
skills[skill_info['name']] = skill_info
return skills
@staticmethod
def load_skill(filepath: str) -> Dict[str, Any]:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
skill_info = SkillLoader.parse_skill(content)
return skill_info
@staticmethod
def parse_md_frontmatter(content: str) -> Dict[str, Any]:
# 使用正则表达式提取 frontmatter 部分
frontmatter_match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL | re.MULTILINE)
if frontmatter_match:
frontmatter_content = frontmatter_match.group(1).strip()
# 使用 PyYAML 将 frontmatter 字符串解析为字典
frontmatter_data = yaml.safe_load(frontmatter_content)
return frontmatter_data
else:
return {}
@staticmethod
def parse_skill(content: str) -> Dict[str, Any]:
return SkillLoader.parse_md_frontmatter(content)
lines = content.strip().splitlines()
skill_info = {
'name': lines[0].replace('# ', '').strip(),
'description': lines[1].replace('## Description\n', '').strip(),
'parameters': {},
'example': None,
}
# 解析参数
param_section = False
for line in lines[2:]:
if line.startswith('## Parameters'):
param_section = True
continue
if param_section:
if line.startswith('## Example'):
break
if line.startswith('-'):
param_line = line[1:].strip().split(':')
if len(param_line) == 2:
param_name = param_line[0].strip()
param_desc = param_line[1].strip()
skill_info['parameters'][param_name] = param_desc
# 解析示例
if '## Example' in content:
example_start = content.index('## Example') + len('## Example\n')
example_json = content[example_start:].strip().split('\n```')[0].strip()
skill_info['example'] = json.loads(example_json)
return skill_info