
1. 从“skills”这个标题说起它到底指什么第一次看到“skills”这个标题很多人会以为是泛泛而谈的能力清单或者某个招聘网站的技能标签页。但结合热搜词里反复出现的 Agent Skills、Google Cloud、GKE、Genkit、codex skills、claude agent skills 这些词方向其实很明确——这里说的 skills是围绕 AI Agent 构建的一套可复用能力模块也就是让智能体在特定场景下“会做某件事”的最小封装单元。我把它理解成给 Agent 装的“技能插件”。一个 Agent 本身只有推理和调度能力它要真正干活比如查数据库、调云服务、生成分镜脚本、跑测试用例、写论文提纲就得靠一个个 skills 来落地。这和早年我们给编辑器装插件、给浏览器装扩展是一个思路只不过这次装的对象变成了 AI Agent运行环境从本地 IDE 延伸到了 Google Cloud、GKE 集群、Genkit 工作流里。这个内容适合谁看如果你正在用 Claude、Codex 这类工具做自动化任务或者你在 Google Cloud 上跑 Genkit 项目想把自己的业务逻辑封装成 Agent 能调用的能力那这篇就是写给你的。哪怕你只是刚听说“agent skills 测试”这个词想搞清楚它和普通函数调用有什么区别也能从下面找到答案。我会从设计思路、核心细节、实操过程到问题排查把 skills 这套东西拆开讲透尽量让你看完就能动手做一个自己的 skill。2. 整体设计与思路拆解为什么是 skills 而不是一个大函数2.1 从“单体提示词”到“技能模块”的演进逻辑早期做 Agent大家习惯把所有的指令、工具描述、输出格式全塞进一个巨大的系统提示词里。我试过那种做法一个提示词写到三千字里面混杂着“你可以查天气、可以算汇率、可以写周报、可以生成分镜”结果模型经常串台查天气的时候给你返回一段周报格式。问题出在哪出在职责没有边界。skills 的核心设计思路就是职责分离。每个 skill 只负责一类任务有自己的名称、描述、输入参数、输出结构甚至有自己的依赖和权限范围。Agent 在运行时根据用户意图去“找 skills”找到匹配的再加载执行。这就像公司里不会让一个人同时干财务、法务和保洁而是分岗位需要谁就叫谁。热搜词里有个“find skills”说的就是这个匹配过程。Agent 怎么知道该用哪个 skill靠的是 skill 的元数据描述和向量检索。你把每个 skill 的功能描述做成可检索的索引用户提问时先做意图匹配命中哪个就加载哪个。这样做的好处是提示词长度可控每个 skill 的上下文干净模型不容易被无关信息干扰。2.2 为什么选 Google Cloud 和 Genkit 作为落地载体热搜词里 Google Cloud、GKE、Genkit 同时出现不是偶然。Genkit 是 Google 推出的 AI 应用开发框架它天然支持把一个个功能封装成 flow 和 tool而 tool 的概念和 skill 非常接近。GKE 则是跑这些 Agent 服务的容器编排平台。选这套组合的理由很实际Genkit 的 tool 定义和 skill 结构几乎一一对应你写一个 tool 就相当于写了一个 skill省去自己造轮子的时间。GKE 提供弹性伸缩Agent 调用 skill 的频率波动大用 K8s 管理能按需扩缩容不会因为某个 skill 被高频调用把整个服务拖垮。Google Cloud 的 IAM 和 Secret Manager能精细控制每个 skill 能访问哪些资源避免一个查天气的 skill 意外拿到数据库写权限。当然这不是唯一方案。你也可以在本地用 Node.js 或 Python 自己搭一套 skill 注册中心但一旦要上生产、要多人协作、要审计调用日志云上的托管能力就体现出价值了。我个人的经验是原型阶段本地跑验证完就迁到 GKE中间用 Genkit 做一层抽象迁移成本很低。2.3 一个 skill 的边界应该划多大这是设计时最容易纠结的点。划得太细Agent 要调十个 skill 才能完成一件事调用链长、延迟高划得太粗又退化成大函数失去模块化的意义。我的判断标准是一个 skill 对应一个“原子业务动作”。什么叫原子业务动作比如“查询某支股票当前价格”是原子动作“生成一份包含股价走势和财报摘要的投资报告”就不是后者应该拆成查股价、查财报、生成报告三个 skill由 Agent 编排。热搜词里“分镜skills下载”提到的分镜生成如果 skill 里既做剧情分析又做镜头切分又做配图那就太胖了应该拆成剧情解析 skill、镜头切分 skill、配图生成 skill。注意skill 的粒度没有绝对标准但有一个实用检验方法——如果你能用一句话说清这个 skill 的输入和输出且这句话里没有“并且”“然后”这类连接词那粒度基本合适。3. 核心细节解析与实操要点一个 skill 由哪些部分组成3.1 元数据层让 Agent 能找到你每个 skill 都必须有一份元数据这是 Agent 做“find skills”的依据。元数据通常包含以下字段字段作用示例name唯一标识调用时用stock_price_querydescription自然语言描述用于检索匹配查询指定股票代码的实时价格input_schema输入参数结构{ code: string, market: string }output_schema输出结构{ price: number, currency: string }tags分类标签辅助检索finance, realtimeversion版本号便于灰度1.0.0description 的写法很关键。我见过有人写“查询股票”太短检索时容易和“查询基金”混淆。好的 description 应该包含动作、对象、限定条件比如“根据股票代码查询当前市场实时交易价格支持 A 股和港股”。这样向量检索的区分度才够。3.2 执行层skill 内部到底怎么跑执行层是 skill 真正干活的地方。以 Genkit 为例一个 skill 本质上是一个定义了 input 和 output schema 的 flow内部可以调用外部 API、查数据库、跑计算逻辑。关键点在于错误处理要统一。我踩过的坑早期每个 skill 自己抛异常格式五花八门Agent 拿到错误信息后不知道怎么处理有时候直接把异常文本当结果返回给用户。后来统一成结构化错误{ success: false, error_code: UPSTREAM_TIMEOUT, error_message: 上游接口超时, retryable: true }Agent 看到 retryable 为 true 就知道可以重试看到 false 就换策略或告知用户。这个约定看起来小但在多 skill 编排时能省大量调试时间。3.3 权限与隔离别让一个 skill 捅娄子skill 能访问什么资源必须在定义时就锁死。在 GKE 上跑的时候我习惯给每个 skill 单独配 Service Account只授予它需要的最小权限。比如一个只读 BigQuery 的 skill就只给 bigquery.dataViewer绝不给 dataEditor。热搜词里“自动挖洞skills”这种涉及安全测试的场景权限隔离更是生死线。一个用于扫描漏洞的 skill如果跑在拥有集群管理员权限的容器里一旦被恶意输入诱导后果不堪设想。我的做法是所有 skill 默认无网络出站权限需要访问外部接口的单独开白名单。这在 GKE 的 NetworkPolicy 里配置虽然麻烦但安全底线不能破。提示本地开发时可以用环境变量模拟权限但上线前一定要在真实 IAM 环境下做一次权限回归测试确认每个 skill 只能干它该干的事。4. 实操过程与核心环节实现从零做一个可用的 skill4.1 环境准备与依赖安装假设你已经在 Google Cloud 上有了项目并且本地装了 Node.js 20 以上版本。第一步是初始化 Genkit 项目npm init -y npm install genkit genkit-ai/google-cloud然后配置 Google Cloud 凭证。我一般用应用默认凭证本地开发时执行gcloud auth application-default login这样 Genkit 就能自动拿到调用 Vertex AI 和 Cloud API 的权限。如果你在 GKE 里跑则通过 Workload Identity 绑定 K8s Service Account 和 GCP Service Account不需要在容器里放密钥文件。4.2 定义第一个 skill以“查询股票价格”为例先定义 schema我用 Zod 来做校验Genkit 原生支持import { z } from zod; const StockPriceInput z.object({ code: z.string().describe(股票代码如 600519), market: z.enum([SH, SZ, HK]).describe(市场标识) }); const StockPriceOutput z.object({ code: z.string(), price: z.number(), currency: z.string(), timestamp: z.string() });然后写执行逻辑。这里我模拟一个上游接口调用实际项目中替换成真实数据源import { defineFlow } from genkit-ai/flow; export const stockPriceQuery defineFlow( { name: stock_price_query, inputSchema: StockPriceInput, outputSchema: StockPriceOutput }, async (input) { const resp await fetch( https://api.example.com/quote?code${input.code}market${input.market} ); if (!resp.ok) { throw new Error(UPSTREAM_ERROR: ${resp.status}); } const data await resp.json(); return { code: input.code, price: data.price, currency: data.currency, timestamp: new Date().toISOString() }; } );这段代码里defineFlow 的 name 就是 skill 的唯一标识inputSchema 和 outputSchema 就是元数据。Genkit 会自动根据这些信息生成可检索的描述Agent 通过它来匹配意图。4.3 注册与检索让 Agent 找到这个 skill定义完 skill 后要把它注册到一个 skill 注册中心。在 Genkit 里你可以把所有 flow 导出到一个数组然后启动一个检索服务import { genkit } from genkit; import { googleCloud } from genkit-ai/google-cloud; const ai genkit({ plugins: [googleCloud()], flows: [stockPriceQuery] });启动后Genkit 会暴露一个本地端点Agent 可以通过它列出所有可用 skill 并做语义检索。如果你要自己实现检索可以用 Vertex AI 的 text embedding 模型把每个 skill 的 description 转成向量存进 Vector Search查询时做相似度匹配。我实测下来description 写得好不好直接影响检索准确率。同样的“查股票”意图description 里带“实时价格”的 skill 比只写“股票查询”的 skill 命中率高出一大截。4.4 在 GKE 上部署与扩缩容配置本地跑通后打包成容器镜像推到 Artifact Registrydocker build -t gcr.io/your-project/skill-server:v1 . docker push gcr.io/your-project/skill-server:v1然后写一个简单的 Deployment 和 ServiceapiVersion: apps/v1 kind: Deployment metadata: name: skill-server spec: replicas: 2 selector: matchLabels: app: skill-server template: metadata: labels: app: skill-server spec: serviceAccountName: skill-server-sa containers: - name: server image: gcr.io/your-project/skill-server:v1 ports: - containerPort: 3400 resources: requests: memory: 512Mi cpu: 250m limits: memory: 1Gi cpu: 500m副本数我一般从 2 开始配合 Horizontal Pod Autoscaler 根据 CPU 和自定义指标比如每秒 skill 调用次数自动扩。这里有个经验skill 调用往往是突发性的比如早上开盘时股票查询 skill 请求量暴涨HPA 的冷却时间要调短一点否则扩容跟不上。4.5 参数选择与计算过程超时和重试怎么定skill 调用上游接口时超时时间设多少合适我的计算逻辑是这样的先测上游接口的 P99 延迟假设是 800ms那么 skill 的超时至少设 2 秒留出网络抖动和重试空间。重试次数不超过 2 次因为 Agent 本身也有整体超时skill 层重试太多会把总时间拖爆。具体配置const TIMEOUT_MS 2000; const MAX_RETRY 2; async function callWithRetry(fn) { for (let i 0; i MAX_RETRY; i) { try { return await Promise.race([ fn(), new Promise((_, reject) setTimeout(() reject(new Error(TIMEOUT)), TIMEOUT_MS) ) ]); } catch (e) { if (i MAX_RETRY) throw e; } } }这个模式我在多个 skill 里复用稳定可靠。注意重试只对幂等操作做查价格可以重试下单类 skill 绝对不能自动重试。5. 常见问题与排查技巧实录5.1 skill 检索不准Agent 老是调错技能这是最高频的问题。表现是用户问“茅台多少钱”Agent 却调了“查询基金净值”的 skill。排查思路分三步第一检查 description 是否有歧义。如果两个 skill 的 description 都包含“查询”“价格”这类泛词向量距离就会很近。解决办法是在 description 里加入领域限定词比如“股票”和“基金”必须显式出现。第二检查 tags 是否合理。tags 是硬过滤条件如果股票 skill 打了 finance 标签基金 skill 也打了 finance那 tags 起不到区分作用。应该细分成 stock、fund 这样的子标签。第三看检索阈值。相似度低于某个值就不应该匹配宁可让 Agent 回复“我不确定该用哪个技能”也不要乱调。我一般把阈值设在 0.75 左右具体根据业务容忍度调。5.2 skill 执行超时但上游接口明明正常这种情况我遇到过好几次最后发现是 GKE 的 Pod 网络策略限制了出站流量导致 skill 容器根本连不上上游。排查步骤在 Pod 里执行curl测试上游连通性如果超时基本确定是网络策略问题。检查 NetworkPolicy 的 egress 规则确认目标域名或 IP 在白名单里。如果用了 Cloud NAT检查 NAT 网关的端口分配是否耗尽。还有一个隐蔽原因DNS 解析慢。GKE 默认的 kube-dns 在高并发下可能成为瓶颈我后来给 skill 容器配了 NodeLocal DNSCache解析延迟从 200ms 降到 5ms 以内。5.3 常见问题速查表现象可能原因解决方向Agent 找不到任何 skill注册中心未启动或网络不通检查注册服务端点与防火墙skill 返回格式错乱output schema 未严格校验在出口加 Zod parse 强制校验调用频率高时大量 503Pod 副本不足或 HPA 未触发调低 HPA 触发阈值增加最小副本权限报错 PERMISSION_DENIEDService Account 权限不足检查 IAM 绑定补最小权限日志里看不到 skill 调用记录未接入 Cloud Logging在 skill 入口加结构化日志5.4 独家避坑技巧skill 版本管理skill 一旦上线不要直接改原文件。我吃过亏改了一个 skill 的输出字段结果依赖它的上层 Agent 全部报错。正确做法是版本化新逻辑发新版本号旧版本保留一段时间Agent 配置里指定版本。等所有调用方都迁移完再下线旧版本。Genkit 的 flow 支持在 name 里带版本比如 stock_price_query_v2这样检索时也能区分。提示版本切换期间注册中心里会同时存在两个版本的 skill检索时可能命中旧的。我的做法是在 description 里标注“推荐使用 v2”并在检索排序时给高版本加权。6. 进阶玩法把 skills 组合成工作流单个 skill 只能干一件事真正有价值的是把多个 skill 串起来。比如“生成一份投资简报”这个任务Agent 可以先调 stock_price_query 拿价格再调 financial_report_query 拿财报最后调 report_generate 生成文档。这个编排过程可以由 Agent 自主完成也可以预先定义成 Genkit 的 flow。我倾向于混合模式高频固定路径预定义成 flow保证稳定性和速度长尾需求交给 Agent 自主编排保证灵活性。预定义 flow 里每个步骤的输入输出都经过校验出错概率低自主编排则依赖 skill 的 description 质量需要持续调优。热搜词里“codex写论文的skills”就是一个典型组合场景文献检索 skill、提纲生成 skill、段落扩写 skill、引用格式化 skill四个串起来就能辅助完成一篇论文初稿。每个 skill 单独测试通过后再组合起来跑端到端测试这样排查问题时能快速定位是哪个环节出了岔子。7. 我个人在实际操作中的几点体会做 skills 这套东西最大的感受是描述比实现更重要。一个 skill 的内部逻辑写得多漂亮如果 description 写得含糊Agent 就是找不到它、用不对它。我后来养成了一个习惯每写完一个 skill先不写实现先把 description 拿给同事看问他“你觉得这个技能是干什么的”如果他的理解和我的预期一致才继续往下写。另一个体会是日志要打全。skill 被调用时入参、出参、耗时、错误码一个都不能少。Agent 编排出问题时你唯一能依靠的就是这些日志。我一般用 Cloud Logging 的结构化日志每个 skill 调用打一条 JSON字段固定方便在 Logs Explorer 里过滤和聚合。最后分享一个小技巧给 skill 加一个 dry_run 模式。传入 dry_runtrue 时skill 只校验参数和权限不真正执行外部调用直接返回模拟结果。这在测试 Agent 编排逻辑时特别有用不用每次都打真实接口速度快、成本低也不会因为测试数据污染生产环境。等编排逻辑验证通过再去掉 dry_run 跑真实调用。这个模式我在多个项目里复用省下的调试时间相当可观。