拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent Skills实战:基于Google Cloud、GKE与Genkit构建可复用技能模块

AI Agent Skills实战:基于Google Cloud、GKE与Genkit构建可复用技能模块

1. 从“skills”这个标题说起:它到底指什么

第一次看到“skills”这个标题,很多人会以为是某个泛泛而谈的能力清单,或者一份简历上的技能罗列。但结合热搜词里的 Agent Skills、Google Cloud、GKE、Genkit 这些关键词,方向就非常明确了——这里说的 skills,是围绕 AI Agent 构建的一套可复用能力模块,也就是让智能体真正“会做事”的那些封装好的技能单元。

我在实际接触这套东西之前,也走过一段弯路。最开始我以为 Agent 就是写一段提示词,把角色设定好,把任务描述清楚,它就能自己跑起来。结果真到落地的时候发现,提示词只能解决“说什么”,解决不了“怎么做”。比如你要让一个 Agent 去查数据库、调接口、生成报告、再把结果写回某个存储,光靠提示词是撑不起来的。这时候 skills 就登场了。

skills 的本质,是把一个具体的、可复用的操作能力,封装成 Agent 可以识别、可以调用、可以组合的模块。它有点像给 Agent 准备的“工具箱”,每个 skill 就是一把螺丝刀或者一个扳手。Agent 拿到任务之后,自己判断该用哪个工具、按什么顺序用、用完怎么把结果串起来。这个思路一旦跑通,Agent 的能力边界就从“聊天”扩展到了“干活”。

这套东西适合谁来参考?我的判断是三类人:第一类是已经在做 AI 应用开发、想让自己的产品从“能对话”升级到“能执行”的工程师;第二类是在云平台上做部署和运维、需要把 AI 能力接进现有系统的架构师;第三类是对 Agent 感兴趣、想自己动手搭一个能跑通闭环的爱好者。不管你是哪一类,只要你想让 AI 真正替你完成一串有依赖关系的操作,skills 这套机制就值得花时间吃透。

接下来我会从整体设计思路、核心细节、实操过程、常见问题几个层面,把这套东西拆开讲清楚。里面会涉及 Google Cloud、GKE、Genkit 这些具体工具的选择理由和配置方式,也会分享一些我在实际搭建过程中踩过的坑和总结出来的技巧。

2. 整体设计与思路拆解:为什么是 skills 而不是别的方案

2.1 从“单体提示词”到“技能模块化”的演进逻辑

早期做 Agent,大家习惯把所有指令塞进一个巨大的系统提示词里。任务简单的时候没问题,一旦任务变复杂,提示词就会膨胀到几千甚至上万字,维护起来非常痛苦。改一个环节,可能影响其他环节;加一个新功能,得重新梳理整个提示词的结构。更麻烦的是,这种单体提示词很难复用——A 项目里写好的逻辑,搬到 B 项目基本要重写。

skills 的思路正好相反。它把每个独立的能力拆出来,单独定义输入、输出、执行逻辑和依赖关系。Agent 在运行时根据任务需要,动态加载和调用这些 skill。这样做的好处有几个:一是可复用,一个查天气的 skill 可以在多个 Agent 里直接用;二是可测试,每个 skill 可以单独验证,不用把整个 Agent 跑起来才能测;三是可组合,复杂任务可以拆成多个 skill 的编排,像搭积木一样拼起来。

我自己的体会是,这种模块化带来的最大收益不是开发效率,而是调试效率。以前 Agent 出错,你得从头到尾排查提示词,很难定位是哪句话导致的问题。现在每个 skill 有明确的边界,出错的时候直接看是哪个 skill 的输入不对、还是输出格式不对、还是执行超时,排查路径清晰很多。

2.2 为什么选 Google Cloud + GKE + Genkit 这套组合

热搜词里出现了 Google Cloud、GKE、Genkit,这不是偶然。这套组合在当前 Agent 开发场景里,确实有它的合理性。

Google Cloud 提供的是底层基础设施,包括计算、存储、网络、鉴权这些。GKE 是 Google Kubernetes Engine,负责容器编排。为什么 Agent 需要容器编排?因为一个稍微像样的 Agent 系统,往往不是单个服务,而是多个 skill 服务、一个调度服务、一个状态管理服务、可能还有缓存和消息队列。这些服务需要部署、扩缩容、健康检查、滚动更新,Kubernetes 在这方面是成熟方案。

