上下文省了 857 倍,路由却瞎掉 72%:50 个 Agent Skill 渐进式加载的实测复盘

上下文省了 857 倍,路由却瞎掉 72%:50 个 Agent Skill 渐进式加载的实测复盘
背景技能越装越多账单先撑不住给 Agent 装技能这件事现在几乎没人再讨论要不要渐进式加载了——答案显然是要。但很少有人真的去称一称省下来的到底是多少以及省完之后还剩多少信息够 Agent 做出正确的路由判断。我把本机装着的 50 个 Skill 全量扫了一遍用cl100k_base逐字节数 token。结论有两半一半符合预期一半不符合全量塞进上下文要6,768,295 token只常驻描述层要7,890 token压缩比857.8 倍但代价是Agent 判断该不该用这个技能的全部依据被压进了平均 158 token 的一行字里——而这一行还有72% 的技能会被截断。Skill / MCP / Agent Tool 这类扩展机制的共同形态是一个目录一份说明书外加若干脚本和参考资料。装十个还好装到五十个问题就变成了一道很朴素的算术题——这些说明书要不要全部塞进模型的上下文塞那每一轮请求都要为五十份说明书付一次 token。不塞Agent 又怎么知道自己有这个能力行业给出的答案叫渐进式加载progressive disclosure启动时只注入技能的名字和一句描述命中了再读正文正文里再指路去读参考资料。听起来很合理。我想知道的是这套设计在真实语料上的量化收益以及它把风险转移到了哪里。方法把一个技能有多贵拆成三层来称我按加载时机把每个技能的内容切成三层层内容加载时机L1 描述层frontmatter 里的namedescription会话启动全量常驻每轮重发L2 正文层SKILL.md去掉 frontmatter 的正文路由命中后一次性读入L3 引用层references/ templates/ scripts/ assets/下的文本文件正文里再指路才读称重脚本大约 200 行核心就是分层遍历加 tokenizeimport re, tiktoken from pathlib import Path ENC tiktoken.get_encoding(cl100k_base) FM_RE re.compile(r\A---\r?\n(.*?)\r?\n---\r?\n?, re.S) BUNDLE_DIRS (references, templates, scripts, assets, examples) def ntok(s: str) - int: return len(ENC.encode(s, disallowed_special())) def scan_skill(skill_md: Path) - dict: raw skill_md.read_text(encodingutf-8, errorsreplace) m FM_RE.match(raw) fm, body (m.group(1), raw[m.end():]) if m else (, raw) name, desc scalar_field(fm, name), scalar_field(fm, description) l1 ntok(f- {name}: {desc}) # 真正常驻的只有这一行 l2 ntok(body) # SKILL.md 正文 l3 sum( # 技能自带的参考资料 ntok(p.read_text(encodingutf-8, errorsreplace)) for sub in BUNDLE_DIRS for p in (skill_md.parent / sub).rglob(*) if p.is_file() and p.suffix.lower() in TEXT_EXT ) return {name: name, desc_chars: len(desc), l1_tokens: l1, l2_tokens: l2, l3_tokens: l3}有两个细节值得说明。一是 L1 我按- name: description的清单行格式计算而不是只数描述本身因为实际注入系统提示时这些结构字符也要付费。二是 tokenizer 固定用cl100k_base不同模型的分词器会有几个百分点差异但不影响量级结论。扫描范围是三个真实目录用户级技能、连接器技能、内置技能共 50 个SKILL.md、486 个引用层文件。python measure_skill_context.py \ --roots ~/.workbuddy/skills \ ~/.workbuddy/connectors/skills \ app/resources/builtin-skills \ --desc-limit 150 --turns 20 \ --out data/skill-context-budget.json图1三层加载模型的实测体量。常驻的 L1 只占三层合计的 0.12%但它承担了 100% 的路由决策。实证一857 倍压缩比和一条被忽略的幂律第一批数字如下层token 合计每技能均值占比L1 描述层7,890157.80.12%L2 正文层174,6233,492.52.58%L3 引用层6,585,782中位 6,11197.30%三层合计6,768,295——100%857.8 倍的压缩比在意料之中。真正值得停一下的是 L3 的分布形态均值 131,716中位数只有 6,111差了 21 倍。排序之后原因很清楚两个金融数据类技能westock-data3,058,438 token、westock-tool2,225,202 token合计吃掉了引用层的80.2%Top5 占89.5%。剩下 45 个技能挤在长尾里中位数只有六千出头。这条幂律对工程决策的意义是引用层的容量风险不是均摊的而是集中在少数几个数据型技能上。如果你的 Agent 一次性读入了westock-data的某个参考文件单次读取就可能顶掉半个上下文窗口。给引用层做分层加载和单文件体积上限收益远大于给那 45 个长尾技能做优化。图2引用层呈明显幂律分布Top2 技能占 80.2%。均值被两个数据型技能拉高到中位数的 21 倍。实证二唯一常驻的路由信号72% 撞上了 150 字符的墙省下 99.88% 的上下文之后Agent 判断这个任务该不该用某技能的全部依据就只剩 L1 那一行。所以下一个问题是这一行够用吗先要确定它有多长。我没有直接采信文档而是拿技能清单里实际渲染出来的、末尾带省略号的四条描述反推技能清单里的描述字符数csdn-auto-flow148nano-tools-matrix-audit149lark-apps149tencent-docs-routing149四个独立样本全部落在 148–149加上省略号正好 150。截断阈值是 150 字符这是一个硬上限不是建议值。然后拿这个实测阈值去量 50 个技能的描述长度最短 18 字符中位232 字符最长915 字符36 / 50 72%的技能描述超过 150 字符会被截断累计被截掉7,791 个字符。被截掉的是什么内容我用一组触发语模式当用户、时使用、适用于、Use when、遇到、场景等扫了一遍有 6 个技能它的什么时候该用我整句只出现在 150 字符之后——也就是说在 Agent 实际看到的清单里这句话根本不存在。这 6 个技能是csdn-auto-flow、nano-tools-matrix-audit、lark-apps、lark-drive、neodata-financial-search、westock-data。它们的描述前半段都在详细罗列我能做什么把什么时候用我留到了最后——而恰恰是后者才是路由需要的信号。这就是渐进式加载真正的代价所在它没有消灭成本它把成本从算力转移到了描述的信息密度上。而 72% 的超限率说明绝大多数技能作者是按写文档的习惯在写 description不是按写路由索引的习惯。图3描述层长度分布对照 150 字符实测截断线。中位数 232 字符已越线6 个技能的触发条件整句落在线外。复算盈亏平衡点根本够不着有人会问渐进式加载多了一次读正文的往返会不会得不偿失把常驻层每轮重发这件事算进去就清楚了。设一次会话 20 轮。对照组是半急切式——把所有SKILL.md正文也塞进系统提示不含引用层这是不少项目的实际做法方案单轮常驻20 轮累计渐进式只驻 L17,890157,800半急切L1 L2 常驻182,5133,650,260差额——3,492,460差额 349 万 token。按 ¥2 / 百万输入 token 这个假设单价折算约 ¥6.98 —— 单价只是为了给量级一个直观参照不代表任何厂商实际报价。关键在盈亏平衡点渐进式要为命中的每个技能额外付一次 L2平均 3,492.5 token且只付一次不随轮次重发。要追平半急切式需要3,492,460 / 3,492.5 ≈ 1,000个技能正文被加载。语料里总共才 50 个技能。结论很干脆在 20 轮量级的会话里渐进式加载不存在被反超的可能。争论点从来不在要不要渐进式而在描述层要怎么写。局限这次测量没能证明什么得把边界说清楚否则上面的数字容易被过度引用。没有测路由准确率。我测的是触发条件被截断了多少这是路由失败的必要条件不是充分条件。模型完全可能靠前 150 字符里的功能描述猜对。要证明因果得跑一组带标注意图的 A/B 评测。只有一台机器的一份语料。50 个技能里有 27 个来自同一个连接器家族lark-*写作风格高度同质描述偏长可能是这批技能的团队习惯不能推广成行业普遍现象。150 字符是黑盒反推。四个样本一致落在 148–149但这是从渲染结果反推的经验值。不同宿主、不同版本的实现阈值可能不同换环境需要重测。tokenizer 不等价。cl100k_base对中文的切分与实际使用的模型可能有几个百分点偏差量级结论不受影响精确账单会有出入。L3 只统计了文本文件。二进制资源、图片没有计入实际的引用层落盘体积比 token 数反映的更大。结论与下一步一句话方法论渐进式加载省的是算力花的是描述的信息密度省下 857 倍之后唯一该被反复打磨的就是那 150 个字符。落到可执行的三条把 description 当路由索引写不当摘要写。触发条件当用户…时使用、遇到 X 格式文件时放前 80 字符能力清单放后面——反正后面大概率会被切掉。给引用层设单文件体积上限。幂律分布意味着风险集中在少数几个数据型技能上对这几个做拆分和索引化比优化长尾 45 个更划算。把描述长度检查加进 CI。一行断言就够了assert len(description) 150。这次 72% 的超限率本质上是没人在提交时量过。称重脚本已经放进下面这套单文件工具矩阵里可以直接对自己的技能目录跑一遍。开源地址矩阵门户GitHub - wangzifan396-wzf/WB: 一个标签页收纳你全部的开发者工具。24 个单文件、零依赖、本地优先的开源工具下载即用。 · GitHub单文件工具聚合器GitHub - wangzifan396-wzf/nano-workbench: Single-file tabbed launcher for the nano-tools matrix - one tab, all 28 tools, instant switch. Zero-dep. Part of nano-tools. · GitHubGitHub 组织主页wangzifan396-wzf (WangZi) · GitHub