# Phase 9:Skills 注册表(Skills Registry) 日期:2026-04-04 状态:待开始 前置依赖:Phase 6(工具系统重构) Demo参考:claw-code-main — skills/loadSkillsDir.ts, bundledSkills.ts, mcpSkillBuilders.ts --- ## 1. 阶段目标 建立完整的 **Skills 动态加载和注册系统**,支持: - 本地文件 Skills(现有) - 插件提供的 Skills - MCP 动态发现的 Skills - Skills 注册表和搜索 - Bundled Skills 集 **现有状态**:Jarvis 已有基础的 `skill_registry.py`,需要增强为完整系统。 --- ## 2. Skills 架构 ### 2.1 Skill 结构 ```markdown # SKILL.md ## Skill Metadata name: my-skill description: Does something useful version: 1.0.0 author: Developer ## Triggers - keywords: ["skill", "my"] - patterns: ["/my-skill"] ## Capabilities - tools: ["my_tool"] - hooks: ["my_hook"] ## Configuration ```json { "option1": "value1" } ``` ``` ### 2.2 SkillRegistry 增强 ```python # backend/app/agents/skills/registry.py @dataclass class SkillMetadata: """Skill 元数据""" name: str description: str version: str author: str | None = None triggers: list[str] = field(default_factory=list) patterns: list[str] = field(default_factory=list) tools: list[str] = field(default_factory=list) hooks: list[str] = field(default_factory=list) commands: list[str] = field(default_factory=list) config_schema: dict | None = None source: SkillSource = SkillSource.LOCAL file_path: Path | None = None class SkillSource(Enum): """Skill 来源""" LOCAL = "local" # 本地文件 PLUGIN = "plugin" # 插件提供 MCP = "mcp" # MCP 动态发现 BUNDLED = "bundled" # 内置 class SkillRegistry: """Skills 注册表""" def __init__( self, local_skills_dir: Path, tool_registry: ToolRegistry, hook_manager: HookManager ): self.local_skills_dir = local_skills_dir self.tool_registry = tool_registry self.hook_manager = hook_manager self._skills: dict[str, SkillMetadata] = {} self._index: dict[str, list[str]] = defaultdict(list) # keyword -> skill names async def load_all(self): """加载所有 Skills""" await self._load_local_skills() await self._load_bundled_skills() async def _load_local_skills(self): """加载本地 Skills""" if not self.local_skills_dir.exists(): return for skill_file in self.local_skills_dir.rglob("SKILL.md"): try: metadata = await self._parse_skill_file(skill_file) metadata.source = SkillSource.LOCAL self._register_skill(metadata) except Exception as e: logger.warning(f"Failed to load skill from {skill_file}: {e}") async def _load_bundled_skills(self): """加载内置 Skills""" for skill_def in BUNDLED_SKILLS: metadata = SkillMetadata( name=skill_def["name"], description=skill_def["description"], version=skill_def["version"], author="Jarvis Team", triggers=skill_def.get("triggers", []), tools=skill_def.get("tools", []), source=SkillSource.BUNDLED ) self._register_skill(metadata) async def _parse_skill_file(self, path: Path) -> SkillMetadata: """解析 SKILL.md 文件""" content = path.read_text(encoding="utf-8") # 简单的 frontmatter 解析 metadata = SkillMetadata( name=path.parent.name, description="", version="1.0.0" ) # 解析 triggers for line in content.split("\n"): if line.startswith("- keywords:"): keywords = line.split("[")[1].split("]")[0] metadata.triggers = [k.strip() for k in keywords.split(",")] return metadata def _register_skill(self, metadata: SkillMetadata): """注册 Skill""" self._skills[metadata.name] = metadata # 更新索引 for keyword in metadata.triggers: self._index[keyword].append(metadata.name) # 注册关联的工具 for tool_name in metadata.tools: self.tool_registry.register_tool_for_skill(metadata.name, tool_name) async def get_skill(self, name: str) -> SkillMetadata | None: """获取 Skill""" return self._skills.get(name) async def search( self, query: str, limit: int = 10 ) -> list[SkillMetadata]: """搜索 Skills""" results = [] query_lower = query.lower() # 精确匹配 name if query_lower in self._skills: results.append(self._skills[query_lower]) # 关键词匹配 for skill_name in self._index.get(query_lower, []): if skill_name not in results: results.append(self._skills[skill_name]) # 描述匹配 for skill in self._skills.values(): if skill in results: continue if query_lower in skill.description.lower(): results.append(skill) return results[:limit] async def get_skill_context( self, skill_name: str, context: dict ) -> str: """获取 Skill 上下文""" skill = self._skills.get(skill_name) if not skill: return "" # 读取 SKILL.md 内容 if skill.file_path and skill.file_path.exists(): content = skill.file_path.read_text(encoding="utf-8") # 移除 metadata 部分,只保留 instructions return content return "" ``` --- ## 3. MCP Skill Builder ### 3.1 MCPSkillBuilder ```python # backend/app/agents/skills/mcp_builder.py class MCPSkillBuilder: """ MCP Skill Builder 从 MCP 服务器动态构建 Skills """ def __init__( self, mcp_client: MCPClient, skill_registry: SkillRegistry ): self.mcp_client = mcp_client self.skill_registry = skill_registry async def discover_skills_from_mcp( self, mcp_server_id: str ) -> list[SkillMetadata]: """从 MCP 服务器发现 Skills""" # 获取 MCP 服务器提供的工具 tools = await self.mcp_client.list_tools(mcp_server_id) skills = [] # 按工具前缀分组 tool_groups: dict[str, list] = defaultdict(list) for tool in tools: parts = tool.name.split("_") if len(parts) > 1: prefix = parts[0] tool_groups[prefix].append(tool) else: # 单工具,作为独立 skill skill = self._tool_to_skill(tool, mcp_server_id) skills.append(skill) # 创建分组 skill for prefix, group_tools in tool_groups.items(): skill = self._group_to_skill(prefix, group_tools, mcp_server_id) skills.append(skill) return skills def _tool_to_skill( self, tool: MCPProtocolTool, server_id: str ) -> SkillMetadata: """将单个 MCP 工具转换为 Skill""" return SkillMetadata( name=f"{server_id}_{tool.name}", description=tool.description or f"MCP tool: {tool.name}", version="1.0.0", tools=[tool.name], source=SkillSource.MCP, config_schema={ "server_id": server_id, "tool_name": tool.name } ) def _group_to_skill( self, prefix: str, tools: list[MCPProtocolTool], server_id: str ) -> SkillMetadata: """将一组 MCP 工具转换为 Skill""" return SkillMetadata( name=f"{server_id}_{prefix}", description=f"MCP {prefix} tools: {', '.join(t.name for t in tools)}", version="1.0.0", tools=[t.name for t in tools], source=SkillSource.MCP, config_schema={ "server_id": server_id, "prefix": prefix } ) ``` --- ## 4. 内置 Skills ```python # backend/app/agents/skills/bundled.py BUNDLED_SKILLS = [ { "name": "code-analysis", "description": "代码分析技能 - 分析代码结构、复杂度、依赖关系", "version": "1.0.0", "triggers": ["分析代码", "代码分析", "code analysis"], "tools": ["grep", "glob", "lint"] }, { "name": "git-helper", "description": "Git 操作助手 - 管理 Git 仓库和操作", "version": "1.0.0", "triggers": ["git", "版本控制", "commit"], "tools": ["git_status", "git_log", "git_diff"] }, { "name": "web-research", "description": "网络研究技能 - 搜索和抓取网页内容", "version": "1.0.0", "triggers": ["搜索", "研究", "research", "web search"], "tools": ["web_search", "web_fetch"] }, { "name": "file-management", "description": "文件管理技能 - 组织和管理文件", "version": "1.0.0", "triggers": ["文件", "整理", "file management"], "tools": ["glob", "file_read", "file_write", "organize"] }, { "name": "task-planning", "description": "任务规划技能 - 拆解和规划复杂任务", "version": "1.0.0", "triggers": ["规划", "任务拆解", "task planning"], "tools": ["create_task", "get_tasks", "update_task"] } ] ``` --- ## 5. Skill 与 Agent 集成 ### 5.1 修改 AgentService ```python # backend/app/services/agent_service.py (修改部分) async def build_skill_context( self, skill_names: list[str], context: dict ) -> str: """构建 Skill 上下文""" parts = [] for skill_name in skill_names: skill_context = await self.skill_registry.get_skill_context( skill_name, context ) if skill_context: parts.append(f"\n\n=== Skill: {skill_name} ===\n{skill_context}") return "\n".join(parts) async def chat( self, message: str, context: dict ) -> AgentResponse: """处理聊天消息(集成 Skills)""" # 1. 检测触发的 Skills triggered_skills = await self.skill_registry.search(message) # 2. 构建 Skill 上下文 skill_context = await self.build_skill_context( [s.name for s in triggered_skills], context ) # 3. 注入到 system prompt if skill_context: context["skill_context"] = skill_context # 4. 继续正常处理 return await self._process_message(message, context) ``` --- ## 6. API 接口 ```python # backend/app/routers/skills.py @router.get("/api/skills") async def list_skills( source: SkillSource | None = None, current_user: User = Depends(get_current_user) ): """列出所有 Skills""" skills = await skill_registry.list_all() if source: skills = [s for s in skills if s.source == source] return { "skills": [ { "name": s.name, "description": s.description, "version": s.version, "source": s.source.value, "triggers": s.triggers } for s in skills ] } @router.get("/api/skills/search") async def search_skills( q: str, limit: int = 10 ): """搜索 Skills""" results = await skill_registry.search(q, limit) return {"skills": results} @router.get("/api/skills/{skill_name}") async def get_skill( skill_name: str, current_user: User = Depends(get_current_user) ): """获取 Skill 详情""" skill = await skill_registry.get_skill(skill_name) if not skill: raise HTTPException(404, "Skill not found") return { "name": skill.name, "description": skill.description, "version": skill.version, "source": skill.source.value, "triggers": skill.triggers, "tools": skill.tools, "hooks": skill.hooks } ``` --- ## 7. 文件结构 ``` backend/app/agents/skills/ ├── __init__.py ├── registry.py # Skills 注册表(增强) ├── metadata.py # Skill 元数据 ├── mcp_builder.py # MCP Skill Builder ├── bundled.py # 内置 Skills │ ├── loaders/ # 加载器 │ ├── __init__.py │ ├── local_loader.py # 本地文件加载 │ ├── plugin_loader.py # 插件 Skill 加载 │ └── mcp_loader.py # MCP Skill 加载 │ └── context.py # Skill 上下文构建 backend/app/routers/ └── skills.py # Skills API 路由 ``` --- ## 8. 验收标准 | 检查点 | 标准 | |--------|------| | 本地 Skills | 能加载 local_skills_dir 下的所有 SKILL.md | | MCP Skills | 能从 MCP 服务器发现和加载 Skills | | Bundled Skills | 内置 Skills 默认加载 | | Skill 搜索 | 能按关键词搜索 Skills | | Skill 上下文 | Skill 内容正确注入 Agent prompt | | API 可用 | Skills API 可用 | --- ## 9. Demo 借鉴 | claw-code | Jarvis 对应 | |-----------|------------| | `src/skills/loadSkillsDir.ts` | `skills/loaders/local_loader.py` | | `src/skills/bundledSkills.ts` | `skills/bundled.py` | | `src/skills/mcpSkillBuilders.ts` | `skills/mcp_builder.py` | | `/skills` command | `/api/skills` | | Skill registry pipeline | `SkillRegistry` |