Genkit 是 Google 推出的 AI 应用开发框架,它的定位是帮开发者把 AI 能力接进应用里。它提供了模型调用、提示词管理、工具调用、流程编排这些基础能力。用 Genkit 来写 skill,好处是它天然支持工具调用(tool calling)的模式,和 skills 的思路非常契合。你可以把一个 skill 定义成一个 Genkit 的 tool,然后让模型在需要的时候自动调用。

这套组合的逻辑是:Genkit 负责 skill 的定义和编排,GKE 负责 skill 的部署和运行,Google Cloud 提供底层的算力和服务。三层各司其职,整体比较清晰。

2.3 方案选型时的几个关键取舍

在实际选型的时候,有几个点需要想清楚。

第一个取舍是:skill 的粒度怎么定。粒度太粗,一个 skill 干太多事,复用性就差;粒度太细,skill 数量爆炸,编排复杂度上升。我的经验是,一个 skill 最好对应一个“原子操作”,比如“查询用户订单”“发送邮件”“生成 PDF”,而不是“处理用户请求”这种模糊的复合操作。

第二个取舍是:skill 的执行是同步还是异步。同步调用简单直接,但遇到耗时操作会阻塞;异步调用灵活,但需要额外的状态管理。我一般建议,快速操作走同步,超过几秒的操作走异步,并且把任务 ID 返回给调用方,让调用方轮询或者等回调。

第三个取舍是:skill 的鉴权和隔离怎么做。不同 skill 可能需要访问不同的资源,权限不能一刀切。在 GKE 里可以用 Service Account 配合 RBAC 来做细粒度控制,每个 skill 服务用独立的 Service Account,只授予它需要的权限。

3. 核心细节解析与实操要点:skill 的定义、注册与调用

3.1 一个 skill 的最小结构长什么样

一个规范的 skill,至少包含这几个部分:名称、描述、输入参数定义、输出格式定义、执行逻辑、错误处理。名称和描述是给 Agent 看的,Agent 靠这些信息判断该不该调用这个 skill。输入输出定义是给编排层看的,用来做参数校验和结果传递。执行逻辑是实际干活的代码。错误处理决定 skill 失败时怎么反馈。

用 Genkit 来写的话,一个 skill 大概是这样定义的:

