文章总结: 本文解析了ClaudeCode的Skill系统,通过三级指针架构实现知识按需加载,解决大模型上下文窗口有限的问题。文章对比了静态与动态意图,阐述了Skill在节点静态、边动态架构下的优势,并提供了编写Skill的实操建议,旨在利用语义匹配高效管理上下文资源。 综合评分: 94 文章分类: 产品介绍,解决方案
Claude-Code-Skill-深度解析
原创
xsser xsser
xsser的博客
2026年1月26日 15:34 浙江
一个反直觉的问题
假设你是一家公司的 CTO,手下有一个能力超强的员工。他能写代码、能做设计、能分析数据、能写文档,几乎无所不能。但有个问题:他的工位只有一张 A4 纸大小的桌面。
每次给他布置任务,你都得把所有可能用到的参考资料、规范文档、历史案例全部堆在这张小桌子上。桌面很快就满了,他开始把早期的资料推到地上,然后忘记那些内容。
这就是大语言模型面临的核心困境:Context Window 有限。
Claude 的上下文窗口大约 200K tokens,听起来很多,但在真实的工程场景下,这点空间消耗得比想象中快得多。系统提示词、对话历史、代码文件、API 文档、业务规则……每一样都在争抢这块宝贵的「桌面空间」。
Anthropic 的 Skill 系统,就是为了解决这个问题而生的。
Skill 到底是什么?
很多人第一次听到 Skill 这个概念,会以为是某种插件或者扩展。这个理解方向对了一半。
如果用程序员更熟悉的概念来类比:Skill 本质上是一种「知识指针」。
从一个内存管理的视角理解
假设 Claude 的 Context Window 是一块有限的内存空间,大约 200K tokens。你有一堆知识库要让 Claude 使用:Skill 开发规范(17000 字)、MCP 协议文档(25000 字)、PDF 处理指南(8000 字)、API 设计原则(12000 字)……
最笨的做法是什么?全部 copy 进内存:
12345678910// 反模式:值传递,全量复制void handleRequest( string skill_spec, // 17000 字,复制进来 string mcp_docs, // 25000 字,复制进来 string pdf_guide, // 8000 字,复制进来 string api_principles, // 12000 字,复制进来 string user_request // 用户的实际问题) { // Context Window 已经快满了 // 留给真正处理问题的空间所剩无几
这就是很多人用 LLM 的方式:把所有可能用到的参考资料一股脑塞进 System Prompt。结果呢?上下文被撑爆,模型还没开始干活就已经「内存溢出」了。
Skill 系统的做法完全不同。它用的是指针 + 懒加载的模式:
111213141516171819202122232425262728293031// Skill 模式:指针传递,按需解引用struct SkillPointer { string name; // "skill-creator" string description; // "当用户想创建 skill 时触发" (~100 字) string* body; // 指向完整内容的指针,暂不加载 string* references; // 指向参考文档的指针,暂不加载};
void handleRequest( vector<SkillPointer> skill_registry, // 只有指针和描述,很轻量 string user_request) { // Step 1: 扫描所有指针的 description,判断哪些相关 auto relevant = semanticMatch(skill_registry, user_request);
// Step 2: 只对相关的 Skill 执行「解引用」 for (auto& skill : relevant) { loadIntoContext(*skill.body); // 按需加载 }
// Step 3: 处理请求,此时上下文里只有真正需要的知识
看出区别了吗?
在值传递模式下,所有知识在函数调用时就被复制进来,无论用不用得到。在指针模式下,你只持有一堆轻量的「引用」,真正需要时才去「解引用」获取完整数据。
三级指针:Skill 的内存模型
Skill 系统的精妙之处在于,它设计了一套三级指针结构,每一级的「解引用成本」不同:
32333435363738394041424344454647484950515253545556575859606162Level 0: Skill Registry(始终在内存)┌─────────────────────────────────────────────────────────┐│ skill_creator_ptr → name: "skill-creator" ││ desc: "创建 skill 时触发" │ ~100 tokens│ mcp_builder_ptr → name: "mcp-builder" ││ desc: "构建 MCP 服务器时触发" │ ~100 tokens│ pdf_processor_ptr → name: "pdf-processor" ││ desc: "处理 PDF 文件时触发" │ ~100 tokens└─────────────────────────────────────────────────────────┘ │ │ 触发后解引用(Level 1) ▼Level 1: Skill Body(触发后加载)┌─────────────────────────────────────────────────────────┐│ skill-creator.body: ││ "
创建流程 ││ 1. 理解需求 ││ 2. 规划内容 │ 2000 tokens│ 3. 初始化目录 ││ ..." ││ ││ references_ptr → 指向更详细的文档 │└─────────────────────────────────────────────────────────┘ │ │ 需要时再解引用(Level 2) ▼Level 2: References(按需加载)┌─────────────────────────────────────────────────────────┐│ workflows.md: "## 顺序工作流 ... ## 条件工作流" │ 3000 tokens│ output-patterns.md: "## 模板模式 ... ## 示例模式" │ 2000 tokens│ advanced.md: "## 进阶技巧 ..." │ 5000 tokens
用 TypeScript 来表达这个结构可能更清晰:
636465666768697071727374757677787980818283848586878889909192939495969798interface SkillMetadata { name: string; description: string; // 语义匹配的依据}
interface SkillBody { content: string; // 核心指令,~2000 tokens references: Map<string, () => string>; // 懒加载函数}
interface SkillRegistry { skills: Map<string, { metadata: SkillMetadata; // 始终加载 loadBody: () => SkillBody; // 懒加载函数 }>;}
// Claude 的处理流程function processRequest(registry: SkillRegistry, userInput: string) { // Step 1: O(n) 扫描 metadata,每个只有 ~100 tokens const candidates = Array.from(registry.skills.entries()) .filter(([_, skill]) => semanticMatch(skill.metadata.description, userInput));
// Step 2: 只加载匹配的 Skill Body const loadedSkills = candidates.map(([name, skill]) => ({ name, body: skill.loadBody() // 此时才真正加载 ~2000 tokens }));
// Step 3: 执行任务时,如果需要更多细节,再加载 references for (const skill of loadedSkills) { if (needsMoreDetail(currentTask)) { const detail = skill.body.references.get('workflows.md')?.(); // 懒加载 appendToContext(detail); } }
为什么这套设计有效?
回到内存管理的类比。传统的 System Prompt 塞满知识的做法,相当于把所有数据都分配在栈上,函数一调用就全部复制。Skill 系统相当于把数据分配在堆上,只传递指针,按需分配。
具体来说:
1. 空间效率
假设你有 50 个 Skill,每个完整内容 3000 tokens。
全量加载:50 × 3000 = 150,000 tokens(直接爆掉上下文)
Skill 模式:50 × 100(metadata)+ 2 × 3000(触发的 Skill)= 11,000 tokens
节省了 93% 的上下文空间。
2. 语义级的「智能指针」
C++ 的智能指针能自动管理内存生命周期。Skill 系统更进一步:它的「指针解引用」由 LLM 的语义理解来驱动。
99100101102103用户说: "帮我做一个能识别发票的东西"
Claude 内部推理:- "做一个东西" ≈ "创建某种能力" → 匹配 skill-creator- "识别发票" ≈ "处理 PDF/图片" → 匹配 pdf-processor
这种基于语义的自动解引用,是传统的 if-else 或者关键词匹配做不到的。Claude 能理解「做一个东西」和「创建 Skill」在这个语境下是等价的,自动完成指针的选择。
3. 分层解引用的成本控制
104Level 0 → Level 1: 成本 ~2000 tokens
大多数请求在 Level 1 就能完成。只有用户追问细节(「工作流有哪几种模式?」)时,才会触发 Level 2 的解引用。这种分层设计让 token 消耗与任务复杂度成正比,避免了一刀切的浪费。
一个更完整的代码类比
如果把整个 Skill 系统用代码来模拟,大概是这样的结构:
105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171class Skill: """一个 Skill 就是一个带懒加载的知识容器"""
def __init__(self, skill_dir: str): self.dir = skill_dir Level 0: 元数据立即加载 self.metadata = self._parse_metadata() # Level 1 & 2: 延迟加载 self._body_cache = None self._ref_cache = {}
def _parse_metadata(self) -> dict: """只解析 YAML frontmatter,约 100 tokens""" with open(f"{self.dir}/SKILL.md") as f: # 只读取 --- 之间的内容 return yaml.parse(extract_frontmatter(f.read()))
@property def body(self) -> str: """懒加载 Level 1:Skill 主体内容""" if self._body_cache is None: with open(f"{self.dir}/SKILL.md") as f: self._body_cache = extract_body(f.read()) # ~2000 tokens return self._body_cache
def get_reference(self, name: str) -> str: """懒加载 Level 2:参考文档""" if name not in self._ref_cache: path = f"{self.dir}/references/{name}" with open(path) as f: self._ref_cache[name] = f.read() # 按需加载 return self._ref_cache[name]
class SkillRegistry: """Skill 注册表:管理所有 Skill 的指针"""
def init(self, skills_dir: str): self.skills = {} for skill_name in os.listdir(skills_dir): skill = Skill(f"{skills_dir}/{skill_name}") # 只存储轻量的 metadata,不加载完整内容 self.skills[skill_name] = skill
def get_context_for_request(self, user_input: str) -> str: """根据用户输入,构建最小化的上下文""" context_parts = []
# 始终包含所有 metadata(用于意图匹配) context_parts.append("## Available Skills\n") for name, skill in self.skills.items(): context_parts.append(f"- {name}: {skill.metadata['description']}")
# Claude 判断需要哪些 Skill(语义匹配) triggered = self._semantic_match(user_input)
# 只加载被触发的 Skill Body for skill_name in triggered: skill = self.skills[skill_name] context_parts.append(f"\n## {skill_name}\n{skill.body}")
return "\n".join(context_parts)
def _semantic_match(self, user_input: str) -> list[str]: """让 LLM 基于 metadata 判断哪些 Skill 相关""" # 这一步由 Claude 的语义理解完成 # 返回相关的 Skill 名称列表
这段代码展示了 Skill 系统的核心机制:用轻量的 metadata 做索引,用懒加载避免无谓的内存占用,用语义匹配代替硬编码的路由逻辑。
如果你写过 ORM 框架里的 lazy loading,或者用过 React 的React.lazy(),对这套模式应该不陌生。Skill 系统把这些工程实践搬到了 LLM 的知识管理领域。
三层渐进式披露:Skill 的核心设计
Anthropic 的工程师们在设计 Skill 系统时,引入了一个叫做「渐进式披露」(Progressive Disclosure)的架构模式。这套机制分三层,每一层的加载时机和 token 预算都不一样。
第一层:元数据(Metadata)
这是最轻量的一层,大约只有 100 个词,始终驻留在上下文中。
172173174---name: skill-creatordescription: 当用户想创建新 skill、编写 skill、设计 skill 时触发此技能
元数据的作用是让 Claude 知道「有哪些能力可用」以及「什么情况下该用」。就像药柜上的标签,医生一眼扫过去就知道有什么药,但不需要把每种药的详细说明书都读一遍。
Claude 在处理用户请求时,会先扫描所有 Skill 的元数据,判断哪些 Skill 与当前任务相关。这个过程是语义级别的匹配,Claude 能够理解「帮我做一个处理 PDF 的东西」和「创建 PDF 处理工具」表达的是同一个意图。
第二层:主体内容(Body)
当某个 Skill 被触发后,Claude 才会加载它的主体内容。这部分建议控制在 2000 词以内。
175176177178179180181182183184185186187188189
Skill Creator
## 创建流程
1. 理解需求:询问用户具体用途和使用场景2. 规划内容:确定需要哪些 scripts、references、assets3. 初始化目录:运行 init_skill.py 脚本4. 编写核心文件:完成 SKILL.md 和相关资源5. 打包发布:运行 package_skill.py6. 收集反馈迭代
## 写作规范
使用祈使句式,例如「创建文件」而非「你应该创建文件」描述字段使用第三人称:「此技能应在用户需要...时使用」
主体内容包含了执行任务所需的核心指令、工作流程、质量标准。这些信息只在需要时才进入上下文,用完就可以释放空间。
第三层:参考资源(References)
有些知识太过详细,放在主体内容里会让文件变得臃肿。Skill 系统允许把这类内容拆分到references/目录下,Claude 会在需要时主动读取。
190191192193194skill-creator/├── SKILL.md 主体内容(2000 词)└── references/ ├── workflows.md # 工作流模式详解(3000 词) ├── output-patterns.md # 输出格式规范(~2000 词)
比如用户问「Skill 支持哪些工作流模式」,Claude 会判断这个问题需要更详细的信息,然后主动去读取workflows.md。读完回答完,这部分内容也可以从上下文中移除。
这种设计的精妙之处在于:知识的粒度和加载时机都经过了精心编排。核心指令常驻内存,详细文档按需调用,上下文窗口的利用效率因此大幅提升。
静态意图 vs 动态意图:一个架构层面的关键区别
如果你尝试过自己写一个 AI Agent,大概率会写出这样的代码:
195196197198199200201202def handle_request(user_input): if "创建 skill" in user_input: return create_skill_workflow() elif "修改 skill" in user_input: return modify_skill_workflow() elif "删除 skill" in user_input: return delete_skill_workflow() else:
这是典型的「静态意图」设计。你需要预先穷举所有可能的用户表达方式,然后硬编码对应的处理逻辑。问题很明显:
用户说「帮我做一个 skill」,匹配失败
用户说「我想 build 一个 skill」,匹配失败
用户说「搞一个处理 Excel 的东西」,匹配失败
每发现一种新的表达方式,你就得改代码、加规则、重新部署。这套路子在传统软件开发里能用,放到 AI Agent 场景下就显得笨拙。
Skill 系统采用了完全不同的策略:把意图识别的任务交给 LLM 本身。
203204205description: | 当用户想创建新 skill 时触发。 包括但不限于:创建、编写、设计、开发、搭建新的 skill,
Claude 读到这段描述后,能够自动理解各种语义上等价的表达:
「创建一个 skill」→ 触发
「帮我做一个处理 PDF 的工具」→ 触发(组合推理:做工具 ≈ 创建 skill)
「我想让 Claude 学会分析日志」→ 触发(深层理解:让 Claude 学会 ≈ 创建能力 ≈ 创建 skill)
这就是「动态意图」的威力。你不需要穷举所有可能的输入,只需要用自然语言描述触发条件,剩下的交给 LLM 的语义理解能力。
更有意思的是,Claude 可以同时触发多个 Skill。当用户说「创建一个处理发票 PDF 的 skill」,Claude 会判断这个请求同时涉及「创建 skill」和「PDF 处理」两个领域,于是把两个 Skill 的知识都加载进来,综合使用。
这种能力在传统的 if-else 架构下极难实现。你要么写一堆组合判断逻辑,要么就得搞一套复杂的意图分类模型。Skill 系统把这些麻烦事儿都甩给了 LLM。
用图论视角理解:节点与边的静态/动态频谱
上面讲的「静态意图」和「动态意图」,可以用图论的语言更精确地描述。
把一个 AI Agent 系统抽象成一张有向图:
节点(Node)
= 能力模块、知识单元、处理函数
边(Edge)
= 节点之间的调用关系、触发条件、执行顺序
不同的系统架构,区别在于节点和边的「静态程度」不同。
第一类:传统 Workflow(节点静态 + 边静态)
这是最常见的自动化系统。你用 Airflow、n8n、或者自己写的编排代码,把一堆处理步骤串起来。
┌─────────────────────────────────────────────────────────────────────┐│ 传统 Workflow 架构 ││ ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │ 接收请求 │ ───► │ 解析意图 │ ───► │ 路由分发 │ ││ └──────────┘ └──────────┘ └──────────┘ ││ │ ││ ┌─────────────────────────┼─────────────────┐ ││ │ │ │ ││ ▼ ▼ ▼ ││ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 创建流程 │ │ 修改流程 │ │ 删除流程 │││ └──────────┘ └──────────┘ └──────────┘││ │ │ │ ││ ▼ ▼ ▼ ││ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 生成文件 │ │ 更新文件 │ │ 清理文件 │││ └──────────┘ └──────────┘ └──────────┘││ ││ 节点:在部署时就固定了(create/update/delete 三个分支) ││ 边:在代码里写死了(if-else 决定走哪条路) ││ │
用 Mermaid 画出来是这样:
flowchart TD A[接收请求] --> B[解析意图] B --> C{路由分发} C -->|"创建"| D[创建流程] C -->|"修改"| E[修改流程] C -->|"删除"| F[删除流程] D --> G[生成文件] E --> H[更新文件] F --> I[清理文件]
style C fill:#ffcccc style D fill:#cccccc style E fill:#cccccc
这张图的特点:
| 维度 | 状态 | 含义 | | — | — | — | | 节点 | 静态 | 系统有哪些能力,部署时就定死了 | | 边 | 静态 | 节点之间怎么连接,代码里写死了 | | 路由逻辑 | 静态 | if-else / switch-case / 正则匹配 |
问题在哪?
假设用户说「帮我搞一个能处理 Excel 的东西,顺便也支持 PDF」。这个请求同时涉及「创建」+「Excel 处理」+「PDF 处理」三个概念。在静态 Workflow 里,你得预先定义好这种组合场景的处理路径。有 N 个能力模块,理论上就有 2^N 种组合可能。穷举是不现实的。
第二类:Skill 系统(节点静态 + 边动态)
Skill 系统的突破在于:节点还是预定义的,但节点之间的连接关系由 LLM 动态决定。
241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271┌─────────────────────────────────────────────────────────────────────┐│ Skill 系统架构 ││ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ Skill Registry │ ││ │ │ ││ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ ││ │ │ skill- │ │ mcp- │ │ pdf- │ │ xlsx- │ │ ││ │ │ creator │ │ builder │ │processor│ │processor│ │ ││ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ ││ │ ▲ ▲ ▲ ▲ │ ││ └────────┼────────────┼────────────┼────────────┼──────────┘ ││ │ │ │ │ ││ └────────────┴─────┬──────┴────────────┘ ││ │ ││ ┌───────────┴───────────┐ ││ │ LLM 语义匹配 │ ││ │ "这个请求需要哪些 │ ││ │ Skill?怎么组合?" │ ││ └───────────┬───────────┘ ││ │ ││ ▼ ││ ┌───────────────────────┐ ││ │ 用户请求 │ ││ │ "帮我搞一个能处理 │ ││ │ Excel 的东西" │ ││ └───────────────────────┘ ││ ││ 节点:预定义的 Skill(skill-creator, pdf-processor 等) ││ 边:由 LLM 在运行时动态决定(语义匹配 + 组合推理) ││ │
用 Mermaid 表达这种动态连接:
272273274275276277278279280281282283284285286287288289flowchart TD subgraph Registry["Skill Registry(节点池)"] S1[skill-creator] S2[mcp-builder] S3[pdf-processor] S4[xlsx-processor] S5[api-designer] end
U[用户请求] --> LLM{LLM 语义匹配}
LLM -.->|"动态连接"| S1 LLM -.->|"动态连接"| S3 LLM -.->|"动态连接"| S4
S1 -.->|"组合执行"| S4
style LLM fill:#90EE90
关键区别:
| 维度 | 状态 | 含义 | | — | — | — | | 节点 | 静态 | Skill 是预先写好的,知识内容固定 | | 边 | 动态 | 哪些 Skill 被调用、以什么顺序组合,运行时决定 | | 路由逻辑 | 动态 | LLM 基于语义理解自动匹配,无需穷举规则 |
为什么边可以动态?
传统 Workflow 的边是代码逻辑,改一条边就得改代码、重新部署。Skill 系统的边是 LLM 的推理结果,模型「想通了」哪些 Skill 相关,边就自动建立了。
290291292293294295296297298299300301302303304305
传统 Workflow:边是硬编码的def route(intent): if intent == "create": return [create_workflow] # 固定返回一个节点 elif intent == "create_excel": return [create_workflow, excel_processor] # 又一条硬编码的边 # ... 穷举所有组合
# Skill 系统:边是 LLM 推理出来的def route(user_input, skill_registry): # LLM 看完所有 Skill 的 description,自己决定激活哪些 return llm.reason( f"用户说:{user_input}\n" f"可用 Skill:{skill_registry.descriptions}\n" f"请判断需要哪些 Skill,以及执行顺序。" )
这就是「边动态」的含义:连接关系在运行时由 LLM 的语义推理生成,而非在编译时由程序员硬编码。
第三类:更激进的动态系统(节点动态 + 边动态)
如果把思路再推进一步:节点本身也可以是动态的。
这就是一些前沿 Agent 框架在探索的方向。比如让 LLM 在运行时「发明」新的工具、「生成」新的处理函数。AutoGPT、MetaGPT 等项目都有类似的尝试。
306307308309310311312313314315316317318319320321322323324325326327328329330331┌─────────────────────────────────────────────────────────────────────┐│ 全动态 Agent 系统(概念性) ││ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ 动态节点池 │ ││ │ │ ││ │ ┌─────────┐ ┌─────────┐ ┌ ─ ─ ─ ─ ┐ ┌ ─ ─ ─ ─ ┐ │ ││ │ │ 预定义 │ │ 预定义 │ │ 运行时 │ │ 运行时 │ │ ││ │ │ Tool A │ │ Tool B │ │ 生成的 │ │ 生成的 │ │ ││ │ │ │ │ │ │ Tool X │ │ Tool Y │ │ ││ │ └─────────┘ └─────────┘ └ ─ ─ ─ ─ ┘ └ ─ ─ ─ ─ ┘ │ ││ │ ▲ ▲ ▲ ▲ │ ││ └────────┼────────────┼────────────┼────────────┼──────────┘ ││ │ │ │ │ ││ └────────────┴─────┬──────┴────────────┘ ││ │ ││ ┌───────────┴───────────┐ ││ │ LLM 大脑 │ ││ │ • 选择现有工具 │ ││ │ • 组合多个工具 │ ││ │ • 必要时生成新工具 │ ← 节点也变成动态的 ││ └───────────────────────┘ ││ ││ 节点:部分预定义 + 部分运行时生成 ││ 边:完全动态 ││ │
三种架构的对比总结
| 架构类型 | 节点 | 边 | 代表系统 | 适用场景 | | — | — | — | — | — | | 传统 Workflow | 静态 | 静态 | Airflow, n8n, 自定义脚本 | 流程固定、可穷举的业务 | | Skill 系统 | 静态 | 动态 | Claude Code Skill, GPTs | 能力固定、组合方式灵活 | | 全动态 Agent | 动态 | 动态 | AutoGPT, 实验性框架 | 开放探索、研究场景 |
Skill 系统选择了一个务实的平衡点:节点静态保证了可控性和可审计性,边动态提供了灵活性和泛化能力。
你不用担心 LLM 会「发明」出奇怪的处理逻辑,因为所有可用的能力都是你预先定义好的 Skill。但在这些 Skill 之间如何选择、如何组合、以什么顺序执行,LLM 有充分的自由度去推理和决策。
这种设计在工程上很讨巧:既享受了 LLM 语义理解的红利,又避免了完全放开控制带来的不可预测性。
一个具体的例子
用户说:「帮我创建一个能处理发票 PDF 的 Skill,支持提取金额和日期,最后导出成 Excel」
传统 Workflow 的处理方式:
332333334335336337338339
需要预定义这条精确的路径if "创建" in input and "PDF" in input and "发票" in input and "Excel" in input: run_pipeline([ create_skill_step, add_pdf_capability, add_invoice_extraction, add_excel_export ])
Skill 系统的处理方式:
340341342343344345346347348349350351用户输入 → LLM 语义分析
LLM 推理过程:├── "创建一个 Skill" → 触发 skill-creator├── "处理 PDF" → 触发 pdf-processor├── "提取金额和日期" → 这是 OCR/信息抽取任务,pdf-processor 能处理├── "导出成 Excel" → 触发 xlsx-processor└── 组合策略:先用 skill-creator 搭框架,在其中集成 pdf 和 xlsx 的能力
动态生成的执行图:skill-creator ──┬──► pdf-processor ──► xlsx-processor │
同样的请求,用户换一种说法:「我想让 Claude 学会处理发票,能识别里面的数字,整理成表格」
传统 Workflow 大概率匹配失败(没有「创建」「PDF」「Excel」这些关键词)。Skill 系统仍然能正确理解:
352353354355356LLM 推理过程:├── "让 Claude 学会" ≈ "创建新能力" → 触发 skill-creator├── "处理发票" → 发票通常是 PDF/图片 → 触发 pdf-processor├── "识别数字" ≈ "信息提取" → pdf-processor 能处理├── "整理成表格" ≈ "导出 Excel" → 触发 xlsx-processor
边的动态性让系统具备了对同一意图不同表达的鲁棒性。节点(能力)是固定的,但通往这些节点的路径可以有无数条。LLM 扮演的角色,就是在这个图上做实时的路径规划。
从案例中抽象方法论:Skill 的另一种理解视角
回到开头那个 CTO 和超强员工的比喻。
假设这个员工处理了 1000 个「创建 Skill」的任务。如果你去复盘这 1000 个案例,会发现它们有很多共同点:
都需要先理解用户的具体需求
都需要规划文件结构
都需要写 SKILL.md
都需要测试和迭代
这些共同点抽象出来,就是一套「方法论」或者叫「SOP」(标准操作流程)。
传统的做法是:由人类工程师分析案例、总结规律、编写代码。这个过程依赖人的经验和判断,耗时耗力,而且容易遗漏边界情况。
Skill 系统提供了另一条路:把案例和方法论都写进 Skill 文件,让 LLM 自己去理解和运用。
357358359360361skill-creator/├── SKILL.md 抽象出的方法论└── references/ ├── example-docx-skill.md # 案例:创建文档处理 Skill ├── example-api-skill.md # 案例:创建 API 测试 Skill
Claude 在执行任务时,会参考这些案例来理解方法论的具体应用方式。遇到新的场景,它能够基于案例中的模式进行类比推理,而不是机械地执行固定步骤。
某种程度上,这有点像人类专家的学习过程:先看大量案例,然后归纳出规律,最后在新情况下灵活运用。Skill 系统把这个过程工程化了。
Skill 的目录结构:一份实操指南
说了这么多概念,来看看一个 Skill 到底长什么样。
362363364365366367368369370371my-skill/├── SKILL.md [必需] 核心文件├── scripts/ # [可选] 可执行脚本│ ├── init.py│ └── validate.sh├── references/ # [可选] 参考文档│ ├── api-docs.md│ └── patterns.md└── assets/ # [可选] 输出资源 ├── template.docx
SKILL.md:唯一的必需文件
这个文件由两部分组成:YAML 格式的元数据,和 Markdown 格式的主体内容。
372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403---name: invoice-processordescription: | 当用户需要处理发票相关任务时触发。 包括:发票识别、信息提取、格式转换、批量处理、数据校验等。---
发票处理指南
## 核心能力
支持 PDF、图片、扫描件等多种格式的发票识别和信息提取。
## 处理流程
1. 确认发票来源和格式2. 选择合适的识别方案3. 执行信息提取4. 校验提取结果5. 输出结构化数据
## 输出规范
提取的发票信息应包含以下字段:- 发票号码- 开票日期- 购买方信息- 销售方信息- 商品明细- 金额合计- 税额
元数据中的description字段非常关键,它直接决定了 Skill 在什么情况下会被触发。好的描述应该涵盖各种可能的触发场景,用自然语言写清楚适用范围。
scripts/:放可执行脚本的地方
有些任务需要确定性的执行结果,比如文件格式转换、数据校验、模板生成。这类操作用脚本实现比让 LLM 临时生成代码更可靠。
404405406407408409410411412413
scripts/extract_invoice.pyimport pdfplumber
def extract_invoice_info(pdf_path): with pdfplumber.open(pdf_path) as pdf: text = "" for page in pdf.pages: text += page.extract_text()
# 解析逻辑...
Claude 在执行任务时可以直接调用这些脚本,而不需要每次都重新生成代码。这既节省了 token,也提高了执行的稳定性。
references/:放详细文档的地方
主体内容应该保持精简,通常不超过 2000 词。那些详细的 API 文档、复杂的业务规则、大量的示例代码,都应该放到references/目录下。
414415416417418419420421422423424425426427428429430431432433434435<!-- references/ocr-comparison.md -->
OCR 方案对比
## 腾讯云 OCR
优势:识别准确率高,支持增值税发票专用模板劣势:需要联网,有调用成本适用场景:对准确率要求高的正式场景
## PaddleOCR
优势:开源免费,可本地部署劣势:需要配置环境,通用模板识别率一般适用场景:成本敏感或离线环境
## Tesseract
优势:老牌方案,社区成熟劣势:中文支持较弱,需要训练模型适用场景:简单英文文档
Claude 在需要时会主动读取这些文件。比如用户问「用什么方案识别发票比较好」,Claude 会判断需要参考 OCR 方案对比的文档,然后加载它来回答问题。
assets/:放模板和资源文件的地方
有些 Skill 需要输出特定格式的文件,比如 Word 文档、PPT、图片。这些模板文件放在assets/目录下,Claude 可以基于模板生成最终产出。
436437438assets/├── invoice-template.xlsx 发票数据导出模板├── report-template.docx # 分析报告模板
这个目录下的文件通常不需要加载到上下文中,Claude 只需要知道它们的路径,然后在生成输出时引用即可。
Skill 与自建 Agent 的权衡
读到这里,可能有人会问:我自己写一个 ReAct Agent 不行吗?为什么要用 Anthropic 的 Skill 系统?
这是个好问题,答案取决于你的具体需求。
选择 Skill 系统的场景
开发效率优先。Skill 本质上是 Markdown 文件,写一个新 Skill 可能只需要半小时。你不用关心意图识别、上下文管理、工具调用这些底层细节,框架都帮你处理好了。
需要与 Claude Code 深度集成。如果你的工作场景是在 Claude Code 里完成各种开发任务,Skill 系统能无缝融入现有的工作流。你可以用/skill-name直接调用,也可以让 Claude 自动判断何时使用。
知识复用和团队协作。Skill 可以打包分发,可以通过插件市场安装,可以在团队内共享。这套机制让知识的沉淀和传播变得很方便。
选择自建 Agent 的场景
需要完全的控制权。Skill 系统的意图识别、Skill 触发、资源加载都由框架控制。如果你的业务逻辑有特殊要求,比如需要自定义的推理策略、特殊的记忆机制、或者对接私有的工具系统,自建 Agent 能给你更大的灵活性。
性能和成本有严格约束。Skill 系统在每次会话时都会加载所有 Skill 的元数据,当 Skill 数量很多时,这部分开销不可忽视。如果你的场景对延迟和成本非常敏感,精心优化过的自建方案可能更合适。
需要脱离 Claude 生态。Skill 系统目前主要在 Claude Code 和 Claude.ai 中使用。如果你的产品需要部署在其他 LLM 上,或者需要完全自主可控的技术栈,自建是唯一的选择。
两种方案并不互斥。很多团队的做法是:用 Skill 系统快速搭建原型、验证想法,等业务模式跑通后,再根据需要把核心能力迁移到自建的 Agent 架构上。
写好 Skill 的几个实用建议
最后分享一些从实践中总结出来的经验。
描述字段要具体,要包含触发场景
差的写法:
好的写法:
439440441442description: | 当用户需要处理 PDF 文件时触发。 包括:PDF 文本提取、页面旋转、文件合并拆分、 表单填写、水印添加、格式转换(PDF 转 Word/图片)等。
描述写得越具体,Claude 越能准确判断何时触发这个 Skill。模糊的描述会导致该触发时不触发,或者不该触发时乱触发。
主体内容要精简,详细文档放 references
上下文窗口是稀缺资源。SKILL.md 的主体内容建议控制在 2000 词以内,超出的部分拆分到references/目录。这样既保证了核心信息的可用性,又不会在不需要时浪费空间。
使用祈使句式,避免第二人称
差的写法:
好的写法:
Skill 是给 Claude 看的指令,用祈使句更直接、更清晰,也更节省 token。
包含具体的示例和模板
抽象的规则容易产生歧义,具体的示例能消除误解。
443444445446447448449450451452453454455456457
输出格式
发票信息以 JSON 格式输出:
{ "invoice_number": "12345678", "date": "2026-01-15", "buyer": { "name": "某某公司", "tax_id": "91110000..." }, "items": [ {"name": "商品A", "quantity": 2, "price": 100} ], "total": 200
有了这个示例,Claude 就知道具体该输出什么样的数据结构。
结语
Skill 系统的设计思路,代表了 AI 工程领域的一个重要方向:把 LLM 的语义理解能力用到系统架构中去。
传统软件架构依赖程序员预先定义好所有的逻辑路径。AI 原生的架构可以把一部分决策权交给模型,让它基于语义理解来动态选择执行路径。这种范式转换带来了更高的灵活性和更低的开发成本,当然也带来了新的挑战——比如如何保证行为的可预测性,如何调试难以复现的问题。
Anthropic 的 Skill 系统,可以看作这个方向上的一次工程实践。它在灵活性和可控性之间找到了一个平衡点:用自然语言定义触发条件(灵活),用结构化的文件组织知识(可控),用渐进式披露管理上下文(高效)。
对于 AI 应用的开发者来说,理解这套系统的设计思路,比会用它更重要。因为同样的思路可以迁移到你自己的 Agent 架构中,无论你用的是 Claude、GPT 还是其他模型。
上下文窗口的大小会继续增长,但「按需加载」的思想不会过时。毕竟,即使桌面变大了,保持整洁依然是一种美德。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:xsser的博客 xsser xsser《Claude-Code-Skill-深度解析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论