转发请注明出处:
一、什么是 Skill
Skill 是一套 “AI 调度 + 脚本执行”的分工协作机制。它的核心思想是关注点分离:AI 大模型擅长理解意图、做判断和调度,但不擅长精确计算和复杂文件操作;因此,Skill 让 AI 专注于“什么时候调用什么工具”的决策,而把“重型”的实际执行工作交给封装好的专用脚本。
一个 Skill 通常由以下部分构成:
- 指令文件(如
SKILL.md):写给 AI 看的“操作指南”,用自然语言描述这个 Skill 能做什么、在什么场景下触发、具体执行步骤是什么。 - 脚本目录:存放实际干活的代码,比如处理 Excel 的 Python 脚本、生成 PPT 的程序等。
- 参考文档:存放该领域的知识文档,供 AI 在需要时查阅。
- 资源目录:存放模板、图片等输出时需要的素材。
二、Skill 如何产生作用:触发与执行逻辑
Skill 的触发并非魔法,而是基于一套精心设计的调度机制,核心在于解决“AI 怎么知道该用哪个 Skill”的问题。
1. 三层渐进式加载,管理上下文
AI 的“记忆容量”(上下文窗口)有限,因此采用分层策略:
- 第一层(始终在线):只有 Skill 的名称和描述这一小段元数据会一直待在 AI 的“记忆”里。AI 根据对话内容判断是否匹配某个 Skill 的描述,来决定“要不要触发它”。
- 第二层(触发后加载):一旦 AI 决定触发某个 Skill,才会加载完整的指令正文,了解具体执行步骤。
- 第三层(按需加载):执行过程中,如果指令要求查阅参考文档或调用脚本,AI 才会按需加载对应的脚本或参考资料。
2. 触发匹配
AI 根据对话内容和用户输入的关键词,自动匹配最合适的 Skill。为了让匹配更精准,Skill 的描述需要写得非常清晰,明确告知“我能处理什么意图”。
3. 主动发现与边界管理
为解决“装了 Skill 但 AI 想不起来用”的问题,一些进阶机制也被引入,比如在对话开始时主动扫描意图方向,列出当前可用的相关 Skill;当需求超出当前 Skill 的能力范围时,主动告警并建议调用其他 Skill。
三、Skill 如何调用外部工具
Skill 可以调用外部工具,这是它从“告诉 AI 怎么想”进化到“帮 AI 真的去做”的关键一步。
调用外部工具通常通过“连接器”这座桥梁来实现。可以把连接器想象成给 AI 装的“USB 接口”,专门用来对接外部的各种软件和服务。
整个调用流程:
- 配置连接器:在连接器市场里找到需要的服务,手动点击“信任”并完成授权。这一步的核心是安全,遵循“最小权限”原则。
- Skill 声明依赖:在写 Skill 指令时,明确告诉 AI 这个 Skill 需要用到哪个连接器里的哪个工具,相当于列出它“被允许”使用的工具箱。
- AI 调度执行:用户下达任务后,AI 理解意图,自动匹配并加载对应的 Skill,再按照 Skill 里写的流程,通过配置好的连接器去调用外部工具完成具体操作。
四、Skill 与 Function Calling 的区别
Function Calling 是模型“能调用函数”的原生能力,而 Skill 是“如何用好工具”的完整说明书和流程编排。
| 对比维度 | Function Calling(函数调用) | Skill(技能) |
|---|---|---|
| 核心定义 | 一种底层机制,让大模型能输出结构化的 JSON,表示“我要调用哪个函数,参数是什么”。 | 一个上层封装,是包含指令、脚本、资源的完整模块,用来指导 AI 完成一个复杂任务。 |
| 所在层次 | 执行层:解决“怎么调用”的问题,是模型和外部代码之间的桥梁。 | 能力层/调度层:解决“为什么调用、何时调用、按什么流程调用”的问题。 |
| 主要形态 | 通常是 JSON Schema,定义了函数的名称、参数和类型。 | 通常是一个指令文件,用自然语言写明任务目标、操作步骤、触发条件和示例。 |
| 核心职责 | 让 AI 能生成代码可以理解的、格式化的调用指令。 | 封装领域知识、标准操作流程(SOP)、多步骤编排和异常处理,让 AI 像有经验的专家那样工作。 |
| 两者关系 | 是 Skill 的基础。Skill 最终还是要通过 Function Calling 来触发对具体工具的调用。 | 是 Function Calling 的指挥官。它告诉 AI 在什么场景下,按什么顺序,去调用哪些 Function Calling。 |
简单说,Function Calling 给了 AI “手”去操作工具,而 Skill 给了 AI 一张“施工图纸”,告诉它什么时候该动手,第一步做什么,第二步做什么,以及做到什么标准才算合格。
五、如何定义一个 Skill
定义一个 Skill,核心是写好一个指令文件,它由两部分组成:
1. YAML Frontmatter(元数据)
文件顶部的元信息,必须包含 name 和 description,它们决定了 Skill 的触发时机:
name:技能的唯一标识,需与文件夹名一致。命名规则严格,只能用小写字母、数字和连字符(-),且不能以连字符开头或结尾。description:最关键的一行。它需要清晰地告诉 AI “这个技能能做什么”以及“什么时候该用它”。写得好的 description 应包含核心功能和具体的触发关键词。agent_created:如果 Skill 是通过自动生成方式创建的,需要加上agent_created: true,否则后续可能无法修改。
2. Markdown 正文(指令)
这是 Skill 的“灵魂”,指导 AI 如何执行任务。建议包含以下结构:
- 角色设定:给 AI 一个专业身份,比如“你是一名资深后端工程师”。
- 工作流(SOP):分步骤描述执行流程,比如“先通读 diff,再分模块检查,最后输出报告”。
- 约束与边界:设定必须遵守的规则或要避免的坑点,比如“必须考虑生产环境影响”。
3. 一个简单示例
---
name: code-review-expert
description: 专业后端代码审查技能。当用户提交代码 diff、PR 链接,或提到“代码审查”、“PR Review”时触发。按安全性、性能、规范三个维度输出 Markdown 格式报告。
agent_created: true
---
# 角色设定
你现在是拥有 10 年经验的 Senior Backend Engineer。
# 标准操作流程(SOP)
1. 通读 diff,理解变更意图。
2. 分模块检查:安全性、性能、规范。
3. 输出 Markdown 格式报告。
# 常见坑点
- 必须考虑生产环境影响
- 拒绝模糊结论
六、定义 Skill 时需要注意的关键点
1. 描述决定成败
Description 是整个 Skill 体系中最关键的一行文字,直接影响 Token 消耗和响应速度。写得不好会导致“该用时不用”(under-triggering)或“不该用时乱用”(over-triggering)。好的 description 应同时回答三个问题:能做什么、核心能力有哪些、什么情况下触发。
2. 保持单一职责
一个 Skill 只做一件事。不要把多个不相关的功能塞进同一个 Skill,因为规则之间容易冲突,导致 AI 执行混乱。如果需要多个能力协同,可以把它们拆成独立的 Skill,再通过流程编排组合使用。
3. 从高频场景开始
首个自定义 Skill 建议只解决一个明确且高频的问题,比如“整理会议记录”或“生成周报”。越高频的重复操作,越值得沉淀成 Skill。
4. 注意边界与安全
- 明确边界:在 Skill 中写清楚哪些事情不能做,比如“不评价年龄、婚育等合规敏感项”。
- 注意安全:Skill 可以调用本地文件或外部 API,安装第三方 Skill 时需要检查其权限和脚本内容,优先选择可信来源。
5. 多 Skill 协作与共享
复杂任务可以被拆解,由多个 Skill 接力完成。例如,“收集信息 → 生成报告 → 发送邮件”这个流程,可以分别调用网页搜索 Skill、PDF 生成 Skill 和邮件发送 Skill。它们之间通过共享知识池传递上下文,比如上一个 Skill 生成的文件路径、关键结论等,确保信息不断链。
此外,自定义 Skill 还可以共享给团队成员,统一团队的工作方式。
七、总结
Skill 体系是一个精密的“AI 调度系统”,它通过标准化的封装,让 AI 能够按需调用各类专业能力。这既解决了大模型“会聊不会做”的痛点,也让用户能通过创建和组合 Skill,把经验固化成可复用的自动化工具。
它与 Function Calling 的关系可以概括为:Function Calling 是底层执行机制,Skill 是上层能力封装与流程编排。两者配合,才能让 AI 从“能说”真正走向“能做”。
再附一个我在开发过程中使用的一个skill:
---
name: deep-root-cause-analysis
description: Systematically trace and debug complex data consistency issues in distributed systems with caching, async messaging, and multi-instance scenarios. Use when API returns incomplete or inconsistent data, when cache rebuild logic fails after restart, when data is lost across process/Kafka boundaries, or when multi-instance merge logic has field gaps.
---
# Deep Root Cause Analysis
## 适用场景
当遇到以下类型的问题时使用:
- API 返回数据不完整或与预期不一致
- 程序重启后缓存数据丢失
- 异步消息(Kafka/RabbitMQ)链路中数据被截断
- 多实例场景下数据合并不完整
- 双模式采集(MIN/FULL)策略下数据一致性断裂
- 序列化/反序列化后字段丢失
## 分析范式:八步定位法
### Step 1: 端到端数据流追踪
**目标**:画出从数据产生到 API 响应的完整数据流图。
**操作**:
1. 找到数据**产生入口**(定时调度器、事件触发器)
2. 找到**消息发送点**(Kafka producer、HTTP client)
3. 找到**消息消费点**(Kafka consumer、消息处理器)
4. 找到**实际处理逻辑**(数据解析器、转换器)
5. 找到**缓存写入点**(内存缓存、数据库)
6. 找到 **API 读取点**(HTTP handler、GraphQL resolver)
7. 确认每个节点间的数据传递方式(指针引用 / 值拷贝 / JSON 序列化)
**关键检查点**:
- 数据是指针传递还是值拷贝?值拷贝后是否写回?
- API 读取的字段与 parser 写入的字段是否一致(切片 vs Map)?
- 数据在哪些节点可能被过滤或丢弃?
### Step 2: 序列化边界检查
**目标**:识别数据在跨进程/Kafka 边界时是否丢失字段。
**常见陷阱**:
| 语言/框架 | 陷阱 |
|-----------|------|
| Go `json.Marshal` | 不序列化未导出字段(小写开头) |
| Go 值拷贝 | 结构体值拷贝后修改不会反映到原值 |
| Go Map vs Slice | Map 是引用类型,Slice header 是值拷贝 |
| Python `pickle` | 自定义类需要 `__getstate__` |
| Java Serializable | transient 字段不序列化 |
| Protobuf | 未知字段被丢弃 |
**检查清单**:
- [ ] 所有通过 JSON 传递的结构体,字段是否都有正确的 tag?
- [ ] 接收端反序列化后,未导出/未标记字段是否必然为零值?
- [ ] 合并逻辑中是否依赖了这些可能为零的字段?
### Step 3: 缓存生命周期分析
**目标**:分析缓存在重启后、重建时、合并时的行为。
**生命周期阶段检查**:
| 阶段 | 检查项 |
|------|--------|
| 重启后 | 缓存初始化为空,首次请求是否有强制刷新机制? |
| 重建 | 重建函数是否从旧缓存保留了必要字段(特别是静态字段)? |
| 合并 | MergeFrom 是否同步了所有字段类型(切片 + Map + 结构体)? |
| 覆盖 | else 分支是否用空数据覆盖了缓存中的有效数据? |
### Step 4: 时序竞态分析
**目标**:分析启动时序和消息消费时序是否导致消息丢失。
**操作**:
1. 绘制启动时间线:`T+0s: 程序启动 → T+Xs: consumer 就绪 → T+Ys: producer 启动`
2. 确认 consumer 在 producer 之前就绪
3. 检查消息 offset 策略(OffsetNewest vs OffsetOldest)
4. 确认首条消息不会被 consumer 遗漏
**关键问题**:
- consumer 和 producer 是否在同一进程?
- producer 是否等待 consumer 完成(如 chDone channel)?
- 多个 goroutine 是否会竞争同一资源?
### Step 5: 分支条件验证
**目标**:逐一验证每个条件分支,特别是 else 分支的副作用。
**检查每个 if/else**:
1. **if 分支**:预期的正常路径,数据如何处理?
2. **else 分支**:
- 是否用空值/默认值覆盖了有效数据?
- 是否跳过了必要的处理步骤?
- 是否有副作用(如重建切片导致 Map 数据丢失)?
3. **early return**:是否跳过了关键的"写回"操作?
### Step 6: 多实例合并分析
**目标**:确认多实例场景下,结果回传合并是否完整。
**检查项**:
1. sendResultMessage 是否包含完整数据?
2. JSON 序列化是否丢失字段?(回到 Step 2)
3. MergeFrom 是否同步所有字段?
4. `SenderId == self` 的判断逻辑是否正确?
5. 同一进程内直接写缓存 vs 跨进程走 MergeFrom 的路径是否一致?
### Step 7: 诊断日志注入
**目标**:在关键节点注入日志,确认实际执行路径和数据状态。
**日志注入点**:
1. **调度入口**:输出模式、缓存长度
2. **处理返回**:输出数据数量
3. **分支判断**:输出走了哪个分支
4. **缓存读取**:输出各字段大小(切片长度 + Map 长度)
5. **合并函数**:输入和输出的字段大小对比
**诊断决策树**:
- 如果 `fieldA=0` 但 `fieldAMap>0` → 切片未被重建
- 如果所有字段都为 0 → 数据采集从未成功
- 如果 `mapA>0` 但 `sliceA=0` → Map 到切片的转换逻辑有缺陷
### Step 8: 渐进式修复与验证
**原则**:一次修复一个问题,编译验证,逐步推进。
**修复优先级**:
| 优先级 | 类型 | 示例 |
|--------|------|------|
| P0 | 数据完全丢失 | 序列化丢字段、缓存未初始化 |
| P1 | 数据部分残缺 | 合并遗漏字段、else 覆盖 |
| P2 | 数据值错误 | 字段映射错误、类型转换 |
| P3 | 诊断增强 | 添加日志 |
| P4 | 防御性编程 | 增加空检查、边界保护 |
**每次修复后**:
1. 编译验证
2. 确认修复逻辑不引入新问题
3. 如果问题仍在,回到 Step 1 重新追踪
## 详细参考
- 完整检查清单和代码示例,参见 [reference.md](reference.md)
zengjian@Mac ~ %```