Agent Skill 工程化指南(三):运行与生效 — 运行时的全链路机制

4 阅读33分钟

一句话定位:读完这篇,你将完整理解从"用户说了一句话"到"Skill 返回结果"之间发生的每一件事,包括匹配、加载、安全校验、执行和监控。


1. 引言:用户说话之后发生了什么

前两篇我们完成了 Skill 的"设计时"工作:第一篇搞清楚了 Skill 是什么,第二篇学会了怎么写好一个 SKILL.md。现在假设你的"流水线故障诊断 Skill"已经写好并通过了测试,部署到了生产环境。

某天,用户在 Slack 里对 Agent 说:

"master 的 CI 挂了,帮我看看。"

从这句话被 Agent 接收到诊断报告返回给用户,中间发生了什么?

用户:"master 的 CI 挂了"
  │
  ▼
  ① 注册发现  ──  Agent 怎么知道有哪些 Skill 可用?
  │
  ▼
  ② 意图匹配  ──  怎么从众多 Skill 中找到"流水线故障诊断"?
  │
  ▼
  ③ 资源加载  ──  找到之后加载什么?加载多少?
  │
  ▼
  ④ 安全校验  ──  当前用户有没有权限执行这个 Skill?
  │
  ▼
  ⑤ 执行编排  ──  只调一个 Skill 还是需要串联多个?
  │
  ▼
  ⑥ 结果返回  ──  输出怎么回到用户手中?
  │
  ▼
  ⑦ 监控记录  ──  整个过程怎么被观测和记录?

这七个环节中的每一个都影响着最终的用户体验和系统可靠性。本篇逐一拆解。


2. Skill 注册与发现

在 Agent 能调用一个 Skill 之前,它首先需要知道这个 Skill 的存在。这就是注册与发现机制要解决的问题。

2.1 注册:Skill 怎么让 Agent 知道自己存在

当一个新的 Skill 被创建或更新时,它的元数据(front matter 中的 namedescription)需要被 Agent 平台感知到。主流的实现方式有两种:

静态注册:Agent 平台启动时,扫描指定目录下所有 SKILL.md 文件的头部元数据,构建一个本地的 Skill 索引[1]

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 注册中心。

开发者提交 SKILL.md 更新
  │
  ▼
CI/CD 流水线触发
  │
  ▼
解析 front matter,推送到 Skill 注册中心
  │
  ▼
Agent 下次匹配时自动获取最新索引

优点是更新即时生效;缺点是需要额外的事件通知基础设施。

两种方式的选择取决于团队规模和 Skill 变更频率。小团队、Skill 变更不频繁时,静态注册足够用;大团队、多团队协作、Skill 频繁迭代时,动态注册能避免"部署等待"的效率损失[2]

2.2 Skill 索引的最小信息

不管哪种注册方式,Skill 索引中存储的最小信息是 SKILL.md 的 front matter:

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.

索引中只存 namedescription,不存正文。 这样做有两个原因:一是索引的体积要尽可能小,因为匹配阶段需要将用户输入与所有 Skill 的 description 做比较[1];二是正文只在 Skill 被选中后才需要加载,提前加载会浪费资源。


3. 匹配机制:怎么从众多 Skill 中找到对的那一个

用户说"master 的 CI 挂了",Agent 平台需要从 Skill 索引中找到最匹配的 Skill。这是整个运行时链路中最关键的一步——匹配错了,后续做得再好都没有意义。

3.1 基于关键词/规则的匹配

最简单直接的方式:用关键词或正则表达式匹配用户输入。

规则示例:
  - 用户输入包含 "CI" 或 "pipeline" 或 "构建失败" → 匹配 pipeline-diagnosis
  - 用户输入包含 "数据库" 或 "migration" 或 "schema" → 匹配 database-migration

优点是确定性强、可解释、速度快。缺点是覆盖范围有限——用户说"master 挂了"(没有出现"CI"或"pipeline"),规则可能匹配不到。

关键词匹配适用于 Skill 数量少、触发表达相对固定的场景。

3.2 基于语义的匹配

当 Skill 数量增多、用户的表达方式多样化时,需要更智能的匹配方式。主流做法是将 Skill 的 description 和用户输入分别转换为向量(embedding),然后计算语义相似度[3][4]

用户输入:"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]

用户输入
  │
  ▼
规则匹配(优先)
  │
  ├─ 命中确定性规则 → 直接选择该 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(包括正文中的全部执行指令)加载到上下文中。

