最近在整理 AI Agent 项目的时候,我把 HagiCode Skill 这套技能管理系统的设计思路完整梳理了一遍。说句实在话,现在提到 AI Agent 开发,几乎绕不开"skill"这个概念——从 skill 脚本、skill 插件到各种 skill 仓库,大家都在做同一件事:把大模型能够执行的能力拆成独立、可描述、可复用的模块。HagiCode Skill 有意思的地方在于,它不是一个只会"读 manifest 然后调函数"的玩具框架,而是把技能从描述、注册、装载、执行到治理的完整链路都做了规范。这篇文章我想从工程实现的角度,把一套可扩展 AI 技能管理平台的核心模块逐层拆开聊,覆盖技能描述规范、注册中心与热加载、执行引擎、权限沙箱、技能编排,以及我在实际落地中踩过的坑。不管你是想给自家 Agent 加扩展能力,还是在纠结"技能系统怎么设计才算不过度设计",这篇应该都能给你一些可以直接抄的答案。
1. 为什么需要 Skill 抽象层:AI 应用从"函数堆叠"到"能力市场"的分水岭
1.1 单体 Agent 的失控是从"再加一个工具"开始的
大多数团队做 AI 应用,都要经历一个从兴奋到痛苦的过程。最开始,你在代码里写了三五个函数,搜个网页、算个表达式、读个文件,通过 function calling 塞给大模型,跑得很顺。三个月以后,需求方开始提各种新能力:查股票、写周报、解析 PDF、调内部 API……每个能力都是一段硬编码逻辑加一段 Prompt 描述。系统提示词膨胀到几千 token,主程序里堆了几十个 if 分支,模型频繁选错工具,排查一次调用链路要翻半天日志。
我见过最典型的例子:某项目把工具定义全部塞在系统提示词里,每次新增工具都要重新发版。最难受的不是改代码,而是你根本不敢动——因为你不知道改了某个工具的 description 之后,模型在别的场景下会不会突然开始误调它。这种状态下的 Agent,本质上是一个"带了很多口袋的胖子",每个口袋里装了什么只有你自己知道,模型根本不知道什么时候该掏哪个口袋。
1.2 Skill 抽象到底抽象了什么
HagiCode Skill 解决的问题,就是把"能力"从 Agent 主程序里彻底解耦出来。它定义了一个最小但完整的单元:
- 一份描述(Manifest):告诉模型这个技能是干什么的、什么场景下用、需要什么参数、有什么限制;
- 一份实现(Implementation):真正干活的代码、脚本或外部服务调用;
- 一组元数据(Metadata):版本号、作者、依赖、权限声明、超时时间。
模型看到的只有描述,工程系统负责装载和执行实现。这样一来,"新增一个能力"就变成了"往技能目录里放一个新文件夹",不再需要改 Agent 核心代码,也不用重新发版。从架构上看,这个技能层就是夹在大模型和底层工具之间的一道标准化网关,所有技能都走同一套注册、校验、执行、审计流程。
1.3 可扩展这件事,有三个层次
很多人理解的"可扩展"就是"能随便加新功能",但真正做技能平台,扩展性要拆成三个层次来看:
- 新增扩展:往目录里丢一个新技能包,不碰核心代码,系统启动时自动发现并注册;
- 版本扩展:已有技能升级到 v2,不影响旧调用方,必要时支持灰度切换;
- 组合扩展:多个原子技能编排成复合技能,比如"搜索 → 提炼 → 生成报告"串成一条工作流,形成新的高级能力。
这三层都做到了,技能系统才算真正"可扩展"。只有第一层的话,不过是一个花哨的插件加载器而已。HagiCode Skill 的架构设计里,这三个层次分别由注册中心、版本机制和技能编排模块来承接,后面的章节我会逐个拆。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技能描述规范:模型选择技能时,其实是在"读"你的清单
2.1 Manifest 是一份技能的"自我介绍"
技能清单(Manifest)是整个技能系统里最先被消费的文件。系统启动时读它来注册技能,模型做工具选择时读它来判断要不要调用这个技能。HagiCode Skill 用的是 YAML 格式,典型结构长这样:
yaml复制name: web_search
description: >-
在互联网上检索关键词并返回排序后的网页结果列表,每条结果包含标题、链接和摘要。
适合需要获取最新信息、事实核查、资料收集、对比多方观点的场景。
无法回答问题时不要使用,优先尝试本地知识库。
version: 1.2.0
author: hagicode-team
runtime: python3
timeout: 30s
parameters:
type: object
properties:
query:
type: string
description: 搜索关键词,尽量包含主体和时间范围,比如"2024年新能源车销量"
max_results:
type: integer
description: 返回结果条数,1到10之间
default: 5
required:
- query
permissions:
- network.outbound
这里面每个字段都有讲究。name 是技能的唯一标识,全局不能重复;description 是给模型看的使用说明,直接决定了模型会不会在正确的时机选择这个技能;parameters 用 JSON Schema 描述入参格式;permissions 是技能运行所需的权限声明,后面讲安全沙箱时会详细展开。
2.2 Description 写作的三个层次,决定了模型选技能的准确率
我在实际测试中发现,description 写得怎么样,比参数定义还影响调用准确率。把描述分成三个层次,逐层检查基本不会出大问题:
- 直觉层:一句话说清"这个技能是做什么的"。要让模型扫一眼就能建立映射,比如"web_search" vs "网页信息检索",前者更自然。
- 触发层:说清楚"什么场景下应该用我"。这是很多技能清单最容易漏掉的部分。你需要在描述里给出正例和负例,比如上面例子里的"无法回答问题时不要使用,优先尝试本地知识库",就是在帮模型做排除法。
- 参数层:每个参数要说明含义、取值范围、默认行为。不要只写一个
max_results: integer,模型不知道填 3 还是填 100 合适。写清楚"1到10之间,默认5",模型给出的参数就会靠谱很多。
