
1. 从“skills”这个标题说起它到底指什么第一次看到“skills”这个标题很多人会以为是某个泛泛而谈的能力清单或者一份简历上的技能罗列。但结合热搜词里反复出现的 Google Cloud、Agent Skills、GKE、Genkit、codex skills、claude agent skills 这些词基本可以确定这里说的 skills 不是人类的能力项而是给 AI Agent 使用的一套可插拔能力模块。简单说它让一个原本只会聊天的模型能够真正去调用工具、执行任务、访问外部系统把“会说”变成“会做”。我最早接触这个概念是在给一个内部知识库做自动化问答的时候。当时模型能回答“怎么部署一个服务”但没法真的帮你把服务部署上去。后来引入 Agent Skills 的思路把“查文档”“调接口”“跑命令”“校验结果”拆成一个个独立技能模型就能按需组合这些技能完成端到端流程。这个转变非常关键因为它把大模型从“信息检索器”升级成了“任务执行器”。这篇文章适合三类人看一是正在做 AI Agent 应用、想搞清楚技能体系怎么设计的开发者二是用 Google Cloud 生态、想把 GKE、Genkit 这些组件串起来做自动化的人三是刚听说 skills 这个词、想弄明白它和普通函数调用、插件有什么区别的入门者。我会从整体设计思路讲到具体实现再到踩过的坑尽量把每个选择背后的理由说清楚让你看完能直接照着搭一套自己的技能体系。需要先说明一点skills 目前没有唯一标准不同平台、不同框架对它的定义有差异。下面讲的内容是基于我在实际项目中反复验证过的一套通用做法结合 Google Cloud 和 Agent 生态的常见实践来展开不是某个官方规范的逐条翻译。2. 整体设计思路为什么要把能力拆成 skills2.1 从“一个大模型包打天下”到“技能组合”早期做 AI 应用最常见的做法是把所有逻辑塞进一个超长提示词里让模型自己判断该干什么。这个方案在 demo 阶段很爽一旦任务变复杂就崩。原因很简单提示词越长模型注意力越分散指令之间还会互相干扰。我试过一个 3000 字的提示词里面同时包含“查天气”“写邮件”“排日程”三件事结果模型经常把天气信息写进邮件里。Agent Skills 的核心思路是分而治之。每个 skill 只负责一件明确的事有独立的输入输出定义、独立的执行逻辑、独立的错误处理。模型不需要记住所有细节只需要知道“现在该调用哪个技能”。这就像公司里不会让一个人同时干财务、法务和运维而是分成不同岗位需要时按流程协作。这种拆分带来三个直接好处。第一可维护性大幅提升改一个技能不影响其他技能。第二可测试性变强每个技能可以单独写单元测试不用每次跑全流程。第三可复用性提高同一个“发送通知”技能可以被多个 Agent 在不同场景下调用。2.2 技能粒度怎么定太粗和太细都是坑拆分技能时最容易犯的错是粒度失控。我见过有人把“处理用户请求”做成一个技能这等于没拆也见过有人把“拼接字符串”做成一个技能细到没有复用价值。比较合理的粒度判断标准是这个技能是否对应一个完整的、有业务意义的动作。举个例子在做一个客服 Agent 时我最初把“查询订单”拆成“连接数据库”“执行 SQL”“格式化结果”三个技能结果模型每次都要连续调用三次中间任何一步出错整个流程就断。后来合并成一个“查询订单状态”技能内部自己处理连接、查询、格式化模型只需要传订单号、拿结果。调用次数从三次降到一次成功率明显上升。反过来如果两个动作经常需要独立使用就不要硬合并。比如“发送邮件”和“发送短信”虽然都是通知但渠道不同、参数不同、失败处理不同就应该分开。判断依据是它们是否会被不同场景单独调用。会就拆不会就合。2.3 技能与工具调用、插件的区别很多人会把 skills 和 function calling、plugin 混为一谈。它们确实有重叠但侧重点不同。Function calling 更偏向模型层面的能力模型输出一个结构化调用请求具体怎么执行由外部代码决定。Plugin 更偏向平台生态通常是第三方提供的、有固定接口的扩展。而 skills 更强调可组合性和上下文感知一个技能可以包含多个底层调用也可以根据上下文动态调整行为。用生活类比function calling 像是你告诉助手“去查一下这个号码”plugin 像是你装了一个第三方 App而 skill 像是你培养了一个“会处理客户投诉”的完整能力里面可能包含查记录、判断等级、生成回复、发送通知等一系列动作。skill 是更高层的抽象更贴近业务语义。在 Google Cloud 生态里Genkit 提供了定义工具和流程的能力GKE 提供了运行环境Agent Skills 则是把这些串起来的业务层封装。理解这个层次关系后面设计时就不会乱。3. 核心细节解析一个 skill 到底包含什么3.1 技能描述模型怎么知道该用哪个技能技能描述是模型选择技能的唯一依据写得好不好直接决定调用准确率。我踩过的最大坑是描述写得太技术化比如“调用 REST API 获取用户信息”模型根本不知道什么时候该用。后来改成“当用户询问自己的账户余额、订单记录、会员等级时使用”准确率立刻上来了。好的技能描述要包含四个要素触发场景、输入要求、输出内容、边界说明。触发场景告诉模型什么时候用输入要求说明需要哪些参数输出内容让模型知道能拿到什么边界说明防止模型在不该用的时候乱用。比如一个“查询物流”技能描述可以写成“当用户询问包裹位置、配送进度、预计到达时间时使用。需要提供订单号。返回当前物流节点和预计送达时间。不处理退货和换货请求。”注意描述里不要写“这个技能很强大”“可以处理各种情况”这类模糊表述模型对这类词不敏感反而会干扰判断。3.2 输入输出 schema结构化是稳定性的前提技能之间靠数据传递如果输入输出是自由文本上游稍微变一下格式下游就解析失败。所以每个技能都必须定义严格的 schema。我用得最多的是 JSON Schema字段名、类型、是否必填、取值范围都写清楚。举个实际例子一个“创建工单”技能的输入 schema 大概是这样{ type: object, properties: { title: { type: string, maxLength: 100 }, priority: { type: string, enum: [low, medium, high] }, description: { type: string, maxLength: 2000 }, assignee: { type: string } }, required: [title, priority] }这样模型在生成调用参数时会被约束在合法范围内。实测下来加了 schema 约束后参数错误率从 15% 左右降到 3% 以下。输出 schema 同样重要它让下游技能能稳定解析不用做各种兼容处理。3.3 执行逻辑同步、异步还是流式技能的执行方式要根据实际场景选。查询类技能通常同步执行几秒内返回结果。耗时长的任务比如生成报告、批量处理就要用异步先返回任务 ID后续再查状态。流式适合需要逐步输出内容的场景比如实时翻译、逐字生成。我在 GKE 上部署技能服务时同步技能用普通 HTTP 接口异步技能用任务队列加状态查询接口。这里有个经验不要把所有技能都做成异步因为异步会引入状态管理复杂度模型也要多轮交互才能拿到结果。只有确实超过 10 秒的任务才值得异步化。3.4 错误处理失败时模型该怎么办技能执行失败是常态网络抖动、参数错误、下游服务不可用都会发生。关键不是避免失败而是让失败可恢复。我的做法是给每个技能定义清晰的错误码和错误信息模型根据错误类型决定重试、换技能还是告知用户。比如“查询库存”技能返回RATE_LIMITED模型可以等待后重试“参数缺失”则应该向用户追问“服务不可用”可以尝试备用数据源。如果错误信息只是一句“出错了”模型完全不知道下一步该干什么整个流程就卡死。实操心得错误信息里带上“建议动作”字段比如{code: RATE_LIMITED, suggestion: wait_and_retry}模型处理起来会顺畅很多。4. 实操过程从零搭一套可用的技能体系4.1 环境准备与依赖安装先说明下面这套流程是我在 Google Cloud 环境下验证过的其他环境思路类似具体命令会有差异。基础依赖包括 Node.js 18 以上、Google Cloud SDK、以及 Genkit 相关包。安装命令大致如下# 安装 Google Cloud SDK 后初始化 gcloud init gcloud config set project YOUR_PROJECT_ID # 安装 Genkit CLI npm install -g genkit-cli # 在项目里安装依赖 npm install genkit genkit-ai/google-cloudGKE 部分如果只是本地开发可以先用本地模拟不必一开始就上集群。我建议先在本地把技能跑通再考虑部署到 GKE。因为本地调试快改一行代码几秒就能验证上集群后每次部署都要等镜像构建和滚动更新效率差很多。4.2 定义第一个技能从最简单的开始不要一上来就做复杂技能。我通常从“获取当前时间”或“查询天气”这种简单技能开始目的是把整条链路跑通定义 schema、写执行逻辑、注册到技能列表、让模型调用、拿到结果。这条链路通了后面加复杂技能只是替换内部逻辑。用 Genkit 定义一个技能大概是这样import { genkit, z } from genkit; const ai genkit({ plugins: [] }); export const getWeather ai.defineTool( { name: getWeather, description: 当用户询问某地天气时使用需要提供城市名, inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ temperature: z.number(), condition: z.string() }) }, async (input) { // 实际调用天气接口 const data await fetchWeather(input.city); return { temperature: data.temp, condition: data.condition }; } );这里description就是给模型看的inputSchema和outputSchema保证结构化。执行函数里可以做任何事调接口、查数据库、跑脚本都行。4.3 技能注册与模型调用配置定义好技能后要注册到模型可用的技能列表里。Genkit 里通过tools参数传入const response await ai.generate({ model: googleai/gemini-pro, prompt: 北京今天天气怎么样, tools: [getWeather] });模型会根据 prompt 和技能描述决定是否调用getWeather并生成参数{ city: 北京 }。执行结果返回后模型再组织成自然语言回复。整个过程模型只负责决策和表达具体执行由技能完成。这里有个关键配置最大调用轮数。默认模型可能连续调用多个技能如果不限制遇到循环调用会一直跑下去。我一般设成 5 到 8 轮超过就强制结束并返回当前结果。4.4 部署到 GKE容器化与扩缩容本地跑通后部署到 GKE 主要是为了稳定性和并发能力。步骤是写 Dockerfile、构建镜像、推送到镜像仓库、创建 Deployment 和 Service。Dockerfile 很标准FROM node:18-slim WORKDIR /app COPY package*.json ./ RUN npm install --production COPY . . EXPOSE 8080 CMD [node, server.js]GKE 的扩缩容策略我一般设最小 1 个副本、最大 10 个副本CPU 超过 70% 触发扩容。技能服务通常是无状态的扩缩容很安全。但要注意如果技能内部有缓存或会话状态就要考虑用外部存储否则扩容后请求打到不同副本会出问题。注意GKE 上的服务要配置健康检查接口否则滚动更新时可能把还没准备好的副本接入流量导致请求失败。4.5 技能测试怎么验证它真的能用技能测试分三层。第一层是单元测试直接调执行函数验证输入输出符合预期。第二层是集成测试把技能注册到模型用固定 prompt 验证模型能正确选择和调用。第三层是端到端测试模拟真实用户对话看整个流程是否顺畅。我特别推荐第二层测试因为它能发现描述写得不清楚、schema 设计不合理这类问题。具体做法是准备一批测试用例每条包含用户输入和期望调用的技能名跑完后统计准确率。低于 90% 就要回去改描述。5. 常见问题与排查技巧实录5.1 模型不调用技能或调错技能这是最常见的问题。排查顺序是先看技能描述是否清晰再看技能数量是否过多。技能超过 20 个后模型选择准确率会明显下降。解决办法是分组或者加一层路由技能先做粗分类。另一个原因是 prompt 里没有明确意图。比如用户说“帮我看看”模型不知道看什么。这时候可以在系统提示里加一句“如果用户意图不明确先追问澄清”。5.2 参数传递错误参数错误通常来自 schema 定义不严。比如字段类型写成 string 但实际传了数字或者必填字段没标 required。我习惯在 schema 里加description告诉模型这个字段填什么比如z.string().describe(城市名称如北京、上海)。加了字段描述后参数准确率提升很明显。5.3 技能执行超时超时要么是下游服务慢要么是技能内部逻辑太重。先加超时时间比如 5 秒超过就返回超时错误让模型决定重试。如果下游确实慢考虑异步化。我遇到过一个查询接口平均响应 8 秒同步调用经常超时改成异步后体验好很多。5.4 多技能协作时的状态丢失多个技能连续调用时中间结果需要传递。如果每个技能独立执行不共享上下文就会出现“查了订单但创建工单时不知道订单号”的情况。解决办法是在 Agent 层面维护一个会话状态把关键信息存进去后续技能从状态里读。下面这张表整理了我遇到的高频问题和对应处理方式问题现象可能原因排查动作解决方式模型不调用技能描述不清、技能过多检查描述、统计技能数改描述、分组路由参数错误schema 不严检查字段类型和必填加 schema 约束和字段描述执行超时下游慢、逻辑重看日志定位耗时点加超时、异步化状态丢失无共享上下文检查会话状态引入状态存储循环调用无轮数限制看调用链设最大轮数5.5 独家避坑技巧第一个技巧给技能加版本号。技能描述或 schema 改动后老版本可能还在被缓存导致行为不一致。加版本号后可以明确知道当前用的是哪版。第二个技巧记录每次调用的输入输出。出问题时能快速复现不用靠猜。日志里带上 trace ID方便串联整个调用链。第三个技巧不要在生产环境直接改技能描述。描述改动会直接影响模型行为可能让原本正常的流程出问题。先在测试环境验证再灰度发布。6. 技能体系的扩展与长期维护6.1 技能复用与组合技能多了以后会发现很多技能有共同部分。比如多个技能都需要“获取用户信息”这时候可以抽出一个基础技能其他技能内部调用它。但要注意技能之间的依赖不要太深否则改一个底层技能会影响一大片。我一般控制依赖层级不超过三层。组合技能是另一个方向。把几个固定搭配的技能打包成一个复合技能模型调用一次就能完成多步操作。比如“处理退款”可能包含查订单、验资格、发起退款、通知用户四个步骤打包后模型不用逐个调用成功率和效率都更高。6.2 监控与迭代上线后要监控几个指标技能调用成功率、平均耗时、模型选择准确率、用户满意度。成功率下降通常意味着下游有问题耗时上升可能是数据量变大准确率下降往往是新技能加入后干扰了选择。我每周会看一次技能调用排行长期没人用的技能考虑下线调用频繁但成功率低的技能优先优化。技能体系不是一次搭好就不管而是持续迭代的过程。6.3 安全与权限控制技能能执行真实操作权限控制必须做好。我的做法是每个技能声明自己需要的权限运行时校验调用方是否有对应权限。比如“删除数据”技能只有管理员角色能调用“查询数据”技能普通用户也能用。另外技能输入要做校验防止注入类问题。特别是拼接命令或 SQL 的技能一定要用参数化方式不要直接拼字符串。这个坑我踩过一个查询技能因为直接拼 SQL被构造了恶意输入虽然没造成实际损失但吓出一身冷汗。6.4 技能市场与共享团队大了以后技能可以共享。我们内部建了一个技能仓库每个技能有文档、示例、测试用例。新人要用直接引用不用重复开发。共享时要注意接口稳定性一旦发布改动要向后兼容否则依赖它的 Agent 都会受影响。如果技能要对外开放还要考虑限流和计费。限流防止被滥用计费让资源使用可衡量。这两块我建议一开始就设计进去后期补会很麻烦。7. 我个人的一些实操体会做技能体系这段时间最大的感受是技能设计本质上是业务设计。技术实现反而不难难的是想清楚业务上到底需要哪些能力、它们之间怎么协作、边界在哪里。我见过技术很强但技能拆得乱七八糟的项目也见过技术一般但技能设计清晰、跑得很稳的系统。另一个体会是不要追求一步到位。先做三五个核心技能跑通闭环再逐步扩展。一开始就设计几十个技能大概率会返工。技能描述和 schema 也要在实际调用中不断调整纸上推演和真实效果差距很大。最后分享一个小技巧给每个技能写一句“人话版”说明就是假设你跟一个不懂技术的人解释这个技能干什么。这句话往往比技术描述更能帮你判断技能粒度是否合理。如果一句话说不清楚说明这个技能可能太复杂需要再拆。这套东西后续还可以往几个方向扩展一是技能自动发现让 Agent 根据任务动态查找可用技能二是技能效果评估用数据驱动优化三是跨团队技能共享形成内部生态。这些我还在摸索有进展再分享。