优点:匹配后立即执行,无加载延迟
缺点:Skill 数量多时,上下文窗口被大量占用
       10 个 Skill × 平均 500 Token/个 = 5000 Token 仅用于 Skill 定义
       还没开始对话,上下文窗口就用掉了一大块

适用场景:Skill 数量极少(3-5 个以内),且对响应速度要求极高。

4.2 按需加载

Agent 启动时不加载任何 Skill 正文。匹配成功后,只加载被选中的那一个 Skill 的完整 SKILL.md。

优点:节省上下文窗口,只在需要时加载
缺点:匹配后有一次额外的加载延迟

适用场景:Skill 数量较多、上下文窗口预算紧张的场景。这是大多数工程实践中的默认选择[1]

4.3 分层加载

分层加载是前两种策略的折中,将 Skill 的信息分为多层,按需逐层加载[6]

┌────────────────────────────────────────────────────────────────┐
│  第 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 的首次调用也不会有加载延迟。

预热策略示例:
  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(或对应的配置)中声明了需要的权限:

permissions: pipeline:read, pipeline:retry, dependency:rollback

当用户张三请求调用这个 Skill 时,系统会校验:

张三的角色:开发工程师
张三的权限: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]

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。

场景:某用户反复说"帮我看看 CI 挂了",触发了 100 次
     "流水线故障诊断 Skill",每次都要拉取日志、调用 LLM 分析
     → CI/CD 平台 API 被打爆,LLM Token 消耗暴涨

限制:每用户每小时最多 20 次调用 pipeline-diagnosis
触发后:返回提示"调用频率过高,请稍后再试"

系统级限流:保护下游依赖服务不被 Agent 整体打爆。

场景:所有用户的 pipeline-diagnosis 调用加起来,每小时超过
     CI/CD 平台 API 的承载能力

限制:全系统每分钟最多 100 次 cicd_platform_api 调用
触发后:排队等待或降级处理

5.4 熔断

熔断(Circuit Breaker)是比限流更进一步的保护机制。当某个操作的失败率超过阈值时,系统主动"断开"这个操作,快速返回失败而不是继续重试浪费资源[13]

在 Agent Skill 场景中,熔断有两个典型场景:

Skill 调用级熔断

场景:同一会话中,Agent 反复调用 pipeline-diagnosis,
     每次都失败(CI/CD 平台持续故障),但 Agent 不断重试

熔断条件:同一 pipeline 连续自动修复尝试超过 3 次
熔断后行为:
  1. 停止自动修复尝试
  2. 生成当前已有的诊断报告(即使不完整)
  3. 通知 on-call 工程师介入

工具级熔断

场景:cicd_platform_api 在过去 5 分钟内的失败率超过 50%

熔断后行为:
  所有调用 cicd_platform_api 的 Skill 立即走降级路径
  (使用本地缓存或提示用户提供数据)
  而不是继续尝试调用一个大概率会失败的 API

熔断器的经典状态机由 Martin Fowler 在 2014 年总结[13]

     正常请求成功率高
  ┌──────────────────┐
  │                  │
  │    关闭状态       │◀──────────┐
  │  (正常放行)      │           │
  │                  │    探测成功 │
  └────────┬─────────┘           │
           │                     │
     失败率超过阈值           ┌───┴──────────┐
           │                 │              │
           ▼                 │   半开状态     │
  ┌──────────────────┐      │ (允许少量请求 │
  │                  │      │   探测服务恢复)│
  │    打开状态       │──────▶│              │
  │  (全部快速失败)  │ 超时  └──────────────┘
  │                  │  后尝试探测
  └──────────────────┘

在 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 的输出作为输入。

用户:"CI 挂了,帮我看看是不是迁移引起的"
  │
  ▼
Agent 调用 pipeline-diagnosis Skill
  → 输出:failure_type = "dependency", root_cause = "logback 冲突"
  │
  ▼
Agent 判断:需要检查最近的数据库迁移
  → 调用 database-migration Skill(输入:冲突的包名)
  → 输出:最近迁移未修改 logback 相关依赖
  │
  ▼
Agent 综合两个 Skill 的结果,回复用户:
  "诊断结果是 logback 依赖冲突,但与最近的数据库迁移无关。"

并行编排:多个 Skill 之间没有数据依赖,可以同时执行。

用户:"CI 挂了,帮我看看原因,顺便查一下这个服务最近的告警记录"
  │
  ▼
Agent 同时调用:
  ├─ pipeline-diagnosis Skill(诊断 CI 故障)
  └─ alert-history Skill(查询告警记录)
  │
  ▼
两个结果返回后,Agent 综合回复用户

