Claude Code Skill 完整工作流,从零构建一个 PDF 生成技能
Claude Code Skill 完整工作流:从零构建一个 PDF 生成技能
本文以一个真实场景为线索,完整拆解 Claude Code Skill 的设计原理、数据流转机制和开发实践。
一、什么是 Skill?
在 Claude Code 的体系中,Skill 不是插件,不是 API,也不是自动化脚本。它是一份结构化的「操作手册」——写给大模型看的说明书。
当你在 Claude Code 中安装了一个 Skill,你并没有安装一个会自动运行的程序。你安装的是一份指令文档,它告诉大模型:「当用户遇到某类问题时,你应该按照这个流程、用这些工具来完成工作。」
用一个类比:
Skill = 一本菜谱 大模型 = 厨师 用户的需求 = 顾客点的菜 脚本/工具 = 厨房里的锅碗瓢盆厨师拿到菜谱后,会根据顾客点的菜选择对应的菜谱,然后使用厨房里的工具做菜。菜谱本身不会自动做菜——它只是一张纸。
Skill 的文件结构
一个标准的 Skill 目录长这样:
my-skill/ ├── SKILL.md # 核心指令文件(必须有) │ ├── YAML frontmatter # name + description(触发条件) │ └── Markdown 正文 # 工作流程、命令参考、技巧等 ├── scripts/ # 可执行脚本(按需调用) ├── references/ # 参考文档(按需加载) └── assets/ # 静态资源(模板、字体、图片等)其中SKILL.md的 frontmatter 是关键:
---name:my-skilldescription:这个 Skill 做什么、什么时候该用它。这段文字是大模型决定是否调用的唯一依据。---description字段是触发机制的核心。大模型在每次对话开始时,会扫描所有已安装 Skill 的 name 和 description,判断当前用户的请求是否匹配。如果匹配,就加载完整的 SKILL.md 正文,然后按照里面的指令行动。
二、Skill 的三层加载机制
Skill 不是一次性全部塞进大模型上下文的。它采用了渐进式加载,避免浪费 token:
| 层级 | 内容 | 加载时机 | 大小 |
|---|---|---|---|
| 第一层 | name + description | 始终在上下文中 | ~100 词 |
| 第二层 | SKILL.md 正文 | 当 Skill 被触发时 | < 500 行 |
| 第三层 | scripts/、references/ | 大模型按需读取 | 无限制 |
这意味着:即使用户安装了 100 个 Skill,日常对话也只加载每个 Skill 的 ~100 词描述(总共约 1 万词),不会撑爆上下文窗口。只有当用户真正需要某个 Skill 时,才会加载详细指令。
三、从零构建:PDF Generator Skill
接下来,我用一个完整的实战案例来展示如何构建一个 Skill。
3.1 明确需求
我们的目标是:构建一个 PDF 生成 Skill,支持:
- 输入:Markdown 文本 + 结构化数据(JSON/CSV)
- 功能:图文混排、封面页、页眉页脚、页码、数据表格
- 技术栈:Python + fpdf2(纯 Python 库,无需系统依赖)
3.2 创建目录结构
mkdir-p~/.claude/skills/pdf-generator/{scripts,references,assets}3.3 编写 SKILL.md
这是 Skill 的灵魂。一份好的 SKILL.md 应该包含以下要素:
---name:pdf-generatordescription:从 Markdown 文本、结构化数据(JSON/CSV)或二者混合生成专业 PDF 文档。 当用户要求创建、生成、导出或保存 PDF 时使用此技能, 或当用户提到报告、发票、证书、简历时也应使用——即使他们没有明确说"PDF"。---注意 description 的写法——它需要覆盖尽可能多的触发场景,但不能过度泛化。一个好的 description 应该像搜索引擎的 SEO 描述:精确命中目标查询,同时不误伤无关请求。
正文部分按工作流组织:
## 工作流程 ### 第 1 步:了解用户需求 从上下文中判断或询问:内容类型、风格、特殊部分、输出路径 ### 第 2 步:准备输入 根据内容来源,准备 Markdown 文件或 JSON/CSV 数据文件 ### 第 3 步:生成 PDF 运行脚本,传入合适的命令行参数 ### 第 4 步:验证和交付 打开 PDF 确认效果,告诉用户文件保存位置3.4 编写核心脚本
脚本是 Skill 的「手」——它把大模型的意图转化为实际的输出。我们的generate_pdf.py核心架构如下:
# 三大核心组件:classPDFDocument(FPDF):"""PDF 文档类 —— 管理页面、页眉页脚、封面"""classMarkdownParser:"""Markdown 解析器 —— 把 Markdown 转换为 PDF 绘制指令"""defparse(self,markdown_text):# 逐行解析:标题 → _render_heading()# 列表 → _render_bullet()# 段落 → _render_paragraph()# 图片 → _add_image()# 分页 → add_page()classDataTableRenderer:"""数据表格渲染器 —— 把 JSON/CSV 渲染为带样式的表格"""defrender_from_file(self,filepath):# 读取 JSON 或 CSV# 计算列宽# 渲染表头(主色背景 + 白色文字)# 渲染数据行(交替背景色)# 自动跨页断行 + 表头重复脚本的命令行接口是大模型和实际功能之间的桥梁:
python generate_pdf.py\--outputreport.pdf\# 输出路径--content-file report.md\# Markdown 文本来源--data-file metrics.json\# 数据表格来源--title"年度报告"\# 文档标题--cover\# 生成封面--themeprofessional# 配色主题3.5 编写参考文档
references/style-guide.md提供详细的样式定制说明(字体、颜色、间距等),只有在用户需要深度定制时才被读取。
四、核心问题:Skill 如何接收和处理用户的语料?
这是理解 Skill 最关键的部分。很多人误以为 Skill 会自动扫描用户的文件或自动识别内容。实际上,整个数据流转的桥梁是大模型本身。
4.1 完整的数据流
┌─────────────────────────────────────────────────────────┐ │ 用户对话 │ │ │ │ 用户:"帮我写一篇技术分析报告" │ │ 大模型:(生成 Markdown 内容) │ │ 用户:"把上面的内容生成 PDF" │ │ │ ├─────────────────────────────────────────────────────────┤ │ Skill 触发阶段 │ │ │ │ 1. 大模型扫描已安装 Skill 的 description │ │ 2. 发现 "pdf-generator" 的描述匹配 "生成 PDF" │ │ 3. 加载 SKILL.md 正文 │ │ 4. 按照 SKILL.md 中的工作流执行 │ │ │ ├─────────────────────────────────────────────────────────┤ │ 数据准备阶段 │ │ │ │ 大模型从对话上下文中提取之前生成的 Markdown │ │ ↓ │ │ 写入临时文件(如 /tmp/extracted-content.md) │ │ 或者直接作为 --content 参数传入 │ │ ↓ │ │ 如果有数据文件,直接使用文件路径 │ │ │ ├─────────────────────────────────────────────────────────┤ │ 脚本执行阶段 │ │ │ │ python generate_pdf.py \ │ │ --content-file /tmp/extracted-content.md \ │ │ --data-file /data/metrics.json \ │ │ --output report.pdf \ │ │ --cover --theme professional │ │ │ │ 脚本接收的是:命令行参数(文件路径或字符串) │ │ 脚本不知道:对话上下文、Skill、大模型的存在 │ │ │ ├─────────────────────────────────────────────────────────┤ │ 结果交付阶段 │ │ │ │ 大模型验证 PDF 文件已生成 │ │ 告诉用户:"PDF 已生成,保存在 /path/to/report.pdf" │ │ │ └─────────────────────────────────────────────────────────┘4.2 三种语料来源的处理方式
场景 A:语料在对话上下文中
用户之前的对话中已经生成了 Markdown 内容 ↓ 大模型从上下文中提取该内容 ↓ 方案 1:直接作为 --content 参数传入 python generate_pdf.py --content "# 标题\n\n段落内容..." 方案 2:写入临时文件,再用 --content-file 传入(内容较长时推荐) echo "..." > /tmp/content.md python generate_pdf.py --content-file /tmp/content.md场景 B:语料是磁盘上的文件
用户:"把 /data/report.md 生成 PDF" ↓ 大模型直接使用用户提供的文件路径 ↓ python generate_pdf.py --content-file /data/report.md场景 C:语料是混合来源
用户:"做一份报告,文字内容我来写,数据在 sales.json 里" ↓ 大模型将用户提供的文字 + 读取用户的文件,合并传入 ↓ python generate_pdf.py \ --content "# 销售报告\n\n## 概述\n..." \ --data-file /data/sales.json \ --title "Q4 销售报告" \ --cover4.3 关键认知
| 常见误解 | 实际情况 |
|---|---|
| Skill 会自动扫描我的文件 | 不会。是大模型根据对话内容决定读哪些文件 |
| Skill 会自动识别语料格式 | 不会。是大模型判断内容类型,选择对应的脚本参数 |
| 脚本能访问对话上下文 | 不能。脚本只接收命令行参数和文件路径 |
| 我需要记住脚本的命令行参数 | 不需要。你只需用自然语言描述需求 |
五、设计一个好的 Skill 的核心原则
在构建 PDF Generator 的过程中,我总结了几条关键原则:
原则 1:描述要「强势」但不能泛滥
# 太弱 — 大模型经常不会触发description:生成 PDF 文档# 太泛 — 什么文档相关的请求都会触发description:处理各种文档生成需求# 合适 — 精准覆盖目标场景description:从 Markdown 或 JSON/CSV 生成专业 PDF。 当用户要求创建、导出 PDF,或提到报告、发票、证书时使用。 即使他们没说"PDF"也应使用。原则 2:解释 Why,而不是堆砌 MUST
与其写:
## 必须在生成 PDF 前先安装依赖不如写:
## 快速开始 首次使用需要安装依赖(fpdf2 库),否则脚本会报 ModuleNotFoundError大模型足够聪明,当你解释了「为什么」,它能更好地判断在什么情况下需要跳过、在什么情况下必须执行。
原则 3:脚本是桥,不是门
脚本应该是一个纯粹的「输入 → 输出」转换器。它不应该关心数据从哪来、谁来调用它。这种无状态设计让脚本可以被 Skill 调用,也可以被用户直接在命令行使用,还可以被其他工具链集成。
原则 4:让大模型做它擅长的事
大模型擅长:理解意图、提取上下文、准备输入、判断用哪个参数。
脚本擅长:精确的文件处理、格式转换、排版计算。
不要试图让脚本理解自然语言,也不要试图让 SKILL.md 里的指令精确到每行代码。各司其职,效果最好。
六、总结
用户需求(自然语言) │ ▼ 大模型判断 → 匹配 Skill 的 description → 加载 SKILL.md │ ▼ 大模型理解工作流 → 从对话/文件中提取语料 → 准备输入 │ ▼ 调用脚本 → 脚本执行(纯函数式:参数入,文件出)→ 生成 PDF │ ▼ 大模型验证结果 → 交付给用户Skill 的本质是一套协作协议:它定义了大模型、脚本和用户之间的分工。大模型负责理解和调度,脚本负责精确执行,用户负责表达需求。三者之间通过文件路径和命令行参数传递数据,没有魔法,只有清晰的数据流。
掌握了这个框架,你就可以构建任何类型的 Skill —— 不仅是 PDF 生成,还可以是 Excel 导出、PPT 制作、代码生成、自动化测试…… 原理都是一样的。
