diff --git a/.features/skill/MEMORY.md b/.features/skill/MEMORY.md index e202500..120efd1 100644 --- a/.features/skill/MEMORY.md +++ b/.features/skill/MEMORY.md @@ -18,16 +18,16 @@ Skill 系统支持两种来源:官方 skills (`./skills/`) 和用户 skills (` ## 最近重要事项 +- 2026-04-19: 环境变量 `SKILLS_SUBDIR` 重命名为 `PROJECT_NAME`,用于选择 `skills/{PROJECT_NAME}` 和 `skills/autoload/{PROJECT_NAME}` 目录 - 2026-04-19: `create_robot_project` 的 autoload 去重和 stale 清理补强,autoload 目录也纳入 managed 清理,避免 `rag-retrieve-only` 场景下旧的 `rag-retrieve` 残留 -- 2026-04-18: `create_robot_project` 改为自动加载 `skills/autoload/{SKILLS_SUBDIR}` 下所有 skill,并跳过已显式传入的同名 skill -- 2026-04-18: `/api/v1/skill/list` 的官方库改为同时读取 `skills/common` 和 `skills/{SKILLS_SUBDIR}`,并按目录顺序去重 -- 2026-04-18: `_extract_skills_to_robot` 改为通过环境变量 `SKILLS_SUBDIR` 选择官方 skills 子目录,默认使用 `skills/common` +- 2026-04-18: `/api/v1/skill/list` 的官方库改为同时读取 `skills/common` 和 `skills/{PROJECT_NAME}`,并按目录顺序去重 +- 2026-04-18: `_extract_skills_to_robot` 改为通过环境变量 `PROJECT_NAME` 选择官方 skills 子目录,默认使用 `skills/common` - 2025-02-11: 初始化 skill 功能 memory ## Gotchas(开发必读) - ⚠️ `create_robot_project` 的 autoload 去重是“包含匹配”,只要传入的 skill 字符串里包含 autoload skill 名,就不会重复自动加载 -- ⚠️ `_extract_skills_to_robot` 只会从 `skills/{SKILLS_SUBDIR}` 读取官方 skills,默认是 `common` +- ⚠️ `_extract_skills_to_robot` 只会从 `skills/{PROJECT_NAME}` 读取官方 skills,默认是 `common` - ⚠️ 执行脚本必须使用绝对路径 - ⚠️ MCP 配置优先级:Skill MCP > 默认 MCP > 用户参数 - ⚠️ 上传大小限制:50MB(ZIP),解压后最大 500MB diff --git a/routes/skill_manager.py b/routes/skill_manager.py index 9f52a1a..1617f2e 100644 --- a/routes/skill_manager.py +++ b/routes/skill_manager.py @@ -10,7 +10,7 @@ from typing import List, Optional from dataclasses import dataclass from fastapi import APIRouter, HTTPException, Query, UploadFile, File, Form from pydantic import BaseModel -from utils.settings import SKILLS_DIR, SKILLS_SUBDIR +from utils.settings import SKILLS_DIR, PROJECT_NAME import aiofiles logger = logging.getLogger('app') @@ -438,7 +438,7 @@ def get_official_skills(base_dir: str) -> List[SkillItem]: official_skills_dirs = [ os.path.join(skills_root_dir, "common"), - os.path.join(skills_root_dir, SKILLS_SUBDIR), + os.path.join(skills_root_dir, PROJECT_NAME), ] for official_skills_dir in official_skills_dirs: @@ -511,7 +511,7 @@ async def list_skills( SkillListResponse containing all skills Notes: - - Official skills are read from /skills/common and /skills/{SKILLS_SUBDIR} + - Official skills are read from /skills/common and /skills/{PROJECT_NAME} - User skills are read from /projects/uploads/{bot_id}/skills directory - User skills are marked with user_skill: true """ diff --git a/utils/multi_project_manager.py b/utils/multi_project_manager.py index 544c38e..64f7eb4 100644 --- a/utils/multi_project_manager.py +++ b/utils/multi_project_manager.py @@ -331,15 +331,15 @@ def create_robot_project(dataset_ids: List[str], bot_id: str, force_rebuild: boo skills = list(skills or []) if os.path.isabs(settings.SKILLS_DIR): - autoload_skills_dir = Path(settings.SKILLS_DIR) / "autoload" / settings.SKILLS_SUBDIR + autoload_skills_dir = Path(settings.SKILLS_DIR) / "autoload" / settings.PROJECT_NAME else: - autoload_skills_dir = project_path.parent / settings.SKILLS_DIR / "autoload" / settings.SKILLS_SUBDIR + autoload_skills_dir = project_path.parent / settings.SKILLS_DIR / "autoload" / settings.PROJECT_NAME if autoload_skills_dir.exists(): for item in sorted(autoload_skills_dir.iterdir()): if not item.is_dir() or any(_skill_matches_autoload(skill, item.name) for skill in skills): continue - skill_path = f"@skills/autoload/{settings.SKILLS_SUBDIR}/{item.name}" + skill_path = f"@skills/autoload/{settings.PROJECT_NAME}/{item.name}" skills.append(skill_path) logger.info(f"Auto loaded skill '{skill_path}' from {autoload_skills_dir}") else: @@ -399,10 +399,10 @@ def _extract_skills_to_robot(bot_id: str, skills: List[str], project_path: Path) - 如果是完整路径(如 "projects/uploads/xxx/skills/rag-retrieve_2.zip"),直接使用该路径 - 如果是简单名称(如 "rag-retrieve"),从以下目录按优先级顺序查找: 1. projects/uploads/{bot_id}/skills/ - 2. skills/{SKILLS_SUBDIR}/ + 2. skills/{PROJECT_NAME}/ - 如果是以 @ 开头的仓库相对路径(如 "@skills/autoload/support/rag-retrieve"),则从仓库根目录直接解析 - 搜索目录优先级:先搜索 projects/uploads/{bot_id}/skills/,再搜索 skills/common,最后搜索 skills/{SKILLS_SUBDIR} + 搜索目录优先级:先搜索 projects/uploads/{bot_id}/skills/,再搜索 skills/common,最后搜索 skills/{PROJECT_NAME} Args: bot_id: 机器人 ID @@ -411,8 +411,8 @@ def _extract_skills_to_robot(bot_id: str, skills: List[str], project_path: Path) """ # skills 源目录(按优先级顺序) repo_root = Path(__file__).resolve().parent.parent - official_skills_dir = repo_root / "skills" / settings.SKILLS_SUBDIR - autoload_skills_dir = repo_root / "skills" / "autoload" / settings.SKILLS_SUBDIR + official_skills_dir = repo_root / "skills" / settings.PROJECT_NAME + autoload_skills_dir = repo_root / "skills" / "autoload" / settings.PROJECT_NAME if not official_skills_dir.exists(): logger.warning(f"Official skills directory does not exist: {official_skills_dir}") skills_source_dirs = [ diff --git a/utils/settings.py b/utils/settings.py index 10014f3..d90a714 100644 --- a/utils/settings.py +++ b/utils/settings.py @@ -24,7 +24,7 @@ TOOL_CACHE_AUTO_RENEW = os.getenv("TOOL_CACHE_AUTO_RENEW", "true") == "true" # Project Settings PROJECT_DATA_DIR = os.getenv("PROJECT_DATA_DIR", "./projects/data") SKILLS_DIR = os.getenv("SKILLS_DIR", "./skills") -SKILLS_SUBDIR = os.getenv("SKILLS_SUBDIR", "support") +PROJECT_NAME = os.getenv("PROJECT_NAME", "support") # Tokenizer Settings TOKENIZERS_PARALLELISM = os.getenv("TOKENIZERS_PARALLELISM", "true")