并行执行的优势是减少了总响应时间(两个 Skill 的耗时取较长的那个,而不是两者之和)。但只有当两个 Skill 之间没有数据依赖时才能并行[14]

6.3 上下文传递

多 Skill 协作时,一个核心问题是:Skill 之间怎么共享信息?

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]

对话开始        上下文大小:~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 从被触发到执行完成,经历以下生命周期:

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  触发(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 调用都应该记录结构化日志。日志中包含的关键字段:

{
  "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]

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]

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]

用户请求 [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 布局:

┌──────────────────────────────────────────────────────────────┐
│  实时概览                                                      │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐        │
│  │ 调用量/分 │ │ 任务完成率│ │ 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 / TempoOpenTelemetry 作为采集标准,Jaeger/Tempo 作为后端存储
可视化 DashboardGrafana开源,支持日志、指标、链路追踪三大数据源统一展示

以上工具选型基于 CNCF(Cloud Native Computing Foundation)的生态推荐[15]。具体选择取决于团队的技术栈和运维能力。

8.4 告警规则

监控指标需要配合告警规则才能在问题发生时及时通知[15]

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 工程化指南(四):治理与演进 — 从上线到退役的全生命周期》


参考来源

  1. SKILL.md 的加载模式 — 在多个 Agent 开发框架中,Skill 的匹配阶段只使用头部元数据(name + description),正文在匹配成功后才被加载。这是"最小信息匹配、按需加载完整定义"的通用模式。
  2. 服务注册与发现 — 动态注册与发现是微服务架构中的成熟模式。Consul、etcd、ZooKeeper 等工具提供了服务注册中心的基础设施。参见 HashiCorp Consul Documentation
  3. 向量检索(Vector Retrieval) — 将文本转换为高维向量并通过余弦相似度等度量进行近邻检索,是现代信息检索和 RAG 系统的基础技术。Pinecone、Weaviate、Milvus 等向量数据库提供了工程化实现。参见 Pinecone: What is Vector Search?
  4. Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks — Reimers, N. & Gurevych, I. (2019). 提出了高效的句子向量化方法,广泛用于语义相似度计算和语义检索。参见 arXiv:1908.10084
  5. 混合检索(Hybrid Search) — 将传统的关键词检索(如 BM25)与语义向量检索结合,是现代搜索系统的推荐实践。Elasticsearch 8.x 引入了混合检索支持。参见 Elasticsearch: Reciprocal Rank Fusion
  6. LLM 上下文窗口管理 — 上下文窗口(Context Window)是 LLM 能处理的 Token 上限。GPT-4 Turbo 为 128K Token,Claude 3 为 200K Token。超出窗口的信息会被截断,因此需要管理上下文中的信息优先级和压缩策略。参见 OpenAI Docs: Token LimitsAnthropic Docs: Context Windows
  7. HTTP Caching (RFC 7234) — 缓存失效策略的标准参考。参见 RFC 7234: HTTP/1.1 Caching
  8. 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
  9. NIST RBAC Standard (INCITS 359-2004) — 美国国家标准与技术研究院发布的基于角色的访问控制标准。参见 NIST: Role Based Access Control
  10. LangGraph: Human-in-the-loop — LangChain 的 LangGraph 框架提供了断点(breakpoint)和人工审批(human-in-the-loop)机制,允许在 Agent 执行流程中设置硬性暂停点。参见 LangGraph Docs: Human-in-the-loop
  11. OpenAI Assistants API: Required Action — OpenAI 的 Assistants API 在需要人工输入时返回 requires_action 状态,暂停执行直到人工提供输入。参见 OpenAI Docs: Assistants API
  12. Rate Limiting — API 限流是保护后端服务的基本手段。常见算法包括令牌桶(Token Bucket)、漏桶(Leaky Bucket)和滑动窗口(Sliding Window)。参见 AWS: Throttling API requests
  13. Martin Fowler: Circuit Breaker — 熔断器设计模式,用于防止级联故障。当失败率超过阈值时"熔断",快速失败而非继续重试。参见 martinfowler.com/bliki/Circu…
  14. Orchestration Patterns in Multi-Agent Systems — 多 Agent / 多 Skill 协作中的编排模式。串行编排、并行编排和基于图的编排(如 LangGraph 的 StateGraph)是主要的实现方式。参见 LangGraph Docs
  15. OpenTelemetry — CNCF 的可观测性框架,定义了分布式系统中的链路追踪(Tracing)、指标(Metrics)和日志(Logging)三大支柱。Agent Skill 的监控设计遵循同样的可观测性原则。参见 OpenTelemetry Documentation