1. 从 15000 token 系统提示词说起:Claude skills 渐进式披露到底解决了什么问题
如果你维护过一个生产级 LLM 应用,大概率见过这种系统提示词:开头是角色设定,中间塞了 8 个工具的使用说明,后面跟着 API 文档、字段约束、三个 few-shot 示例,最后还有一段“注意不要编造”的兜底。整段加起来一万五千 token 起步,改一个功能要 Ctrl+F 定位,改完还得担心有没有和别的段落打架。
Claude skills 想解决的就是这件事。它把“什么时候需要什么知识”从一次性预加载,改成按需加载。核心机制叫渐进式披露(progressive disclosure):skill.md 的 YAML 前置元数据始终在上下文里,模型靠它判断当前任务该不该触发这个 skill;只有触发之后,主体内容才被拉进来;主体里引用的 references 和 assets,再等到真正用到时才加载。
这套机制适合谁?三类人最该关注。第一类是正在把 prompt 工程往工程化方向做的开发者,系统提示词已经超过 5000 token 还在膨胀。第二类是做 Agent 的团队,工具多、领域知识杂,需要模块化管理。第三类是个人开发者,想用 Claude Code 或 API 搭一个能长期维护的编码助手,而不是每次重写一大段提示词。
但这里有个真问题:渐进式披露听起来很美,模型真的会稳定触发吗?skill.md 写完之后,模型是每次都调用,还是十次里漏三次?这篇就围绕这个疑问展开,从 skill.md 编写、系统提示词组织,到多轮对照验证,给出可复制的模板和判断方法。我试过把同一套 skill 放在不同触发条件下跑,结果差异比想象中大,下面逐层拆。
2. TaoToken 前置准备:Claude skills 验证环境怎么搭
要验证 skill 是否被稳定调用,你需要一个能观察请求和响应的环境。直接用网页版 Claude 做对照实验有两个麻烦:一是看不到实际发送的 system prompt 结构,二是没法批量跑多轮。用 API 就清楚得多,每次请求的 messages、system、tools 都能自己控制。
这里用 TaoToken 作为接入层,它提供兼容 Anthropic 的 API 端点,方便你在本地脚本里反复调用同一个模型做对照。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后在控制台生成 Key。
拿到 Key 之后,你需要确认三件套:Base URL、API Key、Model ID。Base URL 用 https://taotoken.net/api ,注意这个地址不带 UTM 参数,直接作为请求根路径。API Key 在控制台的 API Keys 页面创建,建议单独建一个用于实验的 Key,方便后面看调用量。Model ID 按你实际要测的 Claude 模型填,比如 claude-sonnet-4-5 这类标识,具体以控制台模型列表为准。
环境变量建议这样设,避免 Key 写死在代码里:
export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_API_KEY="sk-你的实验Key" export TAOTOKEN_MODEL="claude-sonnet-4-5"如果你用的是 Claude Code 这类 CLI 工具,配置方式不太一样。Claude Code 读取的是 settings 文件,路径通常在~/.claude/settings.json,里面要写全 Base URL、Key 和 Model ID 三件套:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的实验Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }注意ANTHROPIC_BASE_URL后面不要带斜杠,也不要拼/v1,具体以接入文档为准。如果你用的是 Cline 或带 MCP 的客户端,配置项名称可能是baseUrl、apiKey、model,逻辑一样,把三件套填全即可。Codex 系的工具如果走auth.json,字段名是OPENAI_BASE_URL之类,但接 Anthropic 协议时仍以 Base URL + Key + Model ID 为准。
这一步的目标不是“连上就行”,而是让你能在一个可控脚本里,把 system prompt 和 skill 内容分开传入,观察模型在不同组合下的行为。接入文档在 https://taotoken.net/doc ,API Keys 管理在 https://taotoken.net/api-keys ,建议先跑通一次最小请求再往下做对照实验。
3. skill.md 模板与系统提示词配置:可复制的渐进式披露结构
先给一个可以直接改的 skill.md 模板。它的结构分三层:YAML 前置元数据、主体说明、捆绑资源引用。前置元数据始终加载,所以 description 要写清楚“什么时候用”,而不是“这是什么”。
--- name: pdf-invoice-parser description: 当用户需要从 PDF 发票中提取金额、税号、开票日期等结构化字段时使用。适用于财务对账、报销审核场景。不适用于图片发票或手写票据。 version: 1.0.0 --- # PDF 发票解析 ## 工作流 1. 确认输入文件路径存在,且为 .pdf 后缀。 2. 调用 scripts/extract_text.py 抽取文本层。 3. 若文本层为空,提示用户该文件可能是扫描件,转人工。 4. 按 references/invoice_schema.md 中的字段定义解析。 5. 输出 JSON,字段缺失时填 null,不要编造。 ## 约束 - 金额字段保留两位小数,货币符号单独成字段。 - 税号做长度校验,不合法时标记 warning。 - 不要在本节描述何时加载 references,触发条件已写在 description。 ## 资源 - references/invoice_schema.md:字段定义与示例 - scripts/extract_text.py:文本抽取脚本 - assets/report_template.md:对账报告模板关键点有三个。第一,description 里必须包含触发条件,因为主体内容在未触发时根本不在上下文里,模型只能靠 description 判断。第二,主体里不要写“当需要 API 文档时加载 references”,这类“何时用”的指导要挪到 description,否则主体加载时已经晚了。第三,可执行代码放 scripts 文件夹,主体里只留最少的伪代码或 bash 命令,整个文件控制在 10K 字以内。
系统提示词这边,不要把所有 skill 的主体都塞进去,只放 skill 的索引和调用约定。一个可用的组织方式:
你是一个财务处理助手。当前可用 skills 如下: - pdf-invoice-parser:解析 PDF 发票,触发条件见其 description。 - bank-statement-reconciler:银行流水对账。 调用规则: 1. 先判断用户任务是否匹配某个 skill 的 description。 2. 匹配则声明使用该 skill,再按其主体工作流执行。 3. 不匹配则直接用通用能力回答,不要强行套用 skill。 4. 需要字段定义时,读取对应 references 文件,不要凭记忆编造。这样系统提示词本身很短,可能只有几百 token,skill 主体和 references 按需加载。渐进式披露的收益就在这里:日常对话不触发 skill 时,上下文里只有索引,token 消耗低;触发时才把对应模块拉进来,避免“中间丢失效应”把关键信息埋没。
如果你想让模型自己帮你起草 skill,可以先给它一段任务描述,让它输出 YAML 前置元数据和主体草稿,然后你逐段质疑:这段是不是必要?和别的 skill 有没有重复?触发条件写清楚了吗?这比从空白文件开始快,但别直接采纳,模型容易把“何时用”写进主体。
4. 多轮对照验证:判断 skill 是否被稳定调用
验证设计要能区分三种情况:模型完全没触发 skill、触发了但没按工作流执行、触发了且正确执行。用一个脚本跑多轮,每轮换一种提问方式,记录模型是否声明使用 skill、是否读取了 references、输出字段是否完整。
先写一个最小调用脚本,把 system prompt 和用户消息分开:
import os, json, requests BASE = os.environ["TAOTOKEN_BASE_URL"] KEY = os.environ["TAOTOKEN_API_KEY"] MODEL = os.environ["TAOTOKEN_MODEL"] system_prompt = open("system_prompt.txt", encoding="utf-8").read() def ask(user_msg): resp = requests.post( f"{BASE}/v1/messages", headers={ "x-api-key": KEY, "anthropic-version": "2023-06-01", "content-type": "application/json", }, json={ "model": MODEL, "max_tokens": 1024, "system": system_prompt, "messages": [{"role": "user", "content": user_msg}], }, timeout=60, ) return resp.json() cases = [ "帮我解析一下 /tmp/inv_001.pdf 这张发票", "这张 PDF 里有多少钱?/tmp/inv_002.pdf", "我有一堆发票要处理,先看看 /tmp/inv_003.pdf", "把 /tmp/inv_004.pdf 的税号和金额提出来,输出 JSON", ] for i, c in enumerate(cases, 1): r = ask(c) text = "".join(b.get("text", "") for b in r.get("content", [])) print(f"--- case {i} ---") print(text[:400])跑完之后看几个信号。第一,模型有没有在回答里声明“使用 pdf-invoice-parser”。第二,输出是不是 JSON,字段是否和 references 里的定义一致。第三,遇到扫描件时有没有按工作流提示转人工,而不是硬编一个金额。
实测下来,触发稳定性受三个因素影响最大。一是 description 的措辞,如果写得太泛(比如“处理文档”),模型容易在不该触发时触发;写得太窄(比如“解析 2024 年增值税专用发票”),又容易漏触发。二是用户提问的措辞,如果用户说“看看这个文件”,模型可能先做通用回答,不触发 skill。三是系统提示词里的调用规则,如果规则太弱,模型会忽略 skill 索引。
一个改进做法是在系统提示词里加一句强约束:“当任务匹配任一 skill 的 description 时,必须先声明使用该 skill,再执行。”然后重跑上面四组 case,对比声明率。如果声明率从 2/4 提升到 4/4,说明规则有效;如果还是漏,就要回去改 description。
验证模型本身的行为,可以在模型对话页面手动跑几轮,观察不同措辞下的触发差异:https://taotoken.net/model-chat 。如果你要长期跑这类对照实验,用 Coding Plan 更划算,适合反复调用做 Agent 验证:https://taotoken.net/coding-plan 。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
接入和验证过程中,报错基本集中在几类。下面按真实错误信息对照排查。
401 Unauthorized。最常见的原因是 Key 没传对。检查三件套:Base URL 是不是https://taotoken.net/api,Key 是不是从 API Keys 页面复制的完整字符串,Model ID 是不是控制台里存在的标识。如果用的是 Claude Code,检查~/.claude/settings.json里ANTHROPIC_API_KEY有没有多余空格或换行。另外注意,有些客户端会把 Key 放在Authorization: Bearer头里,而 Anthropic 协议用的是x-api-key,混用会 401。
local proxy failed。这个报错通常出现在 CLI 工具里,意思是本地代理层没起来或端口被占。先确认没有其他进程占用同一端口,再检查配置文件里的 Base URL 有没有拼错。如果你在 settings 里同时配了环境变量和文件配置,可能互相覆盖,建议只保留一处。这个报错和网络环境无关,纯粹是本地配置问题,逐项核对即可。
reading choices 相关报错。这类错误一般出现在响应解析阶段,说明返回结构和你代码里假设的结构不一致。Anthropic 协议的响应里,内容在content数组,每项有type和text;如果你按 OpenAI 的choices[0].message.content去取,就会报 reading choices。改法是把解析逻辑换成:
text = "".join( block.get("text", "") for block in resp.get("content", []) if block.get("type") == "text" )OAuth 相关报错。如果你用的是需要 OAuth 登录的客户端,报错可能提示 token 过期或 scope 不足。这类客户端通常不走 API Key,而是走登录流程。排查时先确认你用的是 API Key 模式还是 OAuth 模式,两者配置项不同。如果客户端同时支持,建议实验阶段统一用 API Key,减少变量。
还有一个容易忽略的点:skill.md 的 YAML 前置元数据格式错误。如果---没闭合,或者description里有未转义的特殊字符,skill 可能加载失败,但报错信息不一定直观。排查时先把 skill.md 单独用 YAML 解析器跑一遍,确认能解析再放进流程。
接入文档里有各客户端的配置示例,遇到不确定的字段名可以先对照:https://taotoken.net/doc 。Key 管理在 https://taotoken.net/api-keys ,如果怀疑 Key 失效,重新生成一个再试。
6. 把验证变成习惯:skill 维护的几条实用做法
skill 写完不是终点。模型版本更新、任务分布变化、references 内容调整,都会影响触发稳定性。建议每次改完 skill.md 或系统提示词,都重跑一遍第 4 节那四组 case,记录声明率和字段完整率。这两个指标比“感觉能用”可靠得多。
description 的措辞值得反复打磨。一个实用技巧是把 description 当成检索 query 来写:假设模型只看到这一行,它能不能判断该不该触发?如果 description 里全是名词没有场景,触发率通常偏低。加上“当用户需要……时使用”这类条件句,效果会好一些。
references 和 assets 要分清。references 是拉进上下文给模型读的材料,比如字段定义、API 文档;assets 是模型编辑后输出的材料,比如报告模板。放错位置会导致模型把模板当参考读,或者把文档当模板改。这个区分在 skill 变多之后尤其重要。
最后,别把所有知识都塞进一个 skill。模块化的意义在于复用和独立维护。一个 skill 只解决一类任务,description 只描述这一类任务的触发条件。skill 数量多了之后,系统提示词里的索引也要分组,否则索引本身又会变成新的臃肿来源。
如果你要长期维护多个 skill,用 Coding Plan 跑批量验证会比按次调用省心:https://taotoken.net/coding-plan 。模型对话页面适合手动抽查单个 case:https://taotoken.net/model-chat 。配置和 Key 相关的操作在控制台完成:https://taotoken.net/console 和 https://taotoken.net/api-keys 。接入细节以文档为准:https://taotoken.net/doc 。