SKRT:技能库超过 100 个后,先本地路由再读 SKILL.md
Claude Code、Gemini CLI、Codex、Cursor 这类编码 Agent 都靠 SKILL.md 技能文件扩展能力。技能一旦装到 100+,每个会话开始都要把「技能名 + 描述」全列一遍,上下文预算大量花在数清单上,真正干活的 token 反而被挤掉。
SKRT(github.com/LittlePeter52012/skill-router)这个 Skill Router 给的解法:给定一条用户请求,先在本地技能库里挑出最相关的几个,只把那几个 SKILL.md 正文读进来执行。纯 Go CLI、一次性进程,不引入重型依赖,没配 API Key 时退化成纯本地关键词匹配也能跑。
本文记录它的落地方式与取舍。它与站内 #6388 讲的是两件事:那篇讲 SKILL.md 的 progressive disclosure 三级加载机制本身,这篇讲技能多了之后怎么做「路由」这层。
它解决的是什么问题
输入:
用NotebookLM查找资料→ 输出:nlm-skill(score: 92, 50ms)
不是所有 Agent 框架都像 Claude Code 那样天然渐进式按需加载技能。很多工作流(尤其是多 Agent 编排、Codex/OpenCode 这类跑在命令行的一次性场景)倾向于把候选技能尽量都暴露给模型,否则它不知道该调用哪个。技能一多,这个「全暴露」策略就开始吃上下文,路由错误也会变多。
SKRT 的做法是把它前置成一个一次性 CLI 查询:skrt query "..." 返回 JSON,Agent 拿到 top-N 技能名与路径,再去读对应 SKILL.md。
本地 7 层打分:不依赖网络也能路由
核心匹配器是七条策略按权重叠加打分,纯本地毫秒级(README 标称 200+ 技能约 50ms):
- 技能名精确命中(100 分)
- 名称/查询包含关系(90 分)
- description 子串匹配(最高 95 分)
- 关键词 token 重叠(最高 80 分)
- 单 token 检索(最高 92 分)
- Levenshtein 模糊匹配(最高 40 分)
- CJK bigram 匹配(最高 75 分)——中文/日文/韩文的整词切分
第 7 条对中文用户是关键:英文技能名对中文查询,光靠 token 重叠基本命中不了,双字组(bigram)能处理「小红书运营」这类中文字面。
CJK/西里尔文查询还有一个 Layer 0:先经 Gemini API 翻译成英文再匹配。没 Key 时这层跳过,只靠本地 CJK bigram。
两层可选:本地快 + API 准
| Provider | 速度 | 准确度 | 依赖 |
|---|---|---|---|
| api(默认) | ~3-5s | 高 | API key |
| local | ~3ms | 够用 | 无 |
新装默认 provider_first:先试 API(embedding 检索 + 重排),请求失败或无 Key 时自动回落到本地关键词匹配,不会中断。
支持的 embedding 模型:Gemini gemini-embedding-001(推荐)/ gemini-embedding-2-preview、OpenAI text-embedding-3-small / text-embedding-3-large,本地走任意 Ollama / LM Studio / vLLM 的 /embeddings 端点。
落地步骤(Go 1.21+)
go install github.com/LittlePeter52012/skill-router/cmd/skrt@latest
# 交互式配 Gemini API key(存 ~/.skrt/credentials,0600 权限,config 只记环境变量名不记 key)
skrt provider setup
# 索引技能(扫描本地各 Agent 的技能目录)
skrt index
# 查询
skrt query "PDF merge"
skrt q "用NotebookLM查找" --verbose # CJK + 调试输出
# 强制本地匹配
skrt query "protein structure prediction" --provider local
扫描的技能目录通过 ~/.skrt/config.json 配置,默认覆盖 Gemini、Antigravity、Codex、OpenCode、Qwen、cc-switch 等:
{
"skill_dirs": [
"~/.gemini/antigravity/skills",
"~/.agents/skills",
"~/.config/opencode/skills",
"~/.qwen/skills",
"~/.cc-switch/skills",
"~/.codex/skills",
"~/.codex/vendor_imports/skills",
"./.agent/skills"
],
"provider": "api",
"provider_mode": "provider_first",
"providers": {
"api": {
"endpoint": "https://generativelanguage.googleapis.com/v1beta",
"api_key_env": "GEMINI_API_KEY",
"model": "gemini-embedding-001"
}
}
}
查询输出是给程序吃的 JSON:
{"query":"PDF merge","elapsed_ms":2.3,"total":3,"provider":"api","results":[{"rank":1,"name":"pdf","score":90,"path":"~/.gemini/antigravity/skills/pdf/SKILL.md","summary":"Use this skill for anything with PDF files...","match_reason":"name_in_query"}]}
几个实用命令与注意点
- 屏蔽仓库镜像目录:有的工具把技能装到顶层、同目录还留一份源码 checkout,不排除会把索引撑胖。用
ignore_dir_names跳过.git、.agency-agents-repo这类目录。 - pinned 技能:
skrt pin add brainstorming让常用技能保持可见,但精确/名称命中仍然压过通用 pin,不会把用户特意挂起的工具顶掉。skrt smart-pin --apply会扫聊天历史自动建议该 pin 哪些。 - managed source:把本地技能仓库登记为受管源,
skrt update --reindex拉取并按各自 install 命令重建索引,--dry-run先看要动什么。
它不是什么(边界)
- 不是 MCP server——MCP 要常驻 JSON-RPC 进程,SKRT 是一次性 CLI。
- 不做技能编排/多 Agent 分发——只做「选出技能并返回路径」,选中后由 Agent 自己读技能正文执行。
- 不是某个 IDE 插件,跨所有终端 Agent 通用。
一句话总结
技能库大到你不想把全量 description 塞进每个会话时,Skill Router 的思路是「先用廉价本地匹配圈出 top-N,再读那几份 SKILL.md」。SKRT 的意义在于默认不依赖网络也能路由,CJK 场景下有兜底,且输出是结构化 JSON 方便接进编排。如果你已经在多个 Agent 里铺了几百个技能,值得把技能路由单独摘出来跑一次,而不是继续让每个子 Agent 各自扫同一份技能目录。