一句话定位:读完这篇,你将完整理解从"用户说了一句话"到"Skill 返回结果"之间发生的每一件事,包括匹配、加载、安全校验、执行和监控。
1. 引言:用户说话之后发生了什么
前两篇我们完成了 Skill 的"设计时"工作:第一篇搞清楚了 Skill 是什么,第二篇学会了怎么写好一个 SKILL.md。现在假设你的"流水线故障诊断 Skill"已经写好并通过了测试,部署到了生产环境。
某天,用户在 Slack 里对 Agent 说:
"master 的 CI 挂了,帮我看看。"
从这句话被 Agent 接收到诊断报告返回给用户,中间发生了什么?
latex
用户:"master 的 CI 挂了"
│
▼
① 注册发现 ── Agent 怎么知道有哪些 Skill 可用?
│
▼
② 意图匹配 ── 怎么从众多 Skill 中找到"流水线故障诊断"?
│
▼
③ 资源加载 ── 找到之后加载什么?加载多少?
│
▼
④ 安全校验 ── 当前用户有没有权限执行这个 Skill?
│
▼
⑤ 执行编排 ── 只调一个 Skill 还是需要串联多个?
│
▼
⑥ 结果返回 ── 输出怎么回到用户手中?
│
▼
⑦ 监控记录 ── 整个过程怎么被观测和记录?
这七个环节中的每一个都影响着最终的用户体验和系统可靠性。本篇逐一拆解。
2. Skill 注册与发现
在 Agent 能调用一个 Skill 之前,它首先需要知道这个 Skill 的存在。这就是注册与发现机制要解决的问题。
2.1 注册:Skill 怎么让 Agent 知道自己存在
当一个新的 Skill 被创建或更新时,它的元数据(front matter 中的 name 和 description)需要被 Agent 平台感知到。主流的实现方式有两种:
静态注册 :Agent 平台启动时,扫描指定目录下所有 SKILL.md 文件的头部元数据,构建一个本地的 Skill 索引[1]。
latex
Agent 启动
│
▼
扫描 skills/ 目录
│
├─ 读取 pipeline-diagnosis/SKILL.md 的 front matter
├─ 读取 database-migration/SKILL.md 的 front matter
├─ 读取 code-review/SKILL.md 的 front matter
└─ ...
│
▼
构建本地 Skill 索引
┌──────────────────────────────────────────────────┐
│ name description │
│ pipeline-diagnosis "When a CI/CD pipeline..." │
│ database-migration "When a database schema..." │
│ code-review "When a pull request is..." │
└──────────────────────────────────────────────────┘
优点是实现简单、确定性强;缺点是新增或更新 Skill 后需要重启 Agent 才能生效。
动态注册:Skill 的变更通过事件通知机制实时推送给 Agent,无需重启。当一个新的 SKILL.md 被提交到代码仓库后,CI/CD 流水线自动将更新后的元数据推送到 Agent 平台的 Skill 注册中心。
latex
开发者提交 SKILL.md 更新
│
▼
CI/CD 流水线触发
│
▼
解析 front matter,推送到 Skill 注册中心
│
▼
Agent 下次匹配时自动获取最新索引
优点是更新即时生效;缺点是需要额外的事件通知基础设施。
两种方式的选择取决于团队规模和 Skill 变更频率。小团队、Skill 变更不频繁时,静态注册足够用;大团队、多团队协作、Skill 频繁迭代时,动态注册能避免"部署等待"的效率损失[2]。
2.2 Skill 索引的最小信息
不管哪种注册方式,Skill 索引中存储的最小信息是 SKILL.md 的 front matter:
yaml
name: pipeline-diagnosis
description: When a CI/CD pipeline build fails, fetch the build logs,
diagnose the failure type (dependency conflict, compile error, test
failure, or deploy timeout), locate the root cause, and attempt
auto-fix or provide fix suggestions.
索引中只存 name 和 description,不存正文。 这样做有两个原因:一是索引的体积要尽可能小,因为匹配阶段需要将用户输入与所有 Skill 的 description 做比较[1];二是正文只在 Skill 被选中后才需要加载,提前加载会浪费资源。
3. 匹配机制:怎么从众多 Skill 中找到对的那一个
用户说"master 的 CI 挂了",Agent 平台需要从 Skill 索引中找到最匹配的 Skill。这是整个运行时链路中最关键的一步------匹配错了,后续做得再好都没有意义。
3.1 基于关键词/规则的匹配
最简单直接的方式:用关键词或正则表达式匹配用户输入。
latex
规则示例:
- 用户输入包含 "CI" 或 "pipeline" 或 "构建失败" → 匹配 pipeline-diagnosis
- 用户输入包含 "数据库" 或 "migration" 或 "schema" → 匹配 database-migration
优点是确定性强、可解释、速度快。缺点是覆盖范围有限------用户说"master 挂了"(没有出现"CI"或"pipeline"),规则可能匹配不到。
关键词匹配适用于 Skill 数量少、触发表达相对固定的场景。
3.2 基于语义的匹配
当 Skill 数量增多、用户的表达方式多样化时,需要更智能的匹配方式。主流做法是将 Skill 的 description 和用户输入分别转换为向量(embedding),然后计算语义相似度[3][4]。
latex
用户输入:"master 的 CI 挂了"
│
▼
文本向量化(embedding)
│
▼
与 Skill 索引中所有 description 的向量做相似度计算
│
├─ pipeline-diagnosis 相似度: 0.92 ← 最高
├─ code-review 相似度: 0.45
├─ database-migration 相似度: 0.23
└─ ...
│
▼
选择相似度最高的 Skill
语义匹配的优势在于它能理解表达的含义而非字面词汇 。即使用户没说"CI"或"pipeline",只要表达的含义与 description 中描述的场景相关,就能匹配上。这种方法源于信息检索领域的向量检索技术(Vector Retrieval),在 RAG(Retrieval-Augmented Generation)系统中被广泛使用[3][4]。
语义匹配的局限性是存在误召回 的可能------语义相近但意图不同的输入可能被错误匹配。例如,用户说"我想学 CI/CD",语义上和 pipeline-diagnosis 相关,但实际上是在问学习资料,不是请求故障诊断。
3.3 混合匹配(推荐)
工程实践中推荐将两种方式结合[5]:
latex
用户输入
│
▼
规则匹配(优先)
│
├─ 命中确定性规则 → 直接选择该 Skill
│
└─ 未命中任何规则 → 进入语义匹配
│
▼
语义检索 Top-K 候选
│
▼
置信度评估
│
├─ 最高相似度 > 阈值(如 0.8)→ 选择该 Skill
│
└─ 最高相似度 < 阈值 → 询问用户确认
"我找到了以下可能相关的技能,
你想使用哪一个?"
这种混合方式的优势在于:
- 规则优先保证了确定性:对于高频、表达固定的场景(如"CI 挂了"),规则匹配快速且准确。
- 语义兜底覆盖了长尾:对于规则未覆盖的新表达方式,语义匹配提供了兜底能力。
- 置信度阈值防止了误召回:当匹配结果不确定时,主动询问用户而不是猜测。
其中的"置信度阈值"(如示例中的 0.8)不是一个通用常量,而是需要根据你使用的 Embedding 模型经验校准的 [5]。不同的 Embedding 模型产生的向量空间分布差异很大:
- OpenAI 的旧版模型(如
text-embedding-ada-002)的向量分布中,即使是不相关的文本,余弦相似度也很少低于 0.7;相关文本通常在 0.82~0.88 之间。在这种模型下,0.8 是一个常见的经验阈值。- OpenAI 的新版模型(如
text-embedding-3-large)或开源模型(如 BGE、Sentence-BERT)的向量分布完全不同。在某些开源模型中,0.6 可能就已经是非常高的相关度了。Pinecone、Weaviate 等主流向量数据库的官方文档均强调:相似度阈值需要根据具体的 Embedding 模型进行经验校准,不存在跨模型的通用值[5]。工程实践中的做法是:准备一个标注好的测试集(包含"应匹配"和"不应匹配"的输入对),在你选定的模型上跑一遍相似度分布,然后根据分布选择一个能在"召回率"和"准确率"之间取得平衡的阈值。
3.4 多候选排序
当多个 Skill 都能匹配时(例如"pipeline-diagnosis"和另一个"ci-monitoring"都与用户输入高度相关),需要排序策略来选择最佳候选:
| 排序因素 | 说明 |
|---|---|
| 语义相似度 | description 与用户输入的相似度得分 |
| 优先级声明 | Skill 可以在元数据中声明自己的优先级权重 |
| 上下文相关性 | 当前对话历史的影响------如果前面聊的是部署问题,"部署超时"相关的 Skill 优先级会上升 |
| 历史调用成功率 | 长期成功率高的 Skill 优先(质量信号) |
实际工程中,最终的排序通常是这些因素的加权组合。具体权重取决于业务场景,没有通用的最佳配置[5]。
4. 加载策略:选中之后加载什么
匹配到了正确的 Skill 之后,Agent 需要加载这个 Skill 的完整定义来执行。但"加载"不是把整个 SKILL.md 一股脑塞进上下文那么简单------上下文窗口是有限的资源[6],加载策略直接影响 Token 消耗和响应速度。
4.1 全量预加载
Agent 启动时,将所有 Skill 的完整 SKILL.md(包括正文中的全部执行指令)加载到上下文中。
latex
优点:匹配后立即执行,无加载延迟
缺点:Skill 数量多时,上下文窗口被大量占用
10 个 Skill × 平均 500 Token/个 = 5000 Token 仅用于 Skill 定义
还没开始对话,上下文窗口就用掉了一大块
适用场景:Skill 数量极少(3-5 个以内),且对响应速度要求极高。
4.2 按需加载
Agent 启动时不加载任何 Skill 正文。匹配成功后,只加载被选中的那一个 Skill 的完整 SKILL.md。
latex
优点:节省上下文窗口,只在需要时加载
缺点:匹配后有一次额外的加载延迟
适用场景 :Skill 数量较多、上下文窗口预算紧张的场景。这是大多数工程实践中的默认选择[1]。
4.3 分层加载
分层加载是前两种策略的折中,将 Skill 的信息分为多层,按需逐层加载[6]:
latex
┌────────────────────────────────────────────────────────────────┐
│ 第 0 层(常驻) │
│ 所有 Skill 的 name + description │
│ 用途:匹配阶段。占用 Token 少,可常驻上下文 │
│ 大小:每个 Skill 约 50-100 Token │
├────────────────────────────────────────────────────────────────┤
│ 第 1 层(匹配后加载) │
│ 被选中 Skill 的完整 SKILL.md 正文 │
│ 用途:执行阶段。只有匹配成功的 Skill 才加载 │
│ 大小:每个 Skill 约 300-2000 Token(取决于正文长度) │
├────────────────────────────────────────────────────────────────┤
│ 第 2 层(执行时按需) │
│ 外部 Prompt 模板(prompts/system.md) │
│ 少样本示例(prompts/few_shots/) │
│ 用途:仅在执行到需要 LLM 推理的步骤时才加载 │
│ 大小:视具体内容而定 │
└────────────────────────────────────────────────────────────────┘
为什么分层? 因为上下文窗口是 LLM 应用中最稀缺的资源之一[6]。GPT-4 的上下文窗口为 128K Token,Claude 3 为 200K Token,听起来很大,但当你把系统 Prompt、对话历史、Skill 定义、工具返回结果、少样本示例全部塞进去时,很快就捉襟见肘。分层加载确保"只在需要的时候才占用上下文空间"[6]。
4.4 缓存与预热
对于高频调用的 Skill(如"流水线故障诊断"可能每天被调用几十次),可以引入缓存机制:
会话级缓存:同一个用户会话中,如果一个 Skill 已经被加载过(第 1 层),后续再次调用时直接复用,不需要重新加载。
预热缓存:系统启动时,根据历史调用频率,预先将 Top-N 高频 Skill 的第 1 层内容加载到缓存中。这样高频 Skill 的首次调用也不会有加载延迟。
latex
预热策略示例:
Agent 启动 → 查询近 7 天 Skill 调用频率排行
→ Top-5 的 Skill 预加载第 1 层到缓存
→ "pipeline-diagnosis" 调用频率排名第 2 → 预热
缓存的一个关键原则是一致性 :当 SKILL.md 更新后,缓存中的旧版本必须被及时淘汰。通常通过版本号或内容哈希来实现缓存失效[7]。
5. 安全与权限控制
Skill 加载完成后,Agent 并不是直接就开始执行。在执行之前,还有一个关键的校验步骤:当前用户有没有权限执行这个 Skill?
5.1 最小权限原则
安全设计的基本原则是最小权限(Principle of Least Privilege) :每个主体只拥有完成其任务所需的最小权限集[8]。在 Agent Skill 的场景中,这意味着:
- 不是所有用户都能调用所有 Skill
- 一个 Skill 只能访问它声明需要的权限范围
- Agent 继承当前用户的权限,不能越权
以"流水线故障诊断 Skill"为例,它在 SKILL.md(或对应的配置)中声明了需要的权限:
latex
permissions: pipeline:read, pipeline:retry, dependency:rollback
当用户张三请求调用这个 Skill 时,系统会校验:
latex
张三的角色:开发工程师
张三的权限:pipeline:read ✓, pipeline:retry ✓, dependency:rollback ✗
→ 校验结果:
pipeline:read → 通过,可以拉取日志
pipeline:retry → 通过,可以触发重试
dependency:rollback → 不通过,无权回滚依赖
→ Skill 执行受限:
Steps 1-3(查询和分析)→ 正常执行
Step 4 的依赖回滚分支 → 跳过,提示"需要 dependency:rollback 权限,
请联系有权限的同事执行"
这种基于角色的访问控制(Role-Based Access Control,RBAC)模型源于 NIST 在 2004 年发布的标准[9],是企业级系统中最广泛采用的权限管理方式。在 Agent Skill 场景中,RBAC 的"角色"可以映射为用户的组织角色(开发工程师、运维工程师、Team Lead),"权限"映射为 Skill 声明的操作权限。
5.2 敏感操作的人机协同
即使用户拥有执行权限,某些高风险操作仍然需要人工确认。第二篇的 7.1 节讨论了 STOP 指令在 SKILL.md 中的使用。在运行时,这些人机协同的交互需要框架层的支撑才能可靠执行。
运行时的人机协同流程[10]:
latex
Agent 执行 Step 4(回滚依赖)
│
▼
识别到 SKILL.md 中的 "STOP and ask for user confirmation"
│
▼
Agent 框架拦截器暂停执行
│
▼
向用户展示待执行的操作和影响
"即将回滚 logback-classic 从 1.4.1 → 1.3.12。
这将影响以下 3 个服务:[list]
确认执行?[是/否]"
│
├─ 用户确认 → 恢复执行
│
└─ 用户拒绝 → 终止执行,记录日志
关键点在于"框架拦截器暂停执行"这一步------它不是 LLM 的自觉行为,而是Agent 运行时框架的硬性中断。
不同的框架实现这一机制的方式不同[10][11]:
- LangGraph :在图状态机层面原生提供了 断点(breakpoint) 机制,开发者可以在图的任意节点设置硬性暂停点。这是专为人机协同设计的原生能力。
- OpenAI Assistants API :当 Assistant 决定调用一个函数时,Run 对象的状态变为
requires_action,执行被挂起,等待开发者将函数执行结果通过submit_tool_outputs提交后才恢复。这个状态的设计初衷是处理工具调用的结果回传,而不是专门的人机协同审批 。工程上要实现人机协同,通常的做法是定义一个特殊的工具(如request_user_approval),当 LLM 决定调用这个工具时,Run 进入requires_action状态暂停;前端收到该状态后展示确认/拒绝按钮给用户;用户做出选择后,开发者将用户的选择作为工具输出提交回去,恢复 Run。本质上是借用工具调用的中断机制来实现人机协同。
因此,仅靠 SKILL.md 中的 STOP 文字指令是不够的------LLM 可能忽略或绕过文字指令[18]。STOP 是指令层面的提示,框架级拦截器(LangGraph 的 breakpoint)或借用工具调用中断机制(OpenAI 的 requires_action 变通方案)是工程层面的保障,两者配合才能实现可靠的人机协同。
5.3 调用限流
限流(Rate Limiting)是保护系统不被过度调用的基本手段。在 Agent Skill 场景中,限流有两层[12]:
用户级限流:防止单个用户过度调用某个 Skill。
latex
场景:某用户反复说"帮我看看 CI 挂了",触发了 100 次
"流水线故障诊断 Skill",每次都要拉取日志、调用 LLM 分析
→ CI/CD 平台 API 被打爆,LLM Token 消耗暴涨
限制:每用户每小时最多 20 次调用 pipeline-diagnosis
触发后:返回提示"调用频率过高,请稍后再试"
系统级限流:保护下游依赖服务不被 Agent 整体打爆。
latex
场景:所有用户的 pipeline-diagnosis 调用加起来,每小时超过
CI/CD 平台 API 的承载能力
限制:全系统每分钟最多 100 次 cicd_platform_api 调用
触发后:排队等待或降级处理
5.4 熔断
熔断(Circuit Breaker)是比限流更进一步的保护机制。当某个操作的失败率超过阈值时,系统主动"断开"这个操作,快速返回失败而不是继续重试浪费资源[13]。
在 Agent Skill 场景中,熔断有两个典型场景:
Skill 调用级熔断:
latex
场景:同一会话中,Agent 反复调用 pipeline-diagnosis,
每次都失败(CI/CD 平台持续故障),但 Agent 不断重试
熔断条件:同一 pipeline 连续自动修复尝试超过 3 次
熔断后行为:
1. 停止自动修复尝试
2. 生成当前已有的诊断报告(即使不完整)
3. 通知 on-call 工程师介入
工具级熔断:
latex
场景:cicd_platform_api 在过去 5 分钟内的失败率超过 50%
熔断后行为:
所有调用 cicd_platform_api 的 Skill 立即走降级路径
(使用本地缓存或提示用户提供数据)
而不是继续尝试调用一个大概率会失败的 API
熔断器的经典状态机由 Martin Fowler 在 2014 年总结[13]:
latex
正常请求成功率高
┌──────────────────┐
│ │
│ 关闭状态 │◀──────────┐
│ (正常放行) │ │
│ │ 探测成功 │
└────────┬─────────┘ │
│ │
失败率超过阈值 ┌───┴──────────┐
│ │ │
▼ │ 半开状态 │
┌──────────────────┐ │ (允许少量请求 │
│ │ │ 探测服务恢复)│
│ 打开状态 │──────▶│ │
│ (全部快速失败) │ 超时 └──────────────┘
│ │ 后尝试探测
└──────────────────┘
在 SKILL.md 中,熔断条件以自然语言声明。Agent 框架负责在运行时维护熔断器状态并执行相应的拦截逻辑。
6. 动态编排与上下文流转
第二篇讨论了单个 Skill 内部的步骤编排(静态编排)。但在运行时,Agent 可能需要动态地串联多个 Skill 来完成一个复杂任务。这就是动态编排。
6.1 什么时候需要多 Skill 协作
回到那个场景:用户说"master 的 CI 挂了,帮我看看"。
Agent 调用了"流水线故障诊断 Skill",诊断结果是"依赖冲突导致编译失败"。接下来用户说:
"那顺便帮我看看,这个依赖冲突是不是上次那个数据库迁移引进来的?"
这时 Agent 需要调用第二个 Skill------"数据库迁移分析 Skill"------来检查最近的数据库迁移是否修改了相关依赖。两个 Skill 之间存在数据依赖:第一个 Skill 的诊断结果(冲突的包名)是第二个 Skill 的输入。
这种多 Skill 协作不是在编写 SKILL.md 时预定义的 (那是静态编排),而是 Agent 在运行时根据对话进展动态决策 的[14]。
6.2 动态编排的模式
串行编排:Skill B 需要 Skill A 的输出作为输入。
latex
用户:"CI 挂了,帮我看看是不是迁移引起的"
│
▼
Agent 调用 pipeline-diagnosis Skill
→ 输出:failure_type = "dependency", root_cause = "logback 冲突"
│
▼
Agent 判断:需要检查最近的数据库迁移
→ 调用 database-migration Skill(输入:冲突的包名)
→ 输出:最近迁移未修改 logback 相关依赖
│
▼
Agent 综合两个 Skill 的结果,回复用户:
"诊断结果是 logback 依赖冲突,但与最近的数据库迁移无关。"
并行编排:多个 Skill 之间没有数据依赖,可以同时执行。
latex
用户:"CI 挂了,帮我看看原因,顺便查一下这个服务最近的告警记录"
│
▼
Agent 同时调用:
├─ pipeline-diagnosis Skill(诊断 CI 故障)
└─ alert-history Skill(查询告警记录)
│
▼
两个结果返回后,Agent 综合回复用户
并行执行的优势是减少了总响应时间(两个 Skill 的耗时取较长的那个,而不是两者之和)。但只有当两个 Skill 之间没有数据依赖时才能并行[14]。
6.3 上下文传递
多 Skill 协作时,一个核心问题是:Skill 之间怎么共享信息?
latex
pipeline-diagnosis Skill 的输出:
{
"failure_type": "dependency",
"root_cause": "logback-classic 1.4.x 与 spring-boot-starter-logging 冲突",
"affected_packages": ["logback-classic", "spring-boot-starter-logging"]
}
│
▼
这些信息需要传递给 database-migration Skill
在 Agent 系统中,上下文传递通常通过**共享的上下文对象(Context Object)**实现。Agent 编排层维护一个上下文对象,每个 Skill 的输出被写入上下文,后续 Skill 从上下文中读取所需信息[11]。
上下文管理的一个重要约束是Token 预算 。随着对话轮次增多和 Skill 调用次数增加,上下文会越来越长。当上下文接近窗口上限时,需要做摘要压缩 [6]:
latex
对话开始 上下文大小:~2K Token
│
├─ 调用 pipeline-diagnosis Skill → 上下文增加 ~1.5K
├─ 调用 database-migration Skill → 上下文增加 ~1K
├─ 几轮对话 → 上下文增加 ~3K
│
▼
上下文大小:~7.5K Token
│
▼
接近预算上限时,对早期内容做摘要压缩
旧内容:"[2026-07-06 10:00] 用户请求诊断 CI 故障。
诊断结果:logback 依赖冲突,建议回滚到 1.3.12。
用户确认执行了回滚。"
压缩为:"用户请求诊断 CI 故障(logback 冲突),已回滚修复。"
摘要压缩会丢失细节,所以只在必要时触发。一个常见的策略是设置上下文 Token 预算(如 80% 的窗口大小),超过预算时才开始压缩[6]。
7. Skill 执行生命周期
把上面所有环节串起来,一个 Skill 从被触发到执行完成,经历以下生命周期:
latex
┌─────────────────────────────────────────────────────────────┐
│ │
│ 触发(Trigger) │
│ 用户输入 / Webhook 事件 / 定时触发 │
│ │ │
│ ▼ │
│ 匹配(Match) │
│ 规则匹配 → 语义匹配 → 置信度评估 │
│ │ │
│ ├─ 未匹配 → 返回"没有找到相关技能" │
│ │ │
│ ▼ │
│ 加载(Load) │
│ 加载被选中 Skill 的完整 SKILL.md │
│ │ │
│ ▼ │
│ 校验(Validate) │
│ 权限校验 → 参数校验(Step 1) │
│ │ │
│ ├─ 参数缺失 → 追问用户(最多 2 轮) │
│ ├─ 权限不足 → 返回权限错误,提示需要的权限 │
│ │ │
│ ▼ │
│ 执行(Execute) │
│ 按 SKILL.md 的 Step 2 ~ Step N 逐步执行 │
│ │ │
│ ├─ 正常执行 → 产出结果 │
│ ├─ 工具调用失败 → 超时 → 重试 → 降级 │
│ ├─ 触发 STOP → 暂停,等待人工确认 → 确认后恢复 │
│ ├─ 触发熔断 → 快速失败,返回已有结果 │
│ │ │
│ ▼ │
│ 输出(Output) │
│ 生成结构化结果(JSON)+ 人类可读报告 │
│ │ │
│ ▼ │
│ 记录(Record) │
│ 写入执行日志:谁 / 何时 / 哪个 Skill / 输入 / 输出 / 耗时 │
│ 更新上下文:将结果写入上下文对象供后续 Skill 使用 │
│ │ │
│ ▼ │
│ 返回(Return) │
│ 将结果返回给用户 / 返回给调用方 Agent │
│ │
└─────────────────────────────────────────────────────────────┘
这个生命周期中的几个关键状态转换值得注意:
追问 → 重入校验:当 Step 1 追问用户后获取到了缺失参数,不是直接跳到 Step 2,而是重新进入校验阶段------因为用户的回答可能包含无效参数。
STOP → 恢复执行 :人工确认后恢复执行时,Agent 不是从头开始,而是从 STOP 标注处继续。这要求框架层能够保存和恢复 Skill 的执行状态[10][11]。
熔断 → 部分结果返回:熔断后的返回不是"执行失败",而是"已完成部分步骤的结果 + 熔断原因"。这比完全失败更有价值------用户至少拿到了诊断分析,只是没有自动修复。
8. 监控与可观测性
Skill 在生产环境中运行后,你需要知道它的表现如何。监控与可观测性(Observability)是持续改进的基础[15]。
8.1 执行日志设计
每次 Skill 调用都应该记录结构化日志。日志中包含的关键字段:
json
{
"timestamp": "2026-07-06T14:32:05Z",
"trace_id": "tr-20260706-a8f3",
"user_id": "zhang.san",
"session_id": "sess-20260706-1122",
"skill_name": "pipeline-diagnosis",
"skill_version": "2.1.0",
"trigger_source": "user_input",
"input_params": {
"pipeline_id": "pl-20260706-a3f7",
"branch": "master"
},
"match_score": 0.92,
"match_method": "hybrid_rule+semantic",
"execution_steps_completed": [1, 2, 3, 4, 5],
"execution_step_failed": null,
"output_result": {
"failure_type": "dependency",
"fix_result": "success"
},
"latency_ms": 8430,
"token_used": 3200,
"tools_called": [
{ "name": "cicd_platform_api", "latency_ms": 1200, "status": "success" },
{ "name": "git_api", "latency_ms": 800, "status": "success" },
{ "name": "dependency_registry_api", "latency_ms": 500, "status": "success" },
{ "name": "cicd_trigger_api", "latency_ms": 1100, "status": "success" }
],
"human_confirmation_required": true,
"human_confirmation_result": "approved",
"error": null
}
trace_id 是贯穿整个调用链的唯一标识,用于跨服务的链路追踪[15]。当一个 Skill 调用了多个外部 Tool 时,通过 trace_id 可以把它们串联起来,看到完整的调用链路和每个环节的耗时。
8.2 核心监控指标
基于执行日志,可以计算以下关键指标:
匹配指标:
| 指标 | 定义 | 意义 |
|---|---|---|
| 匹配命中率 | 成功匹配到 Skill 的请求占比 | 反映 Skill 覆盖度------命中率低说明有用户需求没有被 Skill 覆盖 |
| 匹配准确率 | 匹配到的 Skill 是否正确的比例(需要人工标注验证) | 反映 description 的质量------准确率低说明需要优化 description |
执行指标:
| 指标 | 定义 | 意义 |
|---|---|---|
| 任务完成率 | 成功完成全部步骤的调用占比 | 反映 Skill 的可靠性 |
| 步骤失败分布 | 哪个步骤最容易失败 | 定位 Skill 内部的薄弱环节 |
| P50/P95/P99 延迟 | 50%/95%/99% 的请求在多少毫秒内完成 | 反映用户体验------P95 延迟过高说明尾部用户体验差 |
| Token 消耗 | 每次调用平均消耗多少 Token | 成本控制------Token 消耗突增可能意味着 Prompt 变长或少样本示例过多 |
工具调用指标:
| 指标 | 定义 | 意义 |
|---|---|---|
| 工具调用成功率 | 每个外部 API 调用的成功率 | 反映外部依赖的健康度 |
| 降级触发率 | 走降级路径的调用占比 | 降级率上升说明主路径的外部服务不稳定 |
| 熔断触发次数 | 熔断器被触发的频率 | 熔断频繁说明外部服务有系统性问题 |
8.3 如何实现监控与可观测性
前面两节定义了"记什么日志"和"看什么指标"。这一节回答另一个关键问题:这些数据从哪里来、怎么采集、怎么展示?
可观测性(Observability)的三大支柱是日志(Logging)、指标(Metrics)、链路追踪(Tracing) ,这一框架由 CNCF 的 OpenTelemetry 项目标准化[15]。在 Agent Skill 的场景中,三者的实现方式如下:
8.3.1 日志采集
日志是最基础的数据来源。8.1 节展示的结构化 JSON 日志需要被采集、存储和索引,才能支持后续的查询和分析。
采集方式 :Agent 框架在 Skill 的每个生命周期节点(触发、匹配、加载、校验、执行步骤开始/结束、工具调用、输出)自动产生一条日志事件。这些事件由日志采集器(如 Fluentd、Fluent Bit、Vector)收集后写入日志存储[15]。
latex
Agent 框架产生日志事件
│
▼
日志采集器(Fluentd / Fluent Bit / Vector)
│
▼
日志存储(Elasticsearch / Loki / ClickHouse)
│
▼
查询与分析(Kibana / Grafana / 自定义 Dashboard)
日志级别设计:
| 级别 | 何时使用 | 示例 |
|---|---|---|
| INFO | 正常的生命周期事件 | "Skill pipeline-diagnosis 被触发"、"Step 3 执行完成" |
| WARN | 可恢复的异常 | "CI/CD API 超时,正在重试(第 1 次)"、"降级到本地缓存" |
| ERROR | 不可恢复的失败 | "所有重试均失败,降级方案也不可用"、"权限校验未通过" |
| FATAL | 需要立即介入的系统级问题 | "熔断器被触发"、"上下文窗口溢出" |
关键实践 :日志中必须包含 trace_id(贯穿整个调用链的唯一标识)和 skill_name + skill_version(用于关联 Skill 的版本变更和执行表现)。没有这两个字段,后续的问题排查和版本对比将无法进行。
8.3.2 指标采集与存储
日志记录的是"每一次调用的详情",而指标是"一段时间内的统计聚合"。指标需要被时序化存储,以支持趋势查询和告警规则评估。
采集方式 :Agent 框架在 Skill 执行完成后,将关键数据点(延迟、Token 消耗、成功/失败、工具调用状态)以指标事件的形式发送到指标收集器[15]。
latex
Agent 框架产生指标事件
│
▼
指标收集器(OpenTelemetry Collector / Prometheus Exporter)
│
▼
时序数据库(Prometheus / InfluxDB / Datadog)
│
▼
可视化(Grafana / Datadog Dashboard)
指标类型选择:
| 指标场景 | 推荐类型 | 原因 |
|---|---|---|
| 任务完成率、匹配命中率 | 计数器(Counter) | 只增不减,计算比率时用两个计数器相除 |
| P50/P95/P99 延迟 | 直方图(Histogram) | 需要计算分位数,Histogram 天然支持 |
| 当前活跃 Skill 调用数 | 仪表盘(Gauge) | 有增有减的瞬时值 |
| Token 消耗 | 计数器(Counter) | 按时间窗口聚合计算平均值 |
8.3.3 链路追踪
当一个用户请求触发了多个 Skill 的串行或并行调用时,链路追踪(Distributed Tracing)能够展示完整的调用链路,让开发者看到"用户请求 → Agent 匹配 → Skill A 执行 → Skill A 调用 Tool X → Skill B 执行 → 返回结果"的全过程,以及每个环节的耗时[15]。
latex
用户请求 [trace: tr-001]
│
├─ Agent 匹配 [0-50ms]
│ ├─ 规则匹配 [0-10ms]
│ └─ 语义匹配 [10-50ms]
│
├─ pipeline-diagnosis Skill [50-8000ms]
│ ├─ Step 1: 参数校验 [50-100ms]
│ ├─ Step 2: 拉取日志 [100-1500ms]
│ │ └─ cicd_platform_api 调用 [100-1500ms] ⚠️ 慢
│ ├─ Step 3: LLM 诊断 [1500-5000ms]
│ │ └─ LLM API 调用 [1500-5000ms]
│ ├─ Step 4: 执行修复 [5000-7000ms]
│ │ ├─ STOP: 等待人工确认 [5000-6500ms]
│ │ └─ cicd_trigger_api 调用 [6500-7000ms]
│ └─ Step 5: 生成报告 [7000-8000ms]
│
└─ 响应返回 [8000-8100ms]
实现方式 :在 Agent 框架层面,为每个用户请求生成一个全局唯一的 trace_id。每次调用 Skill 或 Tool 时,生成一个 span_id(子调用标识),并将 trace_id 作为上下文传递下去。所有 span 被收集后,通过 trace_id 关联成完整的调用链路。OpenTelemetry SDK 提供了标准化的 API 来实现这一过程[15]。
工程实践中的关键点:
- LLM 调用也应被追踪:LLM API 调用(如 GPT-4、Claude)通常占 Skill 执行时间的 50% 以上。在 span 中记录每次 LLM 调用的 Token 消耗和延迟,是定位性能瓶颈的关键。
- 人工等待时间需要单独标记 :当 Skill 执行到
STOP等待人工确认时,这段等待时间不应计入"Skill 执行延迟"。在 span 中用wait_for_human标签标记,便于区分"系统慢"和"人在思考"。
8.3.4 Dashboard 设计
监控数据的价值在于可被看见和可被行动。以下是推荐的 Dashboard 布局:
latex
┌──────────────────────────────────────────────────────────────┐
│ 实时概览 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 调用量/分 │ │ 任务完成率│ │ P95 延迟 │ │ Token/h │ │
│ │ 42 │ │ 94.2% │ │ 6.2s │ │ 18.5K │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Skill 健康度排行 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ pipeline-diagnosis ████████████████████ 96% │ │
│ │ database-migration ██████████████████ 93% │ │
│ │ code-review ████████████████ 88% │ │
│ │ alert-history ████████████ 72% ⚠️ │ │
│ └──────────────────────────────────────────────────────┘ │
├───────────────────────┬──────────────────────────────────────┤
│ 步骤失败分布 │ 调用趋势(24h) │
│ Step 2: 8% │ 📈 │
│ Step 3: 3% │ │
│ Step 4: 5% │ │
│ Step 5: 1% │ │
└───────────────────────┴──────────────────────────────────────┘
├──────────────────────────────────────────────────────────────┤
│ 工具调用健康度 │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ cicd_platform_api 成功率: 97% P95: 1.8s │ │
│ │ git_api 成功率: 99% P95: 0.6s │ │
│ │ dependency_registry 成功率: 91% P95: 2.3s ⚠️ │ │
│ │ cicd_trigger_api 成功率: 98% P95: 1.1s │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Dashboard 的设计原则:
- 第一屏看全局:实时概览中的四个数字让你在 3 秒内判断"系统是否正常"。
- 第二屏看异常:健康度排行用颜色和进度条突出异常 Skill(如 alert-history 的 72%)。
- 第三屏看细节:步骤失败分布和工具调用健康度帮助定位"坏在哪里"。
工具选型参考:
| 需求 | 推荐工具 | 说明 |
|---|---|---|
| 日志采集 | Fluent Bit / Vector | 轻量级日志采集器,支持多目标输出 |
| 日志存储与查询 | Elasticsearch + Kibana / Grafana Loki | 前者功能全面,后者轻量且与 Grafana 深度集成 |
| 指标存储 | Prometheus / Datadog | 前者开源,后者提供托管服务 |
| 链路追踪 | OpenTelemetry + Jaeger / Tempo | OpenTelemetry 作为采集标准,Jaeger/Tempo 作为后端存储 |
| 可视化 Dashboard | Grafana | 开源,支持日志、指标、链路追踪三大数据源统一展示 |
以上工具选型基于 CNCF(Cloud Native Computing Foundation)的生态推荐[15]。具体选择取决于团队的技术栈和运维能力。
8.4 告警规则
监控指标需要配合告警规则才能在问题发生时及时通知[15]:
latex
P1 告警(立即响应):
- 任务完成率 < 50% 持续超过 10 分钟
- 熔断器被触发
P2 告警(4 小时内响应):
- 匹配命中率下降超过 20%(与上周同期相比)
- P95 延迟超过 30 秒
P3 告警(下一个工作日响应):
- Token 消耗日均增长超过 30%
- 某个工具的降级触发率超过 10%
告警不是目的,告警驱动的行动才是目的。每个告警规则都应该对应一个明确的处理预案:P1 告警触发时,值班工程师需要做什么;P2 告警触发时,团队需要排查什么。
9. 小结
本篇拆解了从"用户说了一句话"到"Skill 返回结果"之间的完整链路。
注册与发现解决了"Agent 怎么知道有哪些 Skill"的问题。静态注册简单直接,动态注册支持实时更新,选择取决于团队规模和变更频率。
匹配机制解决了"怎么从众多 Skill 中找到对的那一个"的问题。关键词匹配保证确定性,语义匹配覆盖长尾表达,混合匹配加置信度阈值是工程实践中的推荐方案。
加载策略 解决了"找到之后加载什么、加载多少"的问题。分层加载(第 0 层常驻元数据、第 1 层按需加载正文、第 2 层执行时按需加载 Prompt)在上下文窗口有限的约束下实现了资源的最优分配[6]。
安全与权限控制 解决了"谁有权执行什么"的问题。最小权限原则[8]确保 Skill 只能做它被授权的事;人机协同通过框架级拦截器(而非仅靠 LLM 自觉)保障高风险操作必须经过确认[10][11];限流和熔断保护系统不被过度调用或级联故障拖垮[12][13]。
动态编排解决了"一个任务需要多个 Skill 协作怎么办"的问题。串行编排处理有数据依赖的场景,并行编排提升无依赖场景的效率,上下文对象承载 Skill 间的信息流转。
监控与可观测性 解决了"Skill 在生产环境表现如何"的问题。结构化执行日志、核心指标、告警规则三者构成了从"发现问题"到"定位问题"到"驱动改进"的闭环[15]。
现在你知道了 Skill 在运行时的全链路机制。但还有最后一个问题没有回答:Skill 上线之后,怎么保证它持续稳定、持续进化? 当外部 API 变了怎么办?当用户的需求变了怎么办?当 Skill 不再被需要了怎么办?------这些生命周期治理的问题,就是下一篇的主题。
下一篇预告:《Agent Skill 工程化指南(四):治理与演进 --- 从上线到退役的全生命周期》
参考来源
- SKILL.md 的加载模式 --- 在多个 Agent 开发框架中,Skill 的匹配阶段只使用头部元数据(
name+description),正文在匹配成功后才被加载。这是"最小信息匹配、按需加载完整定义"的通用模式。 - 服务注册与发现 --- 动态注册与发现是微服务架构中的成熟模式。Consul、etcd、ZooKeeper 等工具提供了服务注册中心的基础设施。参见 HashiCorp Consul Documentation。
- 向量检索(Vector Retrieval) --- 将文本转换为高维向量并通过余弦相似度等度量进行近邻检索,是现代信息检索和 RAG 系统的基础技术。Pinecone、Weaviate、Milvus 等向量数据库提供了工程化实现。参见 Pinecone: What is Vector Search?。
- Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks --- Reimers, N. & Gurevych, I. (2019). 提出了高效的句子向量化方法,广泛用于语义相似度计算和语义检索。参见 arXiv:1908.10084。
- 混合检索(Hybrid Search) --- 将传统的关键词检索(如 BM25)与语义向量检索结合,是现代搜索系统的推荐实践。Elasticsearch 8.x 引入了混合检索支持。参见 Elasticsearch: Reciprocal Rank Fusion。
- LLM 上下文窗口管理 --- 上下文窗口(Context Window)是 LLM 能处理的 Token 上限。GPT-4 Turbo 为 128K Token,Claude 3 为 200K Token。超出窗口的信息会被截断,因此需要管理上下文中的信息优先级和压缩策略。参见 OpenAI Docs: Token Limits、Anthropic Docs: Context Windows。
- HTTP Caching (RFC 7234) --- 缓存失效策略的标准参考。参见 RFC 7234: HTTP/1.1 Caching。
- Saltzer, J.H. & Schroeder, M.D. (1975). "The Protection of Information in Computer Systems" --- 提出了最小权限原则(Principle of Least Privilege)等计算机安全设计原则。参见 Proceedings of the IEEE, 63(9), 1278-1308。
- NIST RBAC Standard (INCITS 359-2004) --- 美国国家标准与技术研究院发布的基于角色的访问控制标准。参见 NIST: Role Based Access Control。
- LangGraph: Human-in-the-loop --- LangChain 的 LangGraph 框架提供了断点(breakpoint)和人工审批(human-in-the-loop)机制,允许在 Agent 执行流程中设置硬性暂停点。参见 LangGraph Docs: Human-in-the-loop。
- OpenAI Assistants API: Required Action --- OpenAI 的 Assistants API 在需要人工输入时返回
requires_action状态,暂停执行直到人工提供输入。参见 OpenAI Docs: Assistants API。 - Rate Limiting --- API 限流是保护后端服务的基本手段。常见算法包括令牌桶(Token Bucket)、漏桶(Leaky Bucket)和滑动窗口(Sliding Window)。参见 AWS: Throttling API requests。
- Martin Fowler: Circuit Breaker --- 熔断器设计模式,用于防止级联故障。当失败率超过阈值时"熔断",快速失败而非继续重试。参见 martinfowler.com/bliki/Circu...。
- Orchestration Patterns in Multi-Agent Systems --- 多 Agent / 多 Skill 协作中的编排模式。串行编排、并行编排和基于图的编排(如 LangGraph 的 StateGraph)是主要的实现方式。参见 LangGraph Docs。
- OpenTelemetry --- CNCF 的可观测性框架,定义了分布式系统中的链路追踪(Tracing)、指标(Metrics)和日志(Logging)三大支柱。Agent Skill 的监控设计遵循同样的可观测性原则。参见 OpenTelemetry Documentation。