1. 从一次“工具调用失败”说起:Skill 机制到底解决什么问题
如果你最近在折腾 Agent 工具链,大概率遇到过这种场景:模型明明“知道”该调用哪个工具,但一到具体执行就翻车——要么参数格式不对,要么根本不知道某个库该怎么用。我试过把 PDF 处理、图片压缩、音视频转码这些能力全塞进 system prompt,结果上下文直接爆掉,模型反而变笨了。
这就是 Skill 机制要解决的核心问题。Skill 是什么?一句话概括:Skill = 知识文档(SKILL.md)+ 加载器(skill_loader.py)+ 底层 Tool 执行能力。它不是一门新技术,而是把“按需加载领域知识”这件事工程化了。能做什么?让模型在需要处理 PDF 时才加载 PDF 的处理指南,需要操作数据库时才加载 SQL 规范,平时这些知识不占用上下文。适合谁?正在把自建能力接入多模型工具链的开发者,尤其是那些已经有一堆 Tool 和 MCP Server、但发现 token 消耗失控的团队。
传统做法是把所有工具定义和领域知识一次性注入。MCP 官方博客里提到过两个典型问题:工具定义会让上下文窗口过载,中间工具的结果会消耗额外 token。Skill 的思路是“渐进式加载”——SKILL.md 平时躺在磁盘上,只有触发条件匹配时才被读进上下文。
整条链路是这样的:用户提问 → 关键词匹配到某个 Skill → skill_loader.py 解析 SKILL.md → 把正文注入 system prompt → 模型根据知识决定是否调用 Tool → ToolExecutor 执行 → 结果返回模型。注意中间那步“决定是否调用 Tool”是可选的,如果用户只是问“PDF 提取表格有几种方法”,模型靠 SKILL.md 的知识就能回答,不需要真的执行代码。
理解了这条链路,你就能明白为什么 Skill 和 Tool、MCP 是互补而非替代关系。Tool 给模型“手”,MCP 给模型“远程手”,Skill 给模型“大脑的参考资料”。下面我会从 SKILL.md 的最小模板开始,一步步拆到 skill_loader.py 的解析逻辑,最后用 TaoToken 统一 Key 验证整条 Tool 与 MCP 调用链是否连通。
2. SKILL.md 最小模板与 skill_loader.py 解析逻辑:从声明到注册为 Tool
2.1 SKILL.md 的两段式结构
每个 Skill 就是一个 Markdown 文件,结构分两部分:YAML 元数据 + 正文知识。元数据用---包裹,至少包含name和description两个字段。description的写法很关键,它决定了模型什么时候该激活这个 Skill。
下面是一个可以直接复制使用的最小模板,我把它放在skills/pdf/SKILL.md:
--- name: pdf description: Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill. license: Proprietary. LICENSE.txt has complete terms --- # PDF Processing Guide ## Overview 处理 PDF 时优先使用 pdfplumber 提取文本和表格,它比 PyPDF2 对复杂版式更友好。 ## Extract Tables ```python import pdfplumber with pdfplumber.open("input.pdf") as pdf: page = pdf.pages[0] tables = page.extract_tables() for table in tables: for row in table: print(row)Merge PDFs
使用 pypdf 的 PdfMerger:
from pypdf import PdfMerger merger = PdfMerger() merger.append("a.pdf") merger.append("b.pdf") merger.write("merged.pdf") merger.close()元数据里的 `description` 写得越具体,触发匹配越准。我见过有人只写“处理 PDF”,结果模型在用户提到“文档”时就误激活。把具体动作(extract、merge、split、OCR)都列出来,匹配精度会高很多。 ### 2.2 skill_loader.py 的 23 行核心逻辑 加载器的职责很单一:读文件、切分元数据和正文、返回结构化结果。核心就是判断文件是否以 `---` 开头,然后用 `split("---", 2)` 切成三部分。下面是完整实现: ```python """Skill 加载模块""" import yaml def load(path: str) -> tuple[dict, str]: """解析 SKILL.md 文件,返回 (metadata, content)。""" with open(path, "r", encoding="utf-8") as f: content = f.read() if not content.startswith("---"): return {}, content # 只分割成三部分:前导空字符串、metadata、剩余内容 parts = content.split("---", 2) if len(parts) < 3: return {}, content metadata = yaml.safe_load(parts[1]) body = parts[2].strip() return metadata or {}, body if __name__ == "__main__": import sys from pathlib import Path script_dir = Path(__file__).parent test_file = script_dir / "skills/pdf/SKILL.md" if len(sys.argv) > 1: test_file = Path(sys.argv[1]) print(f"Loading: {test_file}") print("=" * 50) meta, body = load(str(test_file)) print("METADATA:") for k, v in meta.items(): display = f"{v[:60]}..." if isinstance(v, str) and len(v) > 60 else v print(f" {k}: {display}") print(f"\nCONTENT (first 300 chars):\n{body[:300]}...")这里有个容易踩的坑:split("---", 2)的第二个参数是最大分割次数,不是分割份数。如果写成split("---"),正文里出现的---分隔线会把内容切碎。用maxsplit=2才能保证只切出元数据部分。
2.3 从解析结果到注册为 Tool
加载器返回的metadata用来做触发判断,body才是注入上下文的知识。但 Skill 本身不包含执行逻辑,它只是“告诉模型怎么做”。真正执行时,模型还是要通过 ToolExecutor 调用内置工具。
注册流程可以这样理解:启动时扫描skills/目录下所有SKILL.md,把metadata.name和metadata.description注册成一个轻量级的“可激活 Skill 列表”。这个列表本身很小,不占多少 token。当用户输入进来,先用关键词或向量匹配判断该激活哪个 Skill,再调用load()把对应正文读进来。
如果你用的是支持 MCP 的客户端,可以把 Skill 加载器包装成一个 MCP Server 暴露出去。这样模型既能通过 MCP 调用远程服务,又能通过 Skill 获取领域知识,两条链路共用同一套 Key 管理。
3. 可复制配置:用 TaoToken 统一 Key 打通 Tool 与 MCP 调用链
3.1 为什么需要统一 Key
当你的 Agent 同时要调用多个模型、多个 MCP Server、多个自建 Tool 时,Key 管理会变成噩梦。每个服务一套 Key,轮换时到处改配置,还容易把 Key 硬编码进代码提交到仓库。TaoToken 的做法是提供一个统一的 API 入口,Base URL 指向https://taotoken.net/api,所有模型调用和工具调用都走这一个 Key。
这样配置的好处是:skill_loader.py 里不需要关心具体用哪个模型,只需要在调用时指定 Model ID;MCP Server 的连接配置也统一走同一个 Base URL。换模型时只改一个字段,不用动加载器逻辑。
3.2 settings.json 配置片段
如果你用的是 Claude Code 或类似的客户端,配置文件通常放在~/.claude/settings.json。下面是一个可复制的配置片段,把 Base URL、Key、Model ID 三件套都写全:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-your-taotoken-key", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" }, "skills": { "enabled": true, "paths": ["./skills"] }, "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "./workspace"] } } }注意ANTHROPIC_BASE_URL后面不要加/v1,TaoToken 的 API 入口已经处理了路径。Key 从控制台的 API Keys 页面获取,不要直接写死在代码里,用环境变量注入更安全。
3.3 skill_loader.py 与 MCP 的桥接配置
如果你想让 Skill 加载器本身也通过 MCP 暴露,可以在 MCP Server 配置里加一个自定义 Server。下面是一个 TOML 格式的配置示例,适用于支持 TOML 配置的客户端:
[mcp_servers.skill_loader] command = "python" args = ["-m", "skill_loader_server", "--skills-dir", "./skills"] [mcp_servers.skill_loader.env] TAOTOKEN_BASE_URL = "https://taotoken.net/api" TAOTOKEN_API_KEY = "sk-your-taotoken-key" TAOTOKEN_MODEL = "claude-sonnet-4-20250514"这样配置后,模型可以通过 MCP 协议调用skill_loader的list_skills和load_skill两个方法,动态获取可用 Skill 列表和具体内容。整个链路里,Tool 调用、MCP 调用、Skill 加载都共用同一个 TaoToken Key。
3.4 验证配置是否生效
配置写完后,先用一个最小请求验证 Key 和 Base URL 是否连通。可以用 curl 直接测:
curl -X POST https://taotoken.net/api/v1/messages \ -H "Content-Type: application/json" \ -H "x-api-key: sk-your-taotoken-key" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 100, "messages": [{"role": "user", "content": "回复 OK 两个字母"}] }'如果返回里包含"content"字段且文本是OK,说明 Key 和 Base URL 都正确。这一步过了,再往下测 Skill 加载和 MCP 调用。
4. 验证请求与成功结果:实测 Skill 加载与 MCP 调用是否连通
4.1 单独测试 skill_loader.py
先不接模型,直接跑加载器,确认 SKILL.md 能被正确解析:
python skill_loader.py skills/pdf/SKILL.md预期输出类似:
Loading: skills/pdf/SKILL.md ================================================== METADATA: name: pdf description: Use this skill whenever the user wants to do anything with PDF files... license: Proprietary. LICENSE.txt has complete terms CONTENT (first 300 chars): # PDF Processing Guide ## Overview 处理 PDF 时优先使用 pdfplumber 提取文本和表格...如果METADATA是空的,检查文件开头是不是有 BOM 或者空格。content.startswith("---")对首字符很敏感,Windows 下用记事本保存容易带 BOM,用 VS Code 另存为 UTF-8 无 BOM 即可。
4.2 测试模型能否根据 Skill 知识调用 Tool
写一个最小测试脚本,把 SKILL.md 的正文注入 system prompt,然后让模型处理一个 PDF 任务:
import anthropic client = anthropic.Anthropic( base_url="https://taotoken.net/api", api_key="sk-your-taotoken-key", ) with open("skills/pdf/SKILL.md", "r", encoding="utf-8") as f: content = f.read() parts = content.split("---", 2) body = parts[2].strip() response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=500, system=f"你是 PDF 处理专家,参考以下指南:\n\n{body}", messages=[{"role": "user", "content": "帮我写一段提取 PDF 表格的代码"}], ) print(response.content[0].text)成功的话,模型会输出使用pdfplumber的代码,而不是泛泛而谈。这说明 Skill 知识已经正确注入,模型能根据知识决定调用哪个 Tool。
4.3 测试 MCP 调用链
如果你配置了 MCP Server,可以用客户端自带的 MCP 调试命令验证。以 filesystem Server 为例:
npx @modelcontextprotocol/inspector npx -y @modelcontextprotocol/server-filesystem ./workspace在 Inspector 界面里能看到list_directory、read_file等工具。点击调用,如果返回文件列表,说明 MCP 链路通了。这时候再回到模型侧,让模型通过 MCP 读取一个文件,观察返回结果里是否包含文件内容。
4.4 完整链路成功标志
整条链路跑通的标志是:用户提问 → 模型匹配到 pdf Skill → 加载 SKILL.md → 模型决定调用 bash Tool 执行pip install pdfplumber→ ToolExecutor 返回安装结果 → 模型输出最终代码。整个过程里,模型调用走 TaoToken 的 Base URL,MCP 调用走同一个 Key,Skill 加载走本地文件系统。三者互不干扰,但共用一套认证。
5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth
5.1 401 Unauthorized
最常见的报错。原因通常是 Key 写错、Key 过期、或者 Base URL 配错。先检查ANTHROPIC_AUTH_TOKEN是不是从控制台复制的完整 Key,注意不要有多余空格。然后确认ANTHROPIC_BASE_URL是https://taotoken.net/api,不要写成https://taotoken.net/api/v1,路径重复会导致 404 或 401。
如果用的是 settings.json,检查 JSON 格式是否合法。可以用python -m json.tool settings.json验证。JSON 里不能有注释,尾逗号也会导致解析失败。
5.2 local proxy failed
这个报错通常出现在客户端尝试连接本地代理时。检查你的环境变量里有没有HTTP_PROXY或HTTPS_PROXY指向一个不存在的本地端口。如果有,临时取消这些环境变量再试:
unset HTTP_PROXY unset HTTPS_PROXY另外检查 settings.json 里有没有配置proxy字段指向本地地址。TaoToken 的 API 入口是直连的,不需要额外代理配置。
5.3 reading choices 相关报错
这个报错一般出现在解析模型返回结果时。如果返回体里没有choices字段,说明请求可能发到了错误的端点。检查你用的 SDK 是不是 Anthropic 格式,Anthropic 的返回是content数组,不是choices。如果你用的是 OpenAI 格式的 SDK,需要把 Base URL 改成对应的兼容端点,或者换用 Anthropic SDK。
还有一种情况是流式返回被中断,导致 JSON 解析失败。可以在请求里加stream: false先排除流式问题。
5.4 OAuth 相关报错
如果你在客户端里配置了 OAuth 登录,但同时又配了 API Key,两者可能冲突。OAuth 流程会尝试刷新 token,如果刷新失败就会报错。解决办法是明确用哪一种认证方式:用 API Key 就把 OAuth 相关配置清掉,用 OAuth 就不要在环境变量里放ANTHROPIC_AUTH_TOKEN。
5.5 Skill 加载后模型不调用 Tool
这不是报错,但很常见。模型读完 SKILL.md 后只是回答了问题,没有调用 Tool。原因可能是description写得太模糊,模型没匹配到;或者 system prompt 里没有明确告诉模型“你可以调用工具”。可以在 system prompt 里加一句“如果需要执行代码,请调用 bash 工具”,给模型一个明确的行动指令。
6. 把 Skill 接入你的工具链:从验证到长期使用
整条链路验证通过后,你可以把 skill_loader.py 包装成一个常驻服务,启动时扫描skills/目录,把每个 SKILL.md 的元数据注册到内存里。用户输入进来时,先用轻量级匹配(关键词或向量)判断该激活哪个 Skill,再调用load()读取正文。这样既节省 token,又保证模型在需要时能拿到准确的领域知识。
长期使用时,建议把 Skill 目录纳入版本管理,每个 Skill 一个文件夹,SKILL.md 里的description当成接口文档来维护。新增 Skill 时,先写 SKILL.md,再用skill_loader.py单独测试解析,最后接入模型验证触发是否准确。
如果你需要管理多个模型和多个 MCP Server,用 TaoToken 的统一 Key 能省掉大量配置工作。模型对话可以在控制台里直接测试,API Keys 页面管理所有 Key,接入文档里有各客户端的详细配置示例。需要长期跑编码任务或 Agent 的话,Coding Plan 提供了更稳定的配额。整条 Tool 与 MCP 调用链的连通性,用上面那套 curl 加 Python 脚本就能验证,不需要复杂的测试框架。