最近 AI Agent 圈子几乎被 Skill 刷屏了。Claude Code 在讨论 Skill,Codex 在讨论 Skill,连 IDE 型 Agent 也开始把技能包做成规范目录。但一个奇怪的现象是:很多人 Skill 越装越多,Agent 却越来越不好使。装了几十个技能包之后,让它整理文档,它先去翻错的那个 Skill;让它批量改文件,它反复读取帮助说明;Token 烧得比之前快,任务完成率反而下降。
这篇文章就把这个问题拆开讲清楚。先解释 Skill 到底是什么,再分析“装多了难用”背后的机械原理,然后给出一套从开发、测试到治理的完整方法,最后附上常见问题排查表。无论你是在用 CLI 型 Agent、IDE 型 Agent,还是自己搭建团队 Agent 平台,这篇文章都可以直接当参考。
1. Skill 生态现状速览
| 维度 | 现状说明 |
|---|---|
| Skill 是什么 | Agent 的技能包,由“说明文档 + 脚本 + 模板 + 约束”组成,让 Agent 按固定流程执行任务 |
| 常见形态 | SKILL.md 作为入口,scripts 目录放可执行脚本,assets 放参考素材 |
| 主要战场 | CLI Agent、IDE Agent、桌面级 Agent,以及团队自建 Agent 平台 |
| 核心收益 | 复用流程、降低提示词长度、让 Agent 稳定执行重复性任务、便于团队沉淀经验 |
| 核心风险 | 元信息膨胀、上下文被多吃掉、路由误用、脚本依赖冲突、来源不可控 |
| 是否需要特殊硬件 | 不需要,Skill 本身通常很轻,主要占用的是 Agent 运行时的 Token 资源和磁盘空间 |
| 适合读者 | Agent 重度用户、AI 工具集成开发者、团队基础设施负责人 |
本质上 Skill 不是新概念,它就是插件生态在 Agent 时代的变体。过去浏览器装插件,装多了浏览器卡;编辑器装插件,装多了启动慢;现在 Agent 装 Skill,装多了上下文被吃、决策变乱。问题表现不一样,底层逻辑几乎一样:没有约束的增长,最终都会反噬系统本身。
2. 为什么 Skill 越多,Agent 反而越难用
很多人以为 Agent 变笨是模型能力问题,其实大部分时候是 Skill 体系失控导致的。下面五个原因是最常见的“隐形杀手”。
2.1 上下文放大器:每个 Skill 都在偷 Token
Agent 在执行任务前,需要知道“自己有哪些能力可以用”。这意味着每个 Skill 的元信息,尤其是 description,要在任务规划阶段被读取。假设一个 Skill 的描述有 200 到 500 Token,50 个 Skill 就是 1 万到 2.5 万 Token,这还没算命中后读取完整 SKILL.md 的成本。
上下文越长,注意力越分散,模型对用户真实指令的遵循能力越差。很多用户觉得 Agent“变傻了”,其实不是模型变傻,而是它的视野里塞满了技能包的说明书。
2.2 路由退化:候选越多,选错概率越高
Agent 选择 Skill 的过程很像信息检索。候选集越大,检索精度越低。尤其是当很多 Skill 的描述都长得很像时,比如十几个都叫“PDF 处理”“文档整理”“文件转换”,Agent 在第一步就可能选错。
选错之后的表现是:明明应该用 PDF 总结 Skill,它却去读了 OCR Skill 的 README,绕了一大圈才回到正确路径,甚至直接放弃。每次误路由都在浪费 Token,也都在拉低任务成功率。
2.3 Skill 之间互相干扰
Skill 不是完全隔离的。它们可能共用同一个目录,使用同一个 Python 环境,甚至都往同一个输出路径写文件。装得多了,就可能有同名脚本覆盖、依赖库版本冲突、输出格式不一致的问题。
更隐蔽的是干扰:A Skill 的输出格式和 B Skill 的输入格式对不上。单独跑 A 或单独跑 B 都正常,一旦让 Agent 把 A 的结果喂给 B,就报错。这种问题排查起来非常费时间,因为报错信息往往不在 Agent 层,而在脚本层。
2.4 描述质量参差:等于让 Agent 盲选
Skill 能不能被正确触发,description 说了算。但很多人从网上下载 Skill 后,从不改描述,几十个 Skill 全是“Handles PDF”“A tool for summarize”“Write skill”这种模糊文案。
对模型来说,这些描述没有区分度。它只能靠猜。猜对了是运气,猜错了是常态。你的 Skill 装得再多,如果描述不能帮 Agent 做决策,那它就是一个又一个的“僵尸能力”。
2.5 版本与来源失控
下载 Skill 容易,维护 Skill 难。很多人的 skills 目录里,装完就再也不管,没有更新记录,没有测试用例,没有负责人。哪天 Agent 突然不稳定,想回滚都不知道这个 Skill 是从哪个仓库、哪个版本克隆来的。
这种不可控的 Skill 积累到一定量,整个 Agent 工程就会变成黑盒:看起来什么都能干,实际上什么都查不清楚。
3. Skill、Agent、Tool 的区别到底是什么
要解决 Skill 乱象,先要分清三个概念:Agent、Tool、Skill。
Agent 是决策和执行调度者。它接收用户需求,拆解任务,决定调用什么能力,最后汇总结果。Agent 的核心是“判断”和“编排”。
Tool 是单一可执行单元。通常是一个函数、一个脚本或者一个命令行工具,输入输出非常明确。比如“读取 PDF 文本”“调用翻译 API”“执行 shell 命令”,这些都是 Tool。
Skill 是组合单元。它等于“给 Agent 看的使用手册”加上“可执行脚本”加上“模板和约束”。Skill 可能只封装一个 Tool,也可能把多个 Tool 串成一个流程。
为了方便记忆,可以做这样的类比:
| 角色 | 类比 |
|---|---|
| Agent | 项目经理 |
| Tool | 扳手、螺丝刀 |
| Skill | 一份带操作规程的工具箱,告诉项目经理什么时候打开 A 箱、什么时候打开 B 箱,以及按什么步骤操作 |
所以,真正影响用户体验的不是 Skill 底层用了什么语言、什么框架,而是三个问题:Agent 能不能在正确的时候选中它;选中之后能不能按预期执行;执行结果能不能稳定复用。
4. 高质量 Skill 应该长什么样
先说结论:一个高质量 Skill 的核心不是脚本写得多炫,而是 SKILL.md 写得到不到位。脚本是给机器执行的,SKILL.md 是给模型“看”的。模型能不能正确理解、能不能在正确场景触发,全靠这份文档。
4.1 标准目录结构
skills/ └── doc-summarizer/ ├── SKILL.md ├── scripts/ │ ├── ingest.py │ └── summarize.py ├── templates/ │ └── summary.md.j2 └── tests/ ├── test_ingest.py └── fixtures/ └── sample.txt- SKILL.md:Skill 的入口,Agent 会优先读取这个文件。
- scripts:可执行脚本,负责具体逻辑。
- templates:输出模板,非必需,但有助于固定输出格式。
- tests:测试用例和测试素材,用于回归验证。
4.2 SKILL.md 怎么写
下面是一个通用模板,具体字段以你使用的 Agent 平台文档为准,但“触发时机 + 不适用的场景 + 参数说明 + 用法”这四个部分建议保留。
--- name: doc-summarizer description: 在用户需要对文档生成摘要、提取要点或转成 Markdown 时使用。适用于 txt、md、以及可提取文本的 PDF。不适用于扫描件、图片型 PDF,也不用于问答对话。 version: 0.2.0 platforms: [cli] --- # 文档摘要 Skill ## 触发时机 - 用户说“帮我总结这篇文档” - 用户给出文件路径,并要求输出要点 ## 不适用场景 - 需要 OCR 的扫描件,请调用 ocr-skill - 需要多轮问答,请使用普通对话 ## 参数说明 - input_dir: 输入文件目录 - output_dir: 输出目录 - max_length: 摘要最大长度,默认 500 ## 用法 1. 检查 input_dir 是否存在 2. 遍历目录下所有 .txt / .md / .pdf 文件 3. 调用 scripts/summarize.py 生成摘要 4. 将结果写入 output_dirdescription 这段是重中之重。不要只写“Handles PDF”,至少要包含四要素:什么时候用、什么时候不用、输入是什么、输出是什么。描述写得越具体,Agent 路由的准确率越高。
一个检验方法是:把你自己的 Skill 描述拿给另一个同事看,如果他看完不知道这个 Skill 适合什么任务、不适合什么任务,那描述就该重写。
4.3 一个可执行脚本的最小示例
#!/usr/bin/env python3 """doc-summarizer 的核心脚本:读取文本文件并生成摘要。""" import argparse from pathlib import Path def summarize_text(text: str, max_length: int = 500) -> str: # 实际场景会调用大模型 API 或本地模型 # 这里只给出流程骨架,按需替换 return text[:max_length] def main() -> None: parser = argparse.ArgumentParser(description="Summarize a text file.") parser.add_argument("--input", required=True, help="input file path") parser.add_argument("--max-length", type=int, default=500) args = parser.parse_args() input_path = Path(args.input) if not input_path.exists(): raise FileNotFoundError(f"file not found: {input_path}") text = input_path.read_text(encoding="utf-8") summary = summarize_text(text, args.max_length) print(summary) if __name__ == "__main__": main()注意几个细节:脚本要有参数解析入口、要有文件存在性检查、要有编码声明、要有可替换的内部逻辑。这样 Agent 调用时才能稳定拿到结果,而不是直接被异常打断。
5. Skill 的接口调用与批量任务设计
很多 Skill 真正要做的不是跑一个脚本,而是批量调用外部能力。比如批量总结文档、批量处理图片、批量转换格式。这时候 Skill 的脚本就不能只写“单次处理”,还要考虑批量、幂等、重试和日志。
5.1 批量调用外部 API 的 Python 示例
import os import time from pathlib import Path import requests # 密钥从环境变量读取,不要写进 Skill 文件或 git 仓库 API_URL = os.getenv("SUMMARIZE_API_URL") API_KEY = os.getenv("SUMMARIZE_API_KEY") def send_request(text: str, retries: int = 3) -> dict: for attempt in range(retries): try: resp = requests.post( API_URL, headers={"Authorization": f"Bearer {API_KEY}"}, json={"text": text[:10000], "max_length": 500}, timeout=120, ) resp.raise_for_status() return resp.json() except requests.RequestException as exc: if attempt == retries - 1: raise time.sleep(2 * (attempt + 1)) def batch_summarize(input_dir: Path, output_dir: Path) -> None: output_dir.mkdir(parents=True, exist_ok=True) for file_path in sorted(input_dir.glob("*.txt")): text = file_path.read_text(encoding="utf-8") result = send_request(text) out_file = output_dir / f"{file_path.stem}.md" out_file.write_text(result["summary"], encoding="utf-8") print(f"[OK] {file_path.name} -> {out_file}") if __name__ == "__main__": batch_summarize(Path("./input"), Path("./output"))5.2 批量任务命令行入口
python scripts/batch_summarize.py \ --input ./pdfs \ --output ./summaries \ --max-length 800如果你不希望每次传参都这么长,可以把默认配置放到一个 JSON 文件里:
{ "input_dir": "./inputs", "output_dir": "./outputs", "max_length": 800, "retry_times": 3, "timeout": 120 }5.3 批量任务设计的几条铁律
第一,幂等。脚本重启后,已经生成过的输出应该跳过或覆盖,不能重复调用 API。第二,日志。每条成功和失败记录都写到独立日志文件,方便事后审计,而不是只往控制台打印。第三,限流。批量任务要控制并发数和重试间隔,否则外部 API 很容易触发限流,导致大量失败。第四,失败隔离。单个文件失败不能中断整个任务,要把失败项单独记录,最后汇总重试。
6. 性能与资源开销:Token 是怎么被吃掉的
Skill 本身不占多少磁盘,但它对 Agent 的 Token 消耗影响非常大。要解释清楚这个概念,可以用一个简化的执行流程:
在任务规划阶段,Agent 会读取当前可用的 Skill 索引,也就是一堆 Skill 名称和 description。命中一个 Skill 后,Agent 再读取完整的 SKILL.md 内容,然后根据说明调用脚本。脚本执行时产生的输出,又会回到对话上下文里继续参与推理。
所以,Token 开销主要出现在三处:
- 所有 Skill 的 description 进入上下文;
- 命中后完整 SKILL.md 进入上下文;
- 脚本执行结果进入上下文,尤其当脚本输出很长时。
如果你装了上百个 Skill,光是 description 就可能吃掉数万 Token,具体数值取决于平台实现和描述长度。这还没算命中后读取的完整文档。这也是为什么很多 Agent 在装了 Skill 之后,响应速度明显变慢、单次任务费用明显变高。
怎么观察?优先看 Agent 平台的 token usage 日志,确认单次任务的输入 Token 是不是异常高。其次看请求日志里有没有频繁读取不相关 Skill 的痕迹。如果一次简单问答,日志里却出现了三四个 Skill 的文档读取记录,就说明路由已经乱了,该做减法。
怎么优化?
第一,精简 description。把每个 Skill 的描述压到“触发条件 + 输入 + 输出”三行以内,能显著降低索引开销。第二,延迟加载。完整使用文档放在 SKILL.md 里,命中后再读取,不要在索引阶段全部塞进上下文。第三,拆分资产。大文件、参考素材不要堆在 Skill 目录里,做成按需下载或者外部链接。第四,重计算下沉。如果 Skill 每次都要调用大模型跑一遍长文本,不如独立部署一个 HTTP 服务,让 Skill 脚本只做请求转发。
7. Skill 体系的治理与“减肥”
Skill 需要治理,这不是开发者的洁癖,而是实际工程需求。一套完整的治理流程可以按下面几步走。
7.1 先盘点再动手
把你当前所有 Skill 列出来,记录名称、体积、description 长度、最近触发时间。可以用一个简单脚本快速统计:
# 列出所有 Skill 及其体积,按体积从大到小排序 find skills -maxdepth 2 -name SKILL.md | while read f; do dir=$(dirname "$f") size=$(du -sh "$dir" | cut -f1) desc_len=$(head -20 "$f" | grep '^description:' | wc -c) echo "$size $desc_len $dir" done | sort -rh | head -20这个脚本只看体积和描述长度,不一定能直接判断 Skill 有没有用,但能帮你快速发现“体积异常大”“描述异常短”的嫌疑对象。
7.2 分级管理
把 Skill 分成四层:
- 核心层:团队每天都在用的能力,稳定触发,重点维护。
- 常用层:经常使用但不是核心路径,按需加载。
- 实验层:测试中的新能力,随时可能被砍。
- 禁用层:不再使用但暂不删除,用于回滚观察。
分级之后,新 Skill 默认进实验层,观察一段时间再决定是否晋升。
7.3 定几条质量红线
比如:
- description 少于 50 字,不进入核心层。
- 没有测试用例,不进入常用层。
- 来源不明、无法确认维护者的 Skill,不接受。
- 连续 30 天没有被触发,标记为“待删除”。
- 体积超过某个阈值,必须解释为什么不能拆成独立服务。
质量红线不是限制,而是保护。它们能防止你的 skills 目录再一次变成无人维护的灰色地带。
7.4 收口到版本管理
所有 Skill 放进一个 git 仓库,每次增改都要有 commit,更新要写 changelog。发布前做一次 diff review,重点看脚本变更和描述变更。这样出了生产事故,你能快速定位是哪个 Skill、哪次改动引入的问题,然后一键回滚。
7.5 存量清理
先禁用不删除。把可疑 Skill 移到禁用层,跑一段时间,确认没有任务再依赖它们之后,再物理删除。一次性大规模删除很容易出问题,因为很多 Skill 之间存在隐式依赖,你未必记得住,模型的记忆更不可靠。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| Agent 从不调用某个 Skill | description 与任务描述不匹配 | 查看日志确认是否扫描到该 Skill | 重写 description,写清触发条件和禁用场景 |
| Agent 调用错 Skill | 多个 Skill 描述相似、边界不清 | 对比各 Skill 的 description | 收窄描述范围,增加“不适用场景” |
| 启动或任务规划变慢 | Skill 数量过多、description 过长 | 查看 token usage 日志 | 精简 description,延迟加载完整文档 |
| Token 消耗明显上升 | 每个 Skill 的元信息都进入上下文 | 对比装 Skill 前后的 token 数据 | 降低 Skill 数量,删除非必要描述 |
| 脚本执行报错 | Python/Node 依赖缺失或路径不对 | 单独运行脚本看错误信息 | 在 SKILL.md 写清前置依赖和安装命令 |
| 批量任务卡住 | 外部 API 限流、脚本无超时 | 查看任务日志是否有 timeout | 增加 retry 和 timeout,降低并发 |
| 输出结果格式不一致 | 缺少输出模板或约束说明 | 查看不同批次输出对比 | 在 SKILL.md 增加格式要求,使用模板渲染 |
| 同一 Skill 之前能用现在不能用 | 依赖升级或脚本被修改 | 查看 git 变更记录 | 回滚到上一个可用版本 |
| Agent 反复读取不相关 Skill | 候选集太大、路由精度下降 | 查看完整请求日志 | 精简 Skill 列表,启用延迟加载 |
| 下载的 Skill 来源异常 | 第三方仓库存在恶意或不可靠代码 | 不要直接运行,先审查脚本 | 只从可信仓库获取,脚本必须经过 review |
9. 安全、合规与最佳实践
Skill 本质上是一段可执行代码。它虽小,风险却不小。安装前不看代码、运行前不确认来源、执行时不校验输入输出,这些都是必须避免的坏习惯。
在安全与合规层面,建议遵循下面几条原则:
第一,只从官方市场或受信任的仓库获取 Skill。任何陌生来源的 Skill,先克隆到隔离目录,人工检查 SKILL.md 和 scripts 目录下的代码,再决定要不要接入主环境。
第二,脚本里禁止硬编码密钥。API Key、密码、Token 一律从环境变量或密钥服务读取。Skill 文件很可能被同步到团队的 git 仓库,密钥一旦提交,泄露就是时间问题。
第三,涉及人脸、声音、版权素材、公司内部文档的场景,必须确认使用授权。比如一个 Skill 用于批量总结内部文档,那文档脱敏和访问范围要提前设计好;一个 Skill 用于生成人物图片或克隆声音,必须有当事人的明确授权,且只能用于合规测试环境。
第四,生产环境先隔离运行。新 Skill 先在小范围任务里测试,确认输出质量稳定后再放开到批量任务。不要让一个刚下载、没人看过的 Skill 直接在关键业务链路上跑。
第五,批量任务要可观测、可回滚。每个批量任务都记录输入目录、输出目录、执行时间、成功项、失败项。出问题的时候,第一时间停掉任务,而不是继续重试。
第六,团队协作时给每个 Skill 指定负责人。Skill 要能回答“坏了找谁”“怎么验证”“有没有测试用例”这三个问题,否则就不算一个合格的工程资产。
结语
Skill 不是越多越强,而是越精越强。装一个能稳定触发的 Skill,比装十个描述模糊、互相干扰的 Skill 有用得多。如果你的 Agent 已经出现“变笨”的迹象,先按第 7 节的清单做一次瘦身,把没人触发、来源不明、描述糟糕的 Skill 全部清掉,再观察任务成功率。如果你正准备写自己的第一个 Skill,就从第 4 节的 SKILL.md 模板开始,把 description 写清楚,把不适用场景写明白,然后加一个最小测试用例。
把 Skill 体系当成长期资产来维护,而不是一次性的“下载安装”,你才能真正感受到它带来的效率提升。后面想继续深入的话,可以研究 Skill 的自动生成、Skill 之间的编排,以及更复杂的依赖管理,这些方向都有很多文章可做。