
1. 从“skills”这个标题说起它到底指什么第一次看到“skills”这个标题很多人会以为是泛泛而谈的能力清单或者某个招聘网站的技能标签页。但结合热搜词里反复出现的 Agent Skills、Google Cloud、GKE、Genkit、codex skills、claude agent skills 这些词方向其实很明确——这里说的 skills是围绕 AI Agent 生态构建的一套“可插拔能力模块”体系。简单讲就是把一个智能体需要具备的某项具体能力封装成一个独立、可复用、可分发、可安装的单元让 Agent 在需要的时候按需加载。这件事为什么值得单独拿出来讲因为过去我们做一个 AI 应用习惯把所有逻辑写在一个大提示词里或者把工具调用硬编码在代码里。结果就是改一个功能要动全身复用基本靠复制粘贴团队协作时谁也不敢碰别人的那一段。skills 这套思路把“能力”从“主体”里剥出来变成像手机装 App 一样的东西——Agent 是操作系统skills 是应用。这个类比虽然被用烂了但它确实精准。我最早接触这个概念是在做 GKE 上的一个自动化运维助手时。当时的需求是让 Agent 能查日志、能看监控、能执行滚动重启、能生成变更报告。如果全塞进一个 prompt光是工具描述就上千行模型经常选错工具。后来拆成四个独立 skills每个 skill 只负责一件事附带自己的输入输出 schema 和少量示例准确率立刻上来了。这就是 skills 的核心价值用结构化的边界感换取可靠性和可维护性。这篇文章适合谁看如果你是正在做 Agent 应用的前端或后端开发者或者你在用 Genkit、Google Cloud 这类平台搭建智能工作流又或者你只是好奇 codex skills、claude agent skills 到底怎么玩那接下来的内容应该能帮你省下不少自己摸索的时间。我会从设计思路、核心细节、实操过程到踩坑排查完整走一遍。2. 整体设计思路为什么是“技能”而不是“功能”2.1 从单体提示词到技能模块的演进逻辑早期做 Agent大家习惯写一个巨大的 system prompt里面塞满角色设定、任务说明、工具列表、输出格式。这种做法的好处是简单直接坏处是随着能力增加提示词会膨胀到模型难以稳定遵循的程度。我实测过一个包含 12 个工具的 Agent当工具描述超过 3000 token 后模型选错工具的概率从 5% 飙升到 30% 以上。这不是模型不行而是信息过载导致注意力分散。skills 的思路是把每个能力独立成一个模块每个模块有自己的名称、描述、触发条件、输入参数、执行逻辑和输出格式。Agent 在运行时只加载当前任务相关的 skills而不是一次性把所有能力都塞进上下文。这就像你去餐厅点菜服务员不需要把整本菜单背下来只需要知道“凉菜找张师傅、热菜找李师傅、甜点找王师傅”。每个师傅就是一个 skill服务员是调度器。这种设计带来的直接好处有三个。第一是上下文精简模型每次只看到几个相关技能决策准确率明显提升。第二是独立迭代改一个 skill 不影响其他 skill测试也可以单独跑。第三是跨项目复用一个写好的“日志查询 skill”可以在运维助手、客服助手、数据分析助手里反复使用不用重写。2.2 技能与工具调用的本质区别很多人会把 skills 和传统的 function calling 混为一谈。两者确实有重叠但侧重点不同。function calling 关注的是“模型输出一个结构化调用请求外部代码执行并返回结果”它解决的是模型与外部系统交互的协议问题。skills 关注的是“一个完整能力单元如何被描述、发现、加载、执行和组合”它解决的是能力管理和复用的工程问题。打个比方function calling 像是 USB 接口标准规定了插头形状和信号协议skills 像是 USB 设备本身有鼠标、键盘、U 盘每个设备有自己的驱动和用途。你可以只有接口没有设备也可以有设备但接口不统一。skills 体系通常建立在 function calling 之上但增加了元数据管理、版本控制、依赖声明、权限边界这些工程层的东西。在实际项目中我通常会把一个 skill 设计成包含以下要素技能名称唯一标识、自然语言描述给模型看的、参数 schemaJSON Schema 格式、执行入口本地函数或远程 API、示例对话少样本提示、以及可选的依赖声明和权限要求。这些要素组合起来才构成一个完整的、可分发的 skill。2.3 为什么 Google Cloud 和 Genkit 会出现在这个语境里热搜词里出现 Google Cloud、GKE、Genkit说明 skills 这套东西已经在云原生和 AI 工程化平台上有落地实践。GKE 是 Kubernetes 托管服务天然适合跑需要弹性伸缩的 Agent 服务Genkit 是 Google 推出的 AI 应用开发框架提供了技能定义、流程编排、可观测性等能力。把 skills 部署在 GKE 上意味着你可以用 Kubernetes 的副本管理、滚动更新、服务发现来管理技能服务用 Genkit 来定义技能之间的调用关系和数据流。这个组合解决的是一个很现实的问题当你的 Agent 需要同时服务几百上千个用户每个用户可能触发不同的技能组合你不可能把所有技能都跑在一个进程里。你需要把技能拆成独立的微服务按需扩缩容独立部署。GKE 提供了这个基础设施Genkit 提供了开发框架skills 提供了能力封装标准。三者配合才能撑起生产级的 Agent 应用。3. 核心细节解析一个 skill 到底该怎么写3.1 技能描述给模型看的“说明书”怎么写才有效技能描述是模型决定是否调用这个技能的主要依据。写得太简单模型不知道什么时候该用写得太复杂又浪费上下文。我的经验是遵循“一句话定位 三个典型场景 一个反例”的结构。举个例子一个“查询 Kubernetes Pod 日志”的 skill描述可以这样写查询指定命名空间下某个 Pod 的最近日志。适用于排查应用报错、确认服务启动状态、检查定时任务输出。不适用于查询集群事件或节点系统日志。这句话告诉模型三件事能做什么、什么时候用、什么时候不用。特别是最后那个反例能有效减少误调用。我试过不加反例的版本模型经常拿这个 skill 去查节点日志结果返回空数据还以为是没日志。另外描述里要避免模糊词汇比如“处理数据”“管理资源”这种。模型对这类词的理解非常宽泛容易导致调用混乱。尽量用具体动词和具体对象比如“读取 CSV 文件并返回前 N 行”“向指定频道发送文本消息”。3.2 参数 schema 设计类型、必填与默认值的取舍参数 schema 用 JSON Schema 定义这是目前最通用的做法。设计时有几个关键决策点。第一必填参数尽量少。每增加一个必填参数模型调用失败的概率就上升一点。我通常只把最核心的定位参数设为必填比如“Pod 名称”“命名空间”其他如“日志行数”“时间范围”都设默认值。默认值的选择要符合大多数场景比如日志行数默认 100时间范围默认最近 1 小时。第二枚举类型要写全。如果某个参数只能是几个固定值之一一定要用 enum 列出来并在描述里说明每个值的含义。比如日志级别参数enum 设为 [debug, info, warn, error]描述里写“debug 最详细error 只显示错误”。这样模型不会自己发明一个 warning 出来。第三嵌套结构要谨慎。有些 skill 需要复杂参数比如一个筛选条件对象。嵌套太深会让模型生成错误的 JSON 结构。我一般最多两层超过两层就拆成多个扁平参数或者让 skill 接受一个 JSON 字符串然后内部解析。下面是一个实际的参数 schema 示例用于“创建 GKE 部署”的 skill{ type: object, properties: { cluster_name: { type: string, description: 目标 GKE 集群名称 }, namespace: { type: string, description: 部署所在的命名空间, default: default }, image: { type: string, description: 容器镜像地址包含 tag }, replicas: { type: integer, description: 副本数量, default: 1, minimum: 1, maximum: 10 }, env_vars: { type: object, description: 环境变量键值对, additionalProperties: { type: string } } }, required: [cluster_name, image] }这个 schema 里必填只有集群名和镜像地址其他都有默认值或可选。env_vars 用了 additionalProperties 允许任意键值对但类型限定为字符串避免模型生成嵌套对象导致解析失败。3.3 执行入口本地函数、远程 API 还是容器化服务执行入口的选择取决于你的部署架构。如果 skill 逻辑简单、依赖少直接写成本地函数最方便调用延迟最低。如果 skill 需要访问外部系统、有独立依赖、或者需要单独扩缩容那就做成远程 API 或容器化服务。我在 GKE 上的做法是每个 skill 打包成一个独立的容器镜像通过 Kubernetes Deployment 部署用 Service 暴露内部 gRPC 或 HTTP 接口。Genkit 的流程编排层负责根据模型输出的技能调用请求路由到对应的服务。这样做的好处是技能之间完全隔离一个技能的内存泄漏不会影响其他技能而且可以针对每个技能单独设置资源限制和副本数。代价是网络调用增加了延迟通常每个技能调用增加 5 到 20 毫秒。对于大多数 Agent 场景这个延迟可以接受。如果某个技能调用极其频繁且延迟敏感比如实时翻译那就把它做成 sidecar 容器或者本地库不走网络。3.4 示例对话少样本提示在技能定义中的作用示例对话是提升技能调用准确率的利器。人看说明书不如看例子模型也一样。每个 skill 附带两到三个“用户说什么 - 模型应该怎么调用”的示例能显著降低误调用和参数错误。示例的选择要有代表性。一个正面示例展示典型用法一个边界示例展示参数变化一个反例展示不该调用的情况。比如“发送邮件”这个 skill正面用户说“给张三发封邮件说会议改到三点”模型调用 send_email(to张三, subject会议时间变更, body会议改到三点)边界用户说“给项目组所有人发邮件通知明天放假”模型调用 send_email(toproject-group, subject放假通知, body明天放假)反例用户说“帮我看看收件箱”模型不应该调用 send_email而应该调用 read_inbox这些示例不需要太长每个两三行就够。但一定要真实不要编造不存在的参数值。我见过有人写示例时用了 schema 里没有的参数名结果模型照着学调用时一直报参数错误。4. 实操过程从零搭建一个可用的 skill 体系4.1 环境准备与基础依赖安装假设你已经在 Google Cloud 上有一个 GKE 集群并且本地装了 gcloud、kubectl、docker 这些基础工具。接下来需要准备 Genkit 的开发环境。Genkit 支持 Node.js 和 Go我以 Node.js 为例因为前端开发者更容易上手。首先初始化项目mkdir agent-skills-demo cd agent-skills-demo npm init -y npm install genkit genkit-ai/google-cloud然后配置 Genkit 的 Google Cloud 插件让它能自动上报日志和指标到 Cloud Logging 和 Cloud Monitoring。这一步不是必须的但对生产环境排查问题很有帮助。import { configureGenkit } from genkit; import { googleCloud } from genkit-ai/google-cloud; configureGenkit({ plugins: [googleCloud()], logLevel: debug, enableTracingAndMetrics: true, });环境变量方面确保 GOOGLE_APPLICATION_CREDENTIALS 指向一个有权限的服务账号密钥文件或者直接在 GKE 里用 Workload Identity 绑定。后者更安全推荐生产环境使用。4.2 定义第一个 skill查询 Pod 日志我们用 Genkit 的 defineTool 来定义一个 skill。虽然 Genkit 里叫 tool但概念上就是我们说的 skill。import { defineTool } from genkit; import { z } from zod; import { execSync } from child_process; export const queryPodLogs defineTool( { name: queryPodLogs, description: 查询指定命名空间下某个 Pod 的最近日志。适用于排查应用报错、确认服务启动状态。不适用于查询集群事件或节点系统日志。, inputSchema: z.object({ namespace: z.string().describe(Pod 所在的命名空间), podName: z.string().describe(Pod 名称), lines: z.number().int().min(1).max(1000).default(100).describe(返回的日志行数), container: z.string().optional().describe(容器名称多容器 Pod 需要指定), }), outputSchema: z.object({ logs: z.string(), podName: z.string(), namespace: z.string(), }), }, async (input) { const containerFlag input.container ? -c ${input.container} : ; const cmd kubectl logs ${input.podName} -n ${input.namespace} ${containerFlag} --tail${input.lines}; const logs execSync(cmd, { encoding: utf-8 }); return { logs, podName: input.podName, namespace: input.namespace, }; } );这个 skill 的核心逻辑就是拼一个 kubectl 命令然后执行。注意几个细节lines 参数设了默认值 100范围限制在 1 到 1000防止模型生成一个 100000 导致输出爆炸。container 参数是可选的因为单容器 Pod 不需要指定。4.3 技能注册与流程编排定义好 skill 之后需要把它注册到 Genkit 的流程里。流程编排决定了模型在什么阶段能看到哪些 skill。import { genkit } from genkit; import { queryPodLogs } from ./skills/queryPodLogs; const ai genkit({ plugins: [], }); export const opsAssistantFlow ai.defineFlow( { name: opsAssistantFlow, inputSchema: z.string(), outputSchema: z.string(), }, async (userInput) { const { text } await ai.generate({ model: googleai/gemini-2.0-flash, prompt: userInput, tools: [queryPodLogs], system: 你是一个运维助手帮助用户排查 Kubernetes 问题。只使用提供的工具不要编造数据。, }); return text; } );这个流程把 queryPodLogs 作为可用工具传给模型。模型根据用户输入决定是否调用。如果用户问“帮我看看 nginx Pod 最近有没有报错”模型会生成一个 queryPodLogs 调用Genkit 执行后把结果返回给模型模型再组织成自然语言回复。4.4 部署到 GKE容器化与 Service 暴露本地跑通之后下一步是部署到 GKE。先写 DockerfileFROM node:20-slim WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY . . EXPOSE 3400 CMD [node, dist/server.js]然后构建镜像并推送到 Artifact Registrygcloud builds submit --tag us-central1-docker.pkg.dev/my-project/agent-repo/ops-assistant:v1接着写 Kubernetes Deployment 和 ServiceapiVersion: apps/v1 kind: Deployment metadata: name: ops-assistant spec: replicas: 2 selector: matchLabels: app: ops-assistant template: metadata: labels: app: ops-assistant spec: containers: - name: ops-assistant image: us-central1-docker.pkg.dev/my-project/agent-repo/ops-assistant:v1 ports: - containerPort: 3400 resources: requests: memory: 256Mi cpu: 250m limits: memory: 512Mi cpu: 500m --- apiVersion: v1 kind: Service metadata: name: ops-assistant-svc spec: selector: app: ops-assistant ports: - port: 80 targetPort: 3400 type: ClusterIP应用这些配置kubectl apply -f k8s/deployment.yaml kubectl apply -f k8s/service.yaml部署完成后用 kubectl port-forward 本地验证一下kubectl port-forward svc/ops-assistant-svc 8080:80 curl -X POST http://localhost:8080/flow/opsAssistantFlow -d {input: 查看 default 命名空间下 nginx Pod 的日志}如果返回了日志内容说明整个链路通了。4.5 技能版本管理与灰度发布生产环境里skill 的更新不能一刀切。我通常用 Kubernetes 的滚动更新加上标签选择器来做灰度。比如新版本先部署一个副本通过 Service 的权重路由把 10% 流量导过去观察错误率和延迟指标没问题再逐步扩大。具体做法是给 Deployment 加一个 version 标签Service 的 selector 匹配多个版本然后用 Istio 或者 GKE 的 Traffic Director 做流量切分。如果不想引入服务网格也可以用两个 Deployment 加一个 Ingress 的权重注解来实现简单灰度。版本管理还有一个重要方面是 skill 的 schema 兼容性。如果新版本改了参数名或删了参数老版本的调用方会失败。我的做法是参数只增不删废弃参数保留但标记 deprecated给调用方一个过渡期。5. 常见问题与排查技巧实录5.1 模型不调用技能或调用错误技能这是最常见的问题。排查思路分三步。第一步检查技能描述是否清晰。把描述单独拿出来读一遍问自己如果我是模型看到这句话知道什么时候该用吗如果描述里有“处理”“管理”“操作”这种模糊词换成具体动词。第二步检查技能数量是否过多。如果一次传给模型的技能超过 10 个考虑分组或分层。比如先让模型选择一个技能类别再在类别内选择具体技能。Genkit 支持这种两阶段路由。第三步检查示例对话是否缺失。加上两三个典型示例通常能解决大部分误调用问题。下面是一个速查表现象可能原因解决方法完全不调用描述太模糊或技能未注册重写描述确认 tools 数组包含该技能调用错误技能多个技能描述重叠明确各技能边界加反例参数缺失必填参数太多或描述不清减少必填项参数描述加示例参数类型错误schema 类型与模型输出不匹配用 enum 限制取值范围加类型说明5.2 技能执行超时或返回异常技能执行超时通常有两个原因外部系统响应慢或者技能内部逻辑有阻塞。对于外部 API 调用设置合理的超时时间比如 5 秒超时后返回一个明确的错误信息而不是一直等。对于内部逻辑检查是否有同步阻塞操作比如大文件读取、复杂计算考虑改成异步或拆分成多个小技能。返回异常方面最常见的是输出格式不符合 schema。比如 schema 要求返回 JSON 对象但技能返回了一个字符串。解决办法是在技能执行入口做一层校验不符合 schema 的直接抛错让编排层捕获并重试或降级。我踩过的一个坑是技能返回了 undefined导致模型收到空结果后开始编造数据。后来在技能包装层加了一个检查如果返回值为空就返回一个明确的“未查询到数据”消息而不是让 undefined 溜过去。5.3 技能之间的依赖与冲突处理当多个技能需要共享状态或按顺序执行时依赖管理就变得重要。比如“创建部署”技能依赖“检查集群状态”技能先执行。Genkit 的流程编排支持这种顺序调用但需要你在流程里显式定义。冲突方面主要是资源竞争。比如两个技能同时修改同一个 ConfigMap可能导致数据覆盖。解决办法是给技能加锁或者把相关操作合并成一个原子技能。在 GKE 上还可以用 Kubernetes 的 ResourceQuota 和 LimitRange 来限制每个技能的资源使用防止一个技能耗尽集群资源。5.4 安全边界技能权限最小化原则每个技能应该只拥有完成其任务所需的最小权限。比如“查询日志”技能只需要 Pod 的 logs 读取权限不需要创建或删除 Pod 的权限。在 GKE 上通过 Kubernetes RBAC 给每个技能的服务账号绑定最小权限角色。另外技能参数要做输入校验防止注入攻击。比如拼接 kubectl 命令时如果 podName 参数包含分号或反引号可能执行意外命令。解决办法是用参数化调用而不是字符串拼接或者对输入做严格的白名单校验。注意永远不要信任模型生成的参数值。模型可能因为提示注入或自身幻觉生成恶意参数。所有技能执行入口都必须做输入校验和权限检查。5.5 性能优化减少技能调用延迟的实用技巧技能调用延迟主要来自三个方面模型推理时间、网络传输时间、技能执行时间。模型推理时间可以通过选择更小的模型或减少上下文来优化。网络传输时间可以通过把技能部署在离模型服务更近的区域来优化。技能执行时间可以通过缓存、并行化、异步化来优化。我常用的一个技巧是给技能加结果缓存。比如“查询集群状态”这种读操作结果在 30 秒内可以复用。用内存缓存或 Redis 缓存都行关键是设置合理的 TTL避免返回过期数据。另一个技巧是批量调用。如果模型需要连续调用多个技能可以在编排层把无依赖的技能并行执行而不是串行等待。Genkit 的流程编排支持并行分支能显著降低总延迟。6. 技能生态的扩展与个人实践体会6.1 从单机技能到技能市场当技能数量积累到几十个之后自然会产生分发和发现的需求。这就是技能市场或技能仓库的由来。你可以把技能打包成 npm 包或容器镜像发布到内部仓库其他人通过命令行工具安装到自己的 Agent 项目里。技能市场的关键要素包括技能元数据索引、版本管理、依赖解析、权限声明、评分和评论。目前这个领域还在早期没有统一标准但趋势是朝着“技能即服务”的方向走。你可以参考 npm 或 Docker Hub 的设计思路但要注意技能的特殊性——它不仅是代码还包含给模型看的描述和示例这些也需要版本化。6.2 技能组合与工作流自动化单个技能解决单点问题技能组合解决流程问题。比如“故障排查”工作流可能包含查询日志技能 - 分析错误模式技能 - 查询相关部署技能 - 生成报告技能。这些技能按顺序执行前一个的输出作为后一个的输入。Genkit 的流程编排支持这种组合你可以把多个技能串成一个 flow然后把这个 flow 本身也注册成一个技能供更上层的流程调用。这种嵌套组合能构建出非常复杂的自动化工作流同时保持每个技能的可测试性和可复用性。6.3 我踩过的三个坑和对应解法第一个坑是技能描述写得太长。我一开始觉得描述越详细越好结果一个技能描述写了 500 字模型反而抓不住重点。后来改成“一句话定位 三个场景 一个反例”控制在 100 字以内准确率反而提升了。第二个坑是忽略技能的幂等性。有些技能比如“创建资源”如果模型因为超时重试调用了两次就会创建两个资源。解决办法是给技能加幂等键或者把创建操作改成“存在则更新”的 upsert 语义。第三个坑是没有监控技能调用指标。上线后不知道哪个技能调用最频繁、哪个技能错误率最高。后来在 Genkit 里开启了 tracing把每个技能的调用次数、延迟、错误率上报到 Cloud Monitoring才发现了几个隐藏的性能瓶颈。6.4 后续可以怎么扩展这套体系如果你已经跑通了基本的技能定义和调用下一步可以尝试几个方向。一是技能自动生成用模型根据 API 文档自动生成技能描述和 schema减少手工编写。二是技能效果评估建立一套测试集自动评估每个技能的调用准确率和执行成功率。三是跨平台技能移植把同一套技能定义适配到不同的 Agent 框架比如从 Genkit 迁移到其他支持技能概念的平台。我个人在实际操作中的体会是skills 这套东西的价值不在于技术有多复杂而在于它强迫你把能力边界想清楚。一个技能如果不能用一句话说清楚它是干什么的那它大概率设计得有问题。这种约束反而提升了整个系统的可维护性。最后再分享一个小技巧每次新增技能之前先问自己“这个技能能不能拆成两个更小的”通常答案是可以的。