Skill 机制解析:从定义、触发到调用外部工具

转发请注明出处:

一、什么是 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 接口”,专门用来对接外部的各种软件和服务。

整个调用流程:

  1. 配置连接器:在连接器市场里找到需要的服务,手动点击“信任”并完成授权。这一步的核心是安全,遵循“最小权限”原则。
  2. Skill 声明依赖:在写 Skill 指令时,明确告诉 AI 这个 Skill 需要用到哪个连接器里的哪个工具,相当于列出它“被允许”使用的工具箱。
  3. 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(元数据)

文件顶部的元信息,必须包含 namedescription,它们决定了 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 ~ %```
posted @ 2026-09-11 15:30  未来AI笔记  阅读(135)  评论(0)    收藏  举报