1. 从 Hermes cli 源码看 skill 机制到底解决了什么问题
Hermes cli 里的 skill 机制,本质上是一套「可插拔技能系统」:把领域知识、执行流程、工具调用策略和资源文件打包成一个独立目录,Agent 在需要时按需加载,而不是把所有能力一次性塞进系统提示词。它解决的核心痛点是上下文膨胀——如果每个能力都写进 prompt,token 成本会随能力数量线性增长,而 skill 机制让系统提示词里只保留一份极短的索引,真正的内容在 LLM 决定调用时才展开。
我拆 Hermes cli 源码时发现,它的 skill 目录结构是这样的:
~/.hermes/skills/ └── <category>/ └── <skill-name>/ ├── SKILL.md # 必须,主文件 ├── references/ # 可选,按需加载的深度文档 ├── templates/ # 可选,模板文件 ├── scripts/ # 可选,辅助脚本 └── assets/ # 可选,图片等资源这个结构和普通文件夹最大的区别在于references/、templates/、assets/、scripts/四个目录会被框架自动发现,并在skill_view()返回值的linked_files字段中列出,供 LLM 按需二次调用。也就是说,SKILL.md 是入口,其余目录是「懒加载」的附属资源。
SKILL.md 本身用 YAML Frontmatter 开头,两个---之间是元数据,会被注入到系统提示词里。正文则按固定章节组织:标题、When to Use、Overview、Steps、Examples、Common Pitfalls、Checklist、References。其中 When to Use 是最关键的触发条件,LLM 靠它判断当前任务是否匹配。
源码里_parse_skill_file会解析 frontmatter,做平台过滤(macos/linux/windows),然后调用extract_skill_description把 description 截断到 60 字符。这个 60 字符不是随便定的——它决定了系统提示词里 skill 索引的长度,索引越短,prefix cache 命中率越高,成本和延迟越低。
再看缓存设计,Hermes cli 做了两级:进程内 LRU 最多缓存 8 个 entry,cache_key 包含 skills_dir、external_dirs、tools、toolsets、platform、disabled_skills;磁盘层是~/.hermes/.skills_prompt_snapshot.json,里面存 manifest(每个 SKILL.md 的 mtime + size),用来校验缓存是否过期。如果版本号不匹配或 manifest 变了,就重新扫描。
系统提示词被分成三层:stable(身份 + 工具指引 + skills 索引)、context(AGENTS.md 等项目上下文)、volatile(记忆 + 时间戳)。skills 索引放在 stable 层,意味着一旦 LLM API 缓存了这个 prefix,后续每轮对话不用重新计算这些 token。这也是为什么 description 只截断到 60 字符,而不是完整加载所有 SKILL.md 内容。
调用链路是这样的:LLM 在用户输入中匹配到某个 skill 的触发条件,返回一个skill_view的 tool_call;agent 后端解析并执行工具,经过 guardrail 检查和 plugin block 检查后,通过注册表分发到真实函数;skill_view返回 SKILL.md 全文和 linked_files;LLM 读完后再决定是否调用 terminal 执行 scripts 里的脚本,或用skill_view带file_path参数读取 references 里的细节。
这套机制的精髓在于「渐进式披露」:系统提示词里只有索引,SKILL.md 是第二层,references 和 scripts 是第三层,每一层都在 LLM 真正需要时才加载。理解了这一点,我们就可以在本地用 TaoToken 统一 Key 复刻一套同样的可插拔技能系统。
2. TaoToken 统一 Key 与 API 通道的前置准备
要在本地复刻 Hermes cli 的 skill 机制,第一步是解决模型调用通道。skill 系统本身不绑定具体模型,但 LLM 的 tool_call 能力是整套机制运转的前提——没有稳定的 function calling,skill_view 和 terminal 调用就无从谈起。TaoToken 在这里的角色是提供一个统一的 Key 和 API 通道,让你不用为每个模型单独维护一套鉴权和 endpoint。
TaoToken 的官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api 。注意 API 地址不带 UTM 参数,配置时直接用这个 base URL 即可。
你需要先拿到 API Key。进入控制台创建 Key 的路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理页面在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建后复制保存,后面配置环境变量会用到。
模型选择上,skill 系统对模型的要求是支持 tool_call / function calling。你可以先在模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 测试一下目标模型是否能正常返回 tool_call 结构。如果只是做本地 skill 系统的验证,用对话页面确认模型可用即可;如果要长期跑编码类 Agent 任务,可以考虑 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。
环境变量配置建议统一成三个:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_MODEL="claude-sonnet-4-20250514"这里 Model ID 要写你实际可用的模型标识,不同模型对 tool_call 的支持程度不同,建议先用对话页面验证。Base URL 和 Key 是所有后续配置的基础,skill 系统里的 LLM 调用层会读取这两个值。
如果你用的是 Claude Code 这类工具,它的配置方式略有不同,需要设置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY,具体可以参考接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。Claude Code 的接入细节在文档里有完整说明,包括 Base URL、Key 和 Model ID 三件套的填写位置。
前置准备做完后,你应该有一个可用的 API Key、一个确认支持 tool_call 的模型、以及配置好的环境变量。接下来就可以开始搭 skill 系统的目录结构和注册逻辑了。
3. 可复制的 skill 目录结构与注册配置
复刻 Hermes cli 的 skill 系统,核心是两件事:目录结构要能被扫描器发现,注册配置要能被 LLM 理解。我们先建目录,再写配置。
目录结构完全对齐 Hermes cli 的设计:
~/.myskills/ ├── research/ │ └── polymarket/ │ ├── SKILL.md │ ├── references/ │ │ └── api-endpoints.md │ └── scripts/ │ └── polymarket.py └── software-development/ └── spike/ └── SKILL.mdSKILL.md 的 frontmatter 用 YAML 写,字段包括 name、description、platform、version。description 控制在 60 字符以内,因为系统提示词里只放这个短描述:
--- name: polymarket description: Query Polymarket markets, prices, orderbooks platform: [macos, linux] version: 1.0.0 ---正文按 Hermes cli 的章节顺序组织,When to Use 放在最前面:
# Polymarket ## When to Use 当用户询问预测市场、事件概率、Polymarket 相关数据时加载本 skill。 ## Overview Polymarket 是一个预测市场平台,市场价格代表概率。 ## Steps 1. 用 scripts/polymarket.py search 搜索市场 2. 用 scripts/polymarket.py book 查看 orderbook 3. 汇总结果返回用户 ## Examples python3 scripts/polymarket.py search "bitcoin" ## Common Pitfalls - 市场 ID 和 token ID 不要混淆 - 价格是概率,不是金额 ## Checklist - [ ] 确认市场存在 - [ ] 确认 token ID 正确 ## References - references/api-endpoints.md注册配置用一个 JSON 文件描述 skill 索引,模拟 Hermes cli 的 snapshot 机制:
{ "version": "1.0.0", "skills_dir": "~/.myskills", "manifest": { "research/polymarket/SKILL.md": { "mtime": 1735689600, "size": 2969 }, "software-development/spike/SKILL.md": { "mtime": 1735689600, "size": 1200 } }, "index": { "research": [ { "name": "polymarket", "description": "Query Polymarket markets, prices, orderbooks" } ], "software-development": [ { "name": "spike", "description": "Run a time-boxed experiment to validate a technology" } ] } }这个 JSON 对应 Hermes cli 里的~/.hermes/.skills_prompt_snapshot.json,manifest 用来校验缓存是否过期,index 用来生成系统提示词里的 skills 索引。
如果你用 Claude Code 或 Cline MCP 这类工具,配置方式要写全三件套。以 Claude Code 的 settings 为例:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }Cline MCP 的配置则在 MCP 服务器设置里填 Base URL、Key 和 Model ID。Codex 的 auth.json 类似:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "claude-sonnet-4-20250514" }三件套的核心是 Base URL 指向 TaoToken 的 API 地址,Key 用你创建的 Key,Model ID 用验证过支持 tool_call 的模型。配置写完后,扫描器会遍历 skills_dir,解析每个 SKILL.md 的 frontmatter,生成索引并写入 snapshot。
扫描逻辑可以这样实现,对齐 Hermes cli 的iter_skill_index_files:
import os from pathlib import Path EXCLUDED_DIRS = {".git", "venv", "node_modules", "__pycache__"} def iter_skill_index_files(skills_dir: Path, filename: str): matches = [] for root, dirs, files in os.walk(skills_dir, followlinks=True): dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS] if filename in files: matches.append(Path(root) / filename) for path in sorted(matches, key=lambda p: str(p.relative_to(skills_dir))): yield path这段代码和 Hermes cli 源码里的逻辑一致:排除干扰目录,按相对路径字典序排列,保证结果稳定。稳定排序很重要,因为系统提示词里的索引顺序如果每次都变,prefix cache 就失效了。
4. 验证请求与成功结果:新增一个 skill 后的完整动作
配置写完后,必须验证整套链路能跑通。验证分三步:扫描生成索引、LLM 返回 tool_call、执行 skill 并返回结果。
先写一个扫描脚本,读取 skills_dir 并生成 snapshot:
import json from pathlib import Path def build_manifest(skills_dir: Path): manifest = {} for skill_file in iter_skill_index_files(skills_dir, "SKILL.md"): stat = skill_file.stat() rel = str(skill_file.relative_to(skills_dir)) manifest[rel] = {"mtime": int(stat.st_mtime), "size": stat.st_size} return manifest def build_index(skills_dir: Path): index = {} for skill_file in iter_skill_index_files(skills_dir, "SKILL.md"): raw = skill_file.read_text(encoding="utf-8") frontmatter = parse_frontmatter(raw) category = skill_file.relative_to(skills_dir).parts[0] index.setdefault(category, []).append({ "name": frontmatter.get("name"), "description": frontmatter.get("description", "")[:60] }) return index snapshot = { "version": "1.0.0", "manifest": build_manifest(Path.home() / ".myskills"), "index": build_index(Path.home() / ".myskills") } (Path.home() / ".myskills" / ".snapshot.json").write_text( json.dumps(snapshot, indent=2), encoding="utf-8" )运行后检查.snapshot.json,应该能看到 polymarket 和 spike 两个 skill 的索引。如果某个 skill 没出现,先检查 SKILL.md 是否存在、frontmatter 是否合法。
接下来构造系统提示词,把索引注入 stable 层:
def build_skills_prompt(index): lines = ["## Skills (mandatory)", ""] lines.append("Before replying, scan the skills below. If a skill matches, " "you MUST load it with skill_view(name).") lines.append("") lines.append("<available_skills>") for category, skills in index.items(): lines.append(f" {category}:") for s in skills: lines.append(f" - {s['name']}: {s['description']}") lines.append("</available_skills>") return "\n".join(lines)把这段文本拼到系统提示词的 stable 部分,然后发一个测试请求。用户输入「帮我查一下 bitcoin 相关的预测市场」,LLM 应该返回一个 tool_call:
{ "tool": "skill_view", "arguments": { "name": "polymarket" } }收到这个 tool_call 后,执行 skill_view 函数,返回 SKILL.md 全文和 linked_files:
{ "success": true, "name": "polymarket", "content": "---\nname: polymarket\n...(SKILL.md 全文)...", "linked_files": { "references": ["references/api-endpoints.md"], "scripts": ["scripts/polymarket.py"] }, "usage_hint": "To view linked files, call skill_view(name, file_path=...)" }LLM 读完 SKILL.md 后,如果需要实际数据,会再返回一个 terminal 调用:
{ "tool": "terminal", "arguments": { "command": "python3 ~/.myskills/research/polymarket/scripts/polymarket.py search 'bitcoin'" } }执行脚本后返回结果,LLM 汇总成自然语言回复。整个链路跑通,说明 skill 系统复刻成功。
验证时重点看三个信号:系统提示词里是否出现了 skills 索引、LLM 是否返回了 skill_view 的 tool_call、skill_view 返回的 linked_files 是否包含 references 和 scripts。三个都正常,说明注册、加载、调用链路都通了。
5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth
复刻过程中最容易踩的坑集中在鉴权和调用链路上。下面按真实报错逐个排查。
401 Unauthorized:最常见的原因是 Key 没配对或 Base URL 写错。检查环境变量TAOTOKEN_API_KEY是否以sk-开头,TAOTOKEN_BASE_URL是否指向https://taotoken.net/api。如果用的是 Claude Code,检查ANTHROPIC_API_KEY和ANTHROPIC_BASE_URL是否都设置了。注意 API 地址不要带 UTM 参数,直接写 base URL。
local proxy failed:这个报错通常出现在本地代理配置和实际请求地址不一致时。检查你的 HTTP 客户端是否读取了系统代理设置,如果环境里有HTTP_PROXY或HTTPS_PROXY,可能会把请求转发到错误地址。临时清掉这些变量再试:
unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxyreading choices 报错:这个错误一般出现在解析 LLM 返回结构时。如果模型返回的不是标准 OpenAI 格式的choices数组,解析代码就会报错。检查你用的模型是否兼容 OpenAI 的 response schema,以及请求体里是否带了tools参数。如果模型不支持 tool_call,skill_view 的调用链路就断了。
OAuth 相关报错:如果你用的是 Claude Code 或 Codex 这类带 OAuth 流程的工具,报错可能来自 token 刷新失败。检查 auth.json 或 settings 里的配置是否完整,Base URL、Key、Model ID 三件套是否都填了。OAuth 流程和 API Key 鉴权是两套机制,不要混用。
skill 索引不出现:如果系统提示词里没有 skills 索引,先检查 snapshot 文件是否生成、manifest 是否匹配。如果 SKILL.md 被修改过但 snapshot 没更新,缓存校验会失败,需要重新扫描。检查_SKILLS_SNAPSHOT_VERSION是否和代码里的版本号一致。
linked_files 为空:如果 skill_view 返回的 linked_files 是空的,检查 references、scripts、templates、assets 四个目录是否存在且非空。框架只发现这四个固定目录名,其他名字不会被识别。
tool_call 不触发:如果 LLM 不返回 skill_view 的 tool_call,检查系统提示词里的 When to Use 是否足够明确。description 太模糊会导致 LLM 判断不出该加载哪个 skill。另外确认模型本身支持 function calling,可以先用对话页面测试。
排查顺序建议从鉴权开始:先确认 401 解决,再确认请求能到达模型,然后确认返回结构能解析,最后确认 skill 索引和 tool_call 链路。每一步都验证通过后再往下走,避免多个问题叠加。
6. 把 skill 系统接到 TaoToken 统一通道上
整套 skill 系统跑通后,最后一步是把它和 TaoToken 的 API 通道稳定对接。核心是把 LLM 调用层抽象成一个函数,读取环境变量里的 Base URL 和 Key,这样换模型或换 Key 时不用改 skill 代码。
import os import httpx def call_llm(messages, tools=None): base_url = os.environ["TAOTOKEN_BASE_URL"] api_key = os.environ["TAOTOKEN_API_KEY"] model = os.environ["TAOTOKEN_MODEL"] payload = { "model": model, "messages": messages, } if tools: payload["tools"] = tools resp = httpx.post( f"{base_url}/v1/chat/completions", headers={"Authorization": f"Bearer {api_key}"}, json=payload, timeout=60, ) resp.raise_for_status() return resp.json()这个函数是所有 skill 调用的入口。skill_view 和 terminal 作为 tools 传给模型,模型返回 tool_call 后,你的执行器解析并分发。分发逻辑对齐 Hermes cli 的execute_tool_calls_sequential:
def execute_tool_calls(response, registry): for tool_call in response["choices"][0]["message"].get("tool_calls", []): name = tool_call["function"]["name"] args = json.loads(tool_call["function"]["arguments"]) if name in registry: result = registry[name](**args) yield {"tool": name, "result": result}registry 里注册 skill_view、terminal、skills_list 等工具。skill_view 读取 SKILL.md 并返回 linked_files,terminal 执行 scripts 里的脚本。这样整套系统就和 TaoToken 的 API 通道解耦了,换模型只需要改TAOTOKEN_MODEL。
如果你要长期跑编码类 Agent 任务,Coding Plan 提供了更稳定的通道,配置方式和上面一致,只是套餐不同。验证模型是否可用时,可以先用模型对话页面发一个带 tools 参数的请求,确认返回结构里有 tool_calls 字段。
最后提醒一点:skill 系统的价值在于渐进式披露,不要把太多内容塞进 SKILL.md。description 控制在 60 字符,SKILL.md 保持精简,细节放 references,可执行逻辑放 scripts。这样系统提示词里的索引足够短,prefix cache 命中率高,成本和延迟都可控。新增 skill 时,只要目录结构对、frontmatter 合法、snapshot 更新,LLM 就能自动发现并调用,不需要改任何框架代码。