写 Agent skill 的人很多,真去量过一份 skill 有多大的人很少。
我量了。样本是一份线上生产环境正在跑的SKILL.md,不是玩具 demo。结果比我预估的严重 17 倍——我原先拍脑袋估的是 800 token。
先摆数字:
字符 o200k cl100k SKILL.md 24,624 14,147 18,018 references/*.md(3 份) 6,672 2,870 3,254 摘要那一条(name + description + location + XML 包装): 263 157 212摘要 / 正文 = 157 / 14,147 = 1.1%。
那么我们如何做测量呢?
一、怎么量:三行代码,你可以拿自己的 skill 复现
importtiktoken,pathlib enc=tiktoken.get_encoding("o200k_base")# cl100k_base 是老口径,见下p=pathlib.Path("path/to/SKILL.md")print(len(p.read_text()),len(enc.encode(p.read_text())))两个口径都给,是因为它们能差 27%(14,147 vs 18,018)。中文占比高的 skill 差得更多。报数字的时候不写口径,等于没报——这是我踩过的第一个坑:一开始拿 cl100k 量、拿 o200k 的窗口算,白紧张了一轮。
"摘要那一条"指的是 skill 没被激活时,常驻在 system prompt 里的那部分:name+description+ 文件位置 + 外面那层 XML 包装。263 个字符,157 token。
二、渐进披露不是优化,是可行性前提
很多人把"skill 按需加载"理解成一个省钱的优化项——能省就省,不省也能跑。
算一笔账就知道不是。
假设你有 10 个这种规模的 skill(对一个正经系统来说,10 个不多):
全塞正文 141,470 token 每轮付 ← 直接爆窗口,一轮都跑不起来 只放摘要 1,570 token 每轮付 真读一个 +14,147 token 一次性14 万 token 每轮。这不是贵不贵的问题,是一轮都跑不起来。
所以"只放摘要、用到才读正文"这件事,在 skill 数量上到两位数之后,是能不能跑的前提,不是省不省钱的选择。渐进披露省掉的那 98.9%,是你的系统能存在的理由。
反过来说也成立:如果你只有两三个 skill,你确实感觉不到差别——这就是为什么很多人做到一半、形状建了一半就停了。下一节就是这样一个例子。
三、references/建了,一次都没被引用过
这份 skill 是有references/目录的。三个文件,写得也不差:
references/commands.md 1,154 token references/errors.md 1,083 token references/params.md 633 token ──────────── 2,870 token然后我全目录 grep 了这三个文件名和references这个词:
→ 零命中没有任何一个地方引用它们。SKILL.md 正文里没提,代码里没提,索引里没提。这 2,870 token 写下来了,模型永远读不到。
这不是谁偷懒。渐进披露的目录形状建了,线没接上——建目录这个动作有很强的"我做完了"的错觉,而"正文里真的有一句话告诉模型什么时候去读它"这一步,没有任何东西会提醒你漏了。
所以结论是一条闸,不是一句提醒:
加一个 CI 检查:skill 目录里每一个存在的引用文件,都必须在正文里被真的引用过。写了没接线,判红。
形状不等于机制。靠自觉维持的东西,扛不过第二个往里写代码的人。
四、62.8% 的 token 压在一个小节里
把 SKILL.md 按二级标题拆开量:
主流程小节 8,890 token 62.8% 调用方式 1,290 9.1% 错误处理 649 4.6% 其余 13 个小节 3,318 23.5% ────────────────── 14,147 100%一个小节吃掉六成。而这个小节内部,是线性的 Step 0–6:
Step 0 1,645 Step 3 1,033 Step 4 836 Step 4.5 2,529 ← 最大的一块 Step 5 1,080关键的一句话:
一次对话里,模型不可能同时处于 Step 2 和 Step 5。
链路还停在前半段的时候,Step 4.5 那 2,529 token 是纯浪费——它不只是费钱,它还在稀释当前这一步真正重要的约束。模型手上多拿着 2,500 token 跟眼下无关的规则,判断只会更差,不会更好。
所以正确的形状不是一个 14k 的文件:
SKILL.md ~2k 总原则 + 错误铁律 + 流程骨架(每步一行) steps/step0.md 1.6k 走到哪步读哪步 steps/step4_5.md 2.5k ... 14k → 常驻 2k + 按需 1–2.5k这里有一层很多人没走到的区分:
- 第一层渐进披露:description 常驻、正文按需读。——这层,我量的这份 skill做对了。
- 第二层渐进披露:skill 内部按 step 拆,走到哪步读哪步。——这层没做,一激活就是整份 14k。
第二层才是大头。第一层省的是"没用到的 skill",第二层省的是"用到了、但这一步用不上的那 80%"。
五、一个没人提的坑:自动压缩会把你的口径悄悄摘要掉
这条我觉得最值得单拎出来,因为它不报错。
skill 正文一旦被读进来,它就是对话历史里的一条 message。而现在的 agent 框架基本都带自动压缩(context 超阈值就摘要折叠)。
那下一次压缩,就会把你的口径摘要掉。
想想这意味着什么。假设你的口径里有一条明确的禁止性约束:
模型不会跟你说"我忘了那条禁令"。
它只会开始做那件你禁止过的事。
约束丢失是静默的。你不会在日志里看到任何异常,你只会在某一天看到一个不该发生的输出。
两条路:
| 做法 | 代价 | |
|---|---|---|
| A | 把 skill 正文标成压缩时保留 | 保留的都是原文,压缩基本省不下东西 |
| B ✓ | 正文不进历史,每轮按当前 step 重放 | 每轮付 1–2.5k,但约束永远是原文 |
选 B。每轮 1–2.5k 比一次性读 14k 还便宜;更要紧的是——
不可撤回的动作所依赖的约束,不能靠摘要传递。
如果你的 skill 只管"回答得好不好",摘要掉了顶多效果差一点。如果它管的是能不能执行、执行了收不回来的那一类动作,那它就不能活在会被摘要的那一层。
配套的闸很直接:压缩发生之后,断言当前 step 的口径原文仍逐字节在上下文里——不是"还在",是"是原文"。
六、顺带纠一个流传很广的前提:长 prompt 不是延迟的主因
我一开始也是奔着"太长了会慢"去量的。量完发现动机错了。
prefill 是并行的,通常不是瓶颈。真正的耗时是 decode 和多轮 tool 往返。
对延迟来说:减少轮数 ≫ 缩短 prompt。
这有个很实际的推论——如果链路上有些判断根本不需要模型,把它们从 prompt 里拿掉,收益是双份的:prompt 短了,而且少了一整轮往返。
我这份样本上,能拿掉的比想象中多。按性质分,只有最后一类真的需要模型:
| 判断的性质 | 要模型吗 | 为什么 |
|---|---|---|
| 数值比较、阈值判断 | 不要 | 一次整数比较的事 |
| 状态查重、时间窗口 | 不要 | 查一下、算一下的事 |
| 格式硬校验 | 不要 | 拦截点应该在执行前,不是在模型脑子里 |
| 需要理解语义的判断 | 要 | 这才是模型的活 |
| 需要生成自然语言的 | 要 | 同上 |
五类判断三类是规则。拿掉之后 prompt 短一半,而且结果更稳定——同一条输入今天判 A 明天判 B,调用方会疯。
判据一句话:
把模型拔掉,这条链还能不能跑?能跑,就说明它压根不需要模型。
七、什么该进 skill,什么该进代码
上面那条推到底,会撞上一个真实的矛盾:
流程放进代码 =改流程要发版,而外置 skill 的初衷恰恰是"改一句话术不用发版"。
这两条是打架的。所以分界线必须写出来,不能靠感觉:
| 什么 | 放哪 | 为什么 |
|---|---|---|
| 话术 · 口径 · 语气 | skill | 改得频繁;改错了是效果问题,不是事故 |
| 流程顺序 · 能不能进下一步 · 什么时候允许执行 | 代码 | 改得少;走错一步是不可撤回的事故 |
判据一句话:
改错了会不会造成不可撤回的后果?会 → 进代码;不会 → 进 skill。
顺带一个反直觉的观察:我量的这份 skill 用的是"prose 状态机"——那 8,890 token 的 Step 0–6 全靠模型读散文自律,代码一步都不拦。而它没做错。
因为在人在环、逐轮推进的链路里,四条前提都成立:每一步都有外部输入当天然分界;模型从历史里一眼能看出走到哪了;走错了会被当场纠正;真正不可撤回的点只有一个。
但换成无人值守、批量并发的链路——同一批任务并行跑、没有外部输入当分界、历史里分不清哪条是哪条、每一条的输出都不可撤回——上面四条全反。prose 状态机在那边一条都不成立。
同一个做法,一边对一边错。判据不是做法本身,是那四条前提。
八、三条可以直接抄走的闸
如果这篇你只带走一样东西,带这三条。它们全是脚本判的,不靠人自觉:
- 任何单个
SKILL.md≤ 3,000 token(tiktoken 实测,写明口径)。超了必须拆进steps/。 - skill 目录里每一个引用文件,都必须真的被引用过。写了没接线判红——防的就是那 2,870 token 的死内容。
- 压缩发生之后,当前 step 的口径原文仍逐字节在上下文里。不是"还在",是"是原文"。
第 3 条最容易被跳过,因为没有它系统照常跑——它只会在某一天,让模型做出一件你明确禁止过的事。
最后
这篇里没有一个数字是估的,全是拿一份真在线上跑的 skill 量出来的。你可以拿自己的 skill 复现——我猜大部分人量完的表情会跟我一样。
我最大的收获不是那些数字,是量完之后意识到的这件事:
skill 不是"给模型的说明书",它是一份要付每轮成本的常驻资产。
按说明书写,你会写出一份完整、自洽、24,624 字符的文档。
按资产写,你只会问一个问题:这一刻,模型手上必须有哪一段?
那份 14k 的文件,是照第一种写法写出来的。它写得很好——问题在于,它是一份好说明书。