Agent Zero 技能管理 API 实战解析:api/skills.py 的 list / delete 契约、过滤机制与源码实现
Agent Zero 技能管理 API 实战解析api/skills.py 的 list / delete 契约、过滤机制与源码实现【免费下载链接】agent-zeroAgent Zero AI framework项目地址: https://gitcode.com/GitHub_Trending/ag/agent-zero本文以 api/skills.py.dox.md 为纲结合 api/skills.py 的运行时实现与底层助手层 helpers/skills.py完整拆解 Agent Zero 中/skills端点的请求/响应契约、按项目与 Agent 画像过滤的底层原理、删除动作的安全护栏以及从 Web UI 前端到测试用例的完整调用链。读完后你可以独立维护该端点修改 payload、新增过滤维度或排查删除失败并理解其鉴权、CSRF 与文件系统副作用的边界。一、端点定位与职责划分DOX 文档api/skills.py.dox.md开宗明义该模块拥有skillsAPI 端点职责是列出并管理可用于设置界面和 Agent 技能流程的可用技能。它同时声明了一个重要的目录约定——api/目录被有意保持扁平flat因此每个端点文件旁都配有一个*.dox.md档案并且必须与源码保持同步。职责划分Ownership在 DOX 中写得很明确api/skills.py 拥有运行时实现api/skills.py.dox.md 拥有关于职责、契约、副作用和验证方式的持久化说明。涉及的类是Skills继承自ApiHandler共暴露三个方法class Skills(ApiHandler): async def process(self, input: Input, request: Request) - Output def list_skills(self, input: Input) def delete_skill(self, input: Input)这是api/目录下大量端点的统一模式每个name.py文件里放一个ApiHandler子类DOX 档案则把契约从代码中提炼出来便于维护者在不通读全部源码的情况下快速对齐接口约定。端点是如何被注册和分发的从源码结构看Skills并不是在某个地方显式注册的。helpers/api.py 中的register_api_route把整条/api/path通配路由交给一个动态分发器_dispatch按路径拼出api/path.py先查内置api/目录plugins/plugin/...前缀则查插件目录用load_classes_from_file加载其中的ApiHandler子类校验 HTTP 方法是否允许get_methods()默认只允许POST依据类方法声明层层包裹安全装饰器CSRF、API key、登录鉴权、回环限制并缓存包装后的 handler由watchdog监听api/与usr/api/目录文件变化时清空缓存热加载。因此该端点对外的 URL 就是POST /api/skills而 api/skills.py 只需专注业务逻辑路由、安全与热重载全部由框架兜底。二、安全模型默认开启登录鉴权与 CSRFSkills没有覆写任何类方法声明因此完全继承 helpers/api.py 中ApiHandler的默认值声明默认值含义requires_auth()True需要有效登录会话session 校验requires_csrf()requires_auth()鉴权开启则 CSRF 必须通过X-CSRF-Token头或 cookieget_methods()[POST]仅接受 POSTrequires_api_key()False不要求X-API-KEYrequires_loopback()False不限制仅回环地址DOX 的Work Guidance部分也呼应了这一点除非端点契约明确变更否则必须保留鉴权、CSRF、回环和 API-key 检查。由于delete动作会真实删除文件系统目录这套默认防护不是可选项——CSRF 失败会直接返回 403未登录请求会被重定向到登录页。三、请求/响应契约两种 action一种响应外壳processapi/skills.py#L6-L25是唯一的入口按input[action]分派async def process(self, input: Input, request: Request) - Output: action input.get(action, ) try: if action list: data self.list_skills(input) elif action delete: data self.delete_skill(input) else: raise Exception(Invalid action) return {ok: True, data: data} except Exception as e: return {ok: False, error: str(e)}请求参数一览action参数必填说明listproject_name否字符串去除首尾空白后参与按项目过滤listagent_profile否字符串去除首尾空白后参与按 Agent 画像过滤deleteskill_path是技能目录路径去除空白后不能为空响应外壳统一为两种形态{ ok: true, data: { } } { ok: false, error: Invalid action }两个实现细节值得注意错误不抛 500。process内部自行捕获所有异常并返回ok: false的字典HTTP 仍是 200只有process之外的框架层异常才会走 helpers/api.py 的 500 分支。前端因此只需判断result.ok即可。未知 action 也是受控错误任何非list/delete的取值都会得到{ok: false, error: Invalid action}契约是封闭的。list成功时data是技能数组每项仅含三个字段name、description、path路径已转为字符串delete成功时data为{ok: true, skill_path: ...}。四、list_skills全局扫描加两级过滤4.1 端点层的过滤逻辑list_skills 的流程是先取全量再按需过滤def list_skills(self, input: Input): skill_list skills.list_skills() # 1. 全局技能扫描 # 2. 按项目过滤 if project_name : (input.get(project_name) or ).strip() or None: project_folder projects.get_project_folder(project_name) if runtime.is_development(): project_folder files.normalize_a0_path(project_folder) skill_list [ s for s in skill_list if files.is_in_dir(str(s.path), project_folder) ] # 3. 按 Agent 画像过滤 if agent_profile : (input.get(agent_profile) or ).strip() or None: roots [ files.get_abs_path(agents, agent_profile, skills), files.get_abs_path(usr, agents, agent_profile, skills), ] if project_name: roots.append( projects.get_project_meta(project_name, agents, agent_profile, skills) ) skill_list [ s for s in skill_list if any(files.is_in_dir(str(s.path), r) for r in roots) ] # 4. 组装输出并排序 result [{name: s.name, description: s.description, path: str(s.path)} for s in skill_list] result.sort(keylambda x: (x[name], x[path])) return result几个关键点过滤是累加的同时传project_name和agent_profile时先按项目目录过滤再在结果中保留落在该画像技能根目录之内的条目画像的根目录集合在存在项目时会追加一条项目级路径projects.get_project_meta(project_name, agents, agent_profile, skills)形成项目内画像的第三层查找位置。开发模式的特殊处理runtime.is_development()为真时对project_folder调用files.normalize_a0_path把开发环境下的仓库路径归一化保证is_in_dir前缀匹配不因路径形态差异而失效。输出稳定有序结果按(name, path)字典序排序同一技能名在不同目录下也不会乱序前端可直接渲染。4.2 底层扫描技能根目录、SKILL.md 与 frontmatter 校验端点只是薄封装真正的工作在 helpers/skills.py技能根目录发现get_skill_roots 在无 Agent 上下文即本端点的全局调用时收集仓库根skills/、用户区usr/skills/、各项目的usr/projects/*/.a0proj/skills与agents/*/skills、各 Agent 的agents/*/skills与usr/agents/*/skills以及插件目录plugins/*/skills、usr/plugins/*/skills、插件内 Agent 的plugins/*/agents/*/skills等八类位置含usr/变体。这解释了为什么删除内置插件技能会被拒绝而用户区技能可以管理——两者都在根目录清单里但删除守卫对内置区另有拦截见第五节。SKILL.md 发现discover_skill_md_files 在每个根目录下递归查找SKILL.md跳过任何含隐藏路径段.开头的文件并按路径排序保证确定性。frontmatter 解析与容错split_frontmatter 要求 YAML frontmatter 从文件顶部开始且闭合优先用 PyYAML 解析缺失时退化为一个最小 YAML 子集解析器支持key: value与- item列表。skill_from_markdown 还做了跨平台字段别名兼容name/skill、description/when_to_use/summary、triggers/trigger_patterns/activation、allowed-tools/allowed_tools/tools。校验规则validate_skill 强制name匹配^[a-z0-9-]$、长度 1–64、不能以连字符开头/结尾、不能含连续连字符description必填且 ≤1024 字符compatibility≤500 字符。校验不通过的 SKILL.md 会被跳过并输出一次性警告用_WARNED_SKILL_PARSE_PATHS去重警告形如skill broken-skill skipped: invalid frontmatter at line 4: Unterminated YAML frontmatter——tests/test_skills_runtime.py#L420-L441 精确断言了这条警告文本与同一路径只警告一次的行为。去重策略list_skills 在全局无 Agent场景下不去重同一技能出现在多个根目录会各计一次带 Agent 上下文时按归一化名去重根目录顺序靠前的胜出。本端点调用的是全局模式因此返回结果中可能存在同名技能的不同路径实例path字段正是为消歧而保留的。五、delete_skill五道护栏才允许删目录端点层api/skills.py#L66-L72只做了参数非空检查真正的安全逻辑全部下沉到 helpers/skills.py 的 delete_skill。删除操作按顺序经过五道守卫任何一道失败都会抛异常并被process转成ok: false路径归一化files.get_abs_path(skill_path)转绝对路径开发模式下再经files.fix_dev_path修正路径分隔符最后files.normalize_a0_path统一形态。内置插件保护归一化路径中出现/plugins/且不含/usr/plugins/时直接抛PermissionError(Built-in plugin skills cannot be deleted)。这发生在任何文件系统写入之前——tests/test_skills_runtime.py#L407-L409 的测试名test_builtin_plugin_skill_delete_is_rejected_before_filesystem_delete正是对这一保证的断言。根目录范围检查路径必须落在 get_skill_roots() 返回的某个技能根之内否则抛ValueError(Skill root not in current scope)。这意味着不能借这个端点删除技能树之外的任意目录。目录存在性检查os.path.isdir(skill_path)不成立则抛FileNotFoundError(Skill directory not found)。整目录删除全部通过后调用files.delete_dir(skill_path)递归删除整个技能目录技能 目录 SKILL.md 的粒度。用户区usr/前缀的技能与自定义技能都在合法根目录内可正常删除内置plugins/区则被第二道守卫永久保护与 docs/guides/skills.md 所述技能可在设置中管理的用户心智一致。六、前端调用链Web UI 设置面板如何消费该端点DOX 要求payload 变更时同步更新前端调用方、插件调用方和测试。当前前端调用方是 webui/components/settings/skills/skills-list-store.js一个 Alpine store完整示范了契约的用法加载列表loadSkillsconst response await fetchApi(/skills, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ action: list, project_name: this.projectName || null, }), }); // result.ok ? result.data : 展示 result.error删除技能deleteSkill发送{ action: delete, skill_path: skill.path }成功后弹 toast 并重新loadSkills()刷新列表失败则展示后端error原文。打开技能目录openSkillskill.path还会被直接交给文件浏览器file browser store打开供用户查看技能内容——这也是list响应中保留完整路径字段的原因。客户端搜索关键字过滤matchesSearchQuery在前端本地对name/description/path等字段做包含匹配服务端只负责项目/画像两级过滤。职责切分清晰服务端管可见范围客户端管检索体验。七、维护契约与验证清单继承 DOX 的 Work GuidanceDOX 沉淀的四条维护纪律对照源码均能落地验证保留安全检查修改 api/skills.py 时不要覆写requires_auth/requires_csrf等类方法除非契约明确变更delete的文件系统副作用delete_dir尤其依赖这套防护。同步更新所有调用方payload 形状变更时需同时改 webui/components/settings/skills/skills-list-store.js 等前端调用方、插件调用方与测试。非 JSON 响应用helpers.api.ResponseApiHandler.handle_request 对Response实例与 dict 走不同分支只有需要文件、重定向或特殊状态码时才使用。变更后运行验证DOX 列出了源码搜索识别到的相关测试其中与本端点直接相关的是 tests/test_skills_runtime.py——它用 stub 模块隔离加载helpers/skills.py覆盖了删除守卫PermissionError先于文件系统操作抛出、frontmatter 解析告警、内置 SKILL.md 的合法性回归如 skills/a0-manage-plugin/SKILL.md、以及技能检索排序等行为。无聚焦测试时DOX 建议对浏览器调用方做冒烟验证。此外DOX 明确要求凡请求 payload、鉴权/CSRF 要求、响应形状、路由副作用或 WebSocket 事件契约发生变化都必须同步更新该 DOX 档案本身——这是api/目录扁平化设计能长期可维护的关键约定。八、周边端点与延伸阅读/skills并非孤立端点api/目录还有一组围绕技能包的姊妹端点它们复用同一底层助手api/skills_scan.py扫描已安装技能或上传的.zip技能包extract_skills_zipdiscover_skill_md_filesapi/skills_import.py 与 api/skills_import_preview.py将外部技能包导入到usr/skills/namespace/...导入前先经预览端点确认内容。面向用户侧的使用说明聊天输入中的技能选择器、Pin 技能等见 docs/guides/skills.md技能作者规范见 docs/developer/contributing-skills.md。理解这些端点与本文剖析的/skills端点共享同一套helpers/skills.py底座根目录发现、frontmatter 解析、校验与删除守卫可以形成对 Agent Zero 技能体系的完整认知/skills负责日常列出与管理scan/import 系列负责引入新技能而安全边界由根目录清单与内置插件保护两条主线统一兜底。【免费下载链接】agent-zeroAgent Zero AI framework项目地址: https://gitcode.com/GitHub_Trending/ag/agent-zero创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考