import { genkit, z } from 'genkit'; import { googleAI } from '@genkit-ai/googleai'; const ai = genkit({ plugins: [googleAI()], model: 'googleai/gemini-2.0-flash', }); export const queryOrderSkill = ai.defineTool( { name: 'queryOrder', description: '根据订单号查询订单的详细信息,包括状态、金额、创建时间', inputSchema: z.object({ orderId: z.string().describe('订单号,格式为 ORD 开头加 12 位数字'), }), outputSchema: z.object({ orderId: z.string(), status: z.string(), amount: z.number(), createdAt: z.string(), }), }, async (input) => { // 实际查询逻辑 const order = await fetchOrderFromDB(input.orderId); if (!order) { throw new Error(`订单 ${input.orderId} 不存在`); } return { orderId: order.id, status: order.status, amount: order.amount, createdAt: order.created_at, }; } );

这段代码里,name和description非常关键。Agent 在决定调用哪个 skill 的时候,主要依据就是这两个字段。描述写得越清楚,Agent 判断越准确。我见过很多 skill 调用失败,根本原因就是描述太模糊,Agent 不知道该在什么场景下用它。

3.2 skill 的注册与发现机制

定义好 skill 之后,需要把它注册到 Agent 可以访问的地方。Genkit 的做法是把 skill 作为 tool 传给模型,模型在生成回复的时候,如果判断需要调用某个 tool,就会输出一个 tool call 请求,框架拦截这个请求,执行对应的 skill,再把结果喂回模型。

在 GKE 上部署的时候,我通常会把一组相关的 skill 打包成一个服务,通过 HTTP 或者 gRPC 暴露出去。然后有一个统一的 skill registry 服务,记录所有可用 skill 的地址和元信息。Agent 启动的时候从 registry 拉取 skill 列表,运行时根据任务需要调用对应的 skill 服务。

这里有个细节值得注意:skill 的发现不一定要全量加载。如果 skill 数量很多,全量加载会让模型的上下文变得很长,影响判断准确率。更好的做法是按需加载,比如根据当前任务类型,只加载相关的 skill 子集。这个可以通过给 skill 打标签、按标签过滤来实现。

3.3 参数校验与错误处理的实操要点

skill 的输入参数校验,我建议用 schema 来做,不要靠人工判断。Genkit 集成了 Zod,可以直接用 Zod 定义输入输出的结构。这样做的好处是,参数不对的时候框架会自动拦截,不会让错误参数进入执行逻辑。

错误处理方面,有几个原则。第一,skill 内部要捕获可预期的异常,比如网络超时、资源不存在,转换成结构化的错误信息返回。第二,不可预期的异常要记录日志,方便排查。第三,错误信息要足够具体,让 Agent 或者上层编排逻辑知道该怎么处理。比如“订单不存在”和“数据库连接失败”是两种完全不同的错误,前者可能让 Agent 换个订单号重试,后者可能需要触发告警。

注意:skill 的错误信息不要直接暴露给最终用户,里面可能包含内部实现细节。建议在编排层做一层错误转换,把技术错误转成用户能理解的提示。

4. 实操过程与核心环节实现:从零搭一个可用的 skill 系统

4.1 环境准备与依赖安装

先说一下基础环境。我用的 Node.js 版本是 20 LTS,Genkit 对 Node 版本有要求,太老的版本会报错。安装 Genkit 和相关依赖:

npm install genkit @genkit-ai/googleai @genkit-ai/express zod

如果要用 Google Cloud 的其他服务,比如 Cloud SQL、Cloud Storage,还需要装对应的客户端库:

npm install @google-cloud/sql @google-cloud/storage

GKE 那边需要提前准备好集群。如果只是本地开发测试,可以用 minikube 或者 kind 起一个本地集群,不用一上来就买云资源。等 skill 逻辑跑通了,再往真实集群上部署。

4.2 定义第一个 skill 并本地验证

我建议从最简单的 skill 开始,比如一个“获取当前时间”的 skill。别看它简单,它能帮你把整条链路跑通:定义、注册、调用、返回结果。

export const getCurrentTimeSkill = ai.defineTool( { name: 'getCurrentTime', description: '获取当前服务器时间,返回 ISO 格式字符串', inputSchema: z.object({}), outputSchema: z.object({ time: z.string(), }), }, async () => { return { time: new Date().toISOString() }; } );

定义好之后,写一个简单的测试脚本,让模型调用这个 skill:

const response = await ai.generate({ prompt: '现在几点了?', tools: [getCurrentTimeSkill], }); console.log(response.text);

如果模型正确调用了 skill 并返回了时间,说明基础链路是通的。这一步看起来简单,但能帮你提前发现配置问题,比如 API key 没设对、模型名称写错、网络不通等等。

4.3 把 skill 部署到 GKE 的完整流程

本地验证通过之后,就可以往 GKE 上部署了。流程大致是:写 Dockerfile、构建镜像、推送到镜像仓库、写 Kubernetes 部署文件、应用部署、验证服务。

Dockerfile 大概长这样:

FROM node:20-slim WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE 8080 CMD ["node", "server.js"]

Kubernetes 部署文件里,有几个地方需要特别注意。一是资源限制,skill 服务通常不需要太多 CPU 和内存,但要根据实际负载调整。二是健康检查,要配置 liveness 和 readiness 探针,否则服务假死的时候 Kubernetes 不会自动重启。三是环境变量,API key 这类敏感信息不要写进镜像,用 Secret 挂载。

apiVersion: apps/v1 kind: Deployment metadata: name: skill-service spec: replicas: 2 selector: matchLabels: app: skill-service template: metadata: labels: app: skill-service spec: containers: - name: skill-service image: gcr.io/your-project/skill-service:latest ports: - containerPort: 8080 resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 10 env: - name: GOOGLE_API_KEY valueFrom: secretKeyRef: name: api-secrets key: google-api-key

部署命令:

kubectl apply -f deployment.yaml kubectl get pods -l app=skill-service kubectl logs -f <pod-name>

4.4 多 skill 编排的实操示例

单个 skill 跑通之后,真正的价值在于多 skill 编排。比如一个“处理退款申请”的任务,可能需要依次调用:查询订单 skill、验证退款资格 skill、计算退款金额 skill、发起退款 skill、发送通知 skill。

在 Genkit 里可以用 flow 来编排:

export const refundFlow = ai.defineFlow( { name: 'refundFlow', inputSchema: z.object({ orderId: z.string() }), outputSchema: z.object({ success: z.boolean(), message: z.string() }), }, async (input) => { const order = await queryOrderSkill.run({ orderId: input.orderId }); const eligible = await checkRefundEligibilitySkill.run({ order }); if (!eligible.canRefund) { return { success: false, message: eligible.reason }; } const amount = await calculateRefundSkill.run({ order }); const result = await initiateRefundSkill.run({ orderId: input.orderId, amount }); await sendNotificationSkill.run({ orderId: input.orderId, status: 'refunded' }); return { success: true, message: `退款 ${amount} 元已发起` }; } );

这种编排方式的好处是,每个步骤的输入输出都是明确的,出错的时候能精确定位到是哪一步的问题。而且每个 skill 可以独立测试,不用每次都跑完整流程。

5. 常见问题与排查技巧实录

5.1 skill 调用失败的高频原因速查

问题现象可能原因排查方法解决方式
Agent 不调用 skillskill 描述不清晰检查 description 是否说明了使用场景补充描述,明确触发条件
调用参数格式错误schema 定义与实际不符打印实际传入参数修正 schema 或调用方
skill 执行超时内部逻辑耗时过长加日志看卡在哪一步优化逻辑或改异步
返回结果解析失败输出格式与 schema 不匹配对比实际输出和 schema修正输出或 schema
权限不足Service Account 权限不够查看 GKE 事件日志补充 RBAC 权限
服务无法访问网络策略或端口配置错误检查 Service 和 Ingress修正网络配置

5.2 几个我踩过的坑和对应的解法

第一个坑是 skill 描述写得太技术化。我一开始写描述的时候,习惯用“调用 XX 接口,返回 XX 数据”这种表述。结果 Agent 经常不调用,因为它不理解这个 skill 在什么业务场景下有用。后来改成“当用户询问订单状态时使用”,调用准确率明显提升。描述要站在 Agent 的角度写,告诉它“什么时候用”,而不是“这个 skill 做了什么”。

第二个坑是 skill 之间共享状态。我一开始想让多个 skill 共享一个数据库连接池,结果发现 GKE 里 Pod 是分布式的,每个 Pod 有自己的连接池,共享不了。后来改成每个 skill 服务独立管理自己的连接,通过外部存储来共享状态。这个坑的本质是,不要把单机思维带到分布式环境里。

第三个坑是错误信息太笼统。早期我的 skill 出错都返回“操作失败”,Agent 拿到这个信息完全不知道该怎么办。后来改成返回具体的错误码和描述,比如“ORDER_NOT_FOUND”“INSUFFICIENT_BALANCE”,Agent 就能根据错误类型决定是重试、换参数、还是上报。错误信息是 Agent 做决策的重要依据,不能敷衍。

5.3 性能优化的几个实用技巧

skill 系统的性能瓶颈通常不在计算,而在网络往返和模型调用。优化方向有几个。

一是减少不必要的 skill 调用。有些 skill 的结果可以缓存,比如配置类、字典类的查询,没必要每次都调。在 skill 内部加一层缓存,能显著降低延迟。

二是并行化独立的 skill。如果两个 skill 之间没有依赖关系,可以并行调用,而不是串行等待。Genkit 的 flow 支持并行执行,用Promise.all就能实现。

三是控制 skill 返回的数据量。有些 skill 返回一大堆字段,但 Agent 实际只用其中几个。返回数据太大会占用上下文窗口,影响模型判断。建议只返回必要字段,或者提供分页机制。

四是合理设置超时。每个 skill 调用都要设超时,避免一个慢 skill 拖垮整个流程。超时时间根据 skill 的实际耗时来定,一般设成 P99 耗时的 1.5 到 2 倍。

6. skill 系统的扩展与维护经验

6.1 skill 版本管理与灰度发布

skill 一旦上线,就会被多个 Agent 或者流程依赖。直接改线上 skill 风险很大,可能影响正在运行的任务。我的做法是给 skill 加版本号,新版本先灰度发布,观察一段时间没问题再全量。

在 GKE 里可以用两个 Deployment 分别跑新旧版本,通过 Service 的标签选择器控制流量比例。或者用 Istio 这类服务网格做更精细的流量切分。关键是,skill 的接口要保持向后兼容,不能因为加了个参数就让老调用方挂掉。

6.2 监控与告警的配置要点

skill 系统跑起来之后,必须要有监控。我关注几个核心指标:调用次数、成功率、P50/P95/P99 延迟、错误分布。这些指标可以用 Google Cloud 的 Operations Suite 来采集和展示。

告警方面,我设了两条线:一是成功率低于 95% 持续 5 分钟就告警,二是 P99 延迟超过阈值持续 10 分钟就告警。告警不要设得太敏感,否则会被噪音淹没;也不要太迟钝,否则出了问题半天才发现。

日志方面,每个 skill 调用都要记录:调用时间、输入参数(脱敏后)、输出结果(脱敏后)、耗时、是否成功。这些日志在排查问题的时候非常有用。但要注意,日志里不要记录敏感信息,比如用户密码、支付凭证。

6.3 skill 复用与组合的进阶思路

当 skill 积累到一定数量之后,可以考虑做 skill 的组合封装。比如把“查询订单 + 验证资格 + 计算金额”这三个 skill 组合成一个“退款预检”skill,对外只暴露一个接口。这样上层编排会更简洁,但底层还是复用了原有的原子 skill。

另一个思路是让 Agent 自己组合 skill。给 Agent 一批原子 skill,让它根据任务目标自己决定调用顺序。这种方式灵活度高,但可控性差,适合探索性任务。对于流程固定的业务场景,还是用预定义的 flow 更稳妥。

我在实际项目里的做法是混合使用:核心业务流程用预定义 flow,保证稳定性和可预测性;辅助性的、探索性的任务让 Agent 自由组合 skill,发挥灵活性。两者结合,既能保证主干流程可靠,又能应对长尾需求。

6.4 安全与权限的实操建议

skill 系统涉及多个服务之间的调用,安全不能忽视。几个基本要求:服务间通信用 mTLS,不要裸奔;每个 skill 服务用独立的 Service Account,权限最小化;敏感配置用 Secret 管理,不要硬编码;对外暴露的接口要有鉴权和限流。

在 GKE 里,可以用 Workload Identity 把 Kubernetes 的 Service Account 和 Google Cloud 的 IAM 绑定,这样 Pod 访问云资源的时候不用管理密钥文件,安全性更高。网络策略方面,用 NetworkPolicy 限制 Pod 之间的访问,只允许必要的通信。

提示:skill 的输入参数里如果包含用户可控的内容,一定要做校验和转义,防止注入类问题。尤其是拼接 SQL、调用外部命令的场景,要格外小心。

7. 我对这套东西的真实体会

折腾了这么久,我最大的感受是:skills 这套机制的价值,不在于技术有多新,而在于它把 Agent 的开发从“写提示词”变成了“搭系统”。提示词是模糊的、难以测试的、难以复用的;skill 是明确的、可测试的、可复用的。这个转变,让 Agent 从玩具变成了工具。

另一个体会是,skill 的设计比实现更重要。我见过太多人花大量时间写 skill 的执行逻辑,却忽略了 skill 的描述、输入输出定义、错误处理。结果 skill 本身能跑,但 Agent 不会用、用不对、出错不知道怎么处理。skill 是给 Agent 用的,不是给人用的,设计的时候要站在 Agent 的角度想问题。

最后分享一个小技巧:如果你刚开始接触这套东西,不要一上来就搞复杂的多 skill 编排。先写一个最简单的 skill,把定义、注册、调用、返回这条链路跑通。然后再加第二个、第三个,逐步体会 skill 之间怎么协作。这个过程比看十篇文档都管用。

返回列表