Agent Skills 实战:基于 Genkit 与 GKE 的 AI 技能封装与部署

发布时间:2026/10/8 11:59:43
Agent Skills 实战:基于 Genkit 与 GKE 的 AI 技能封装与部署
1. 从“skills”这个标题说起它到底在解决什么问题第一次看到“skills”这个标题很多人会以为是某个技能清单或者学习路线图。但结合热搜词里的 Google Cloud、Agent Skills、GKE、Genkit 来看这里说的 skills 其实是一个更具体的东西面向 AI Agent 的能力封装单元。你可以把它理解成给 AI 助手安装的“技能插件”——每个 skill 就是一套可复用的指令、工具调用逻辑和上下文约束让 Agent 在特定场景下表现得像一个受过训练的专业人员。我最早接触这个概念是在做自动化工作流的时候。当时手头有一堆重复性任务查日志、生成报告、调用内部 API 做数据同步。每次都要写一大段 prompt 告诉模型“先做什么、再做什么、遇到什么情况怎么处理”。后来发现把这些逻辑固化成一个个独立的 skill用的时候直接挂载效率完全不一样。Agent Skills 的核心价值就在这里把“一次性对话”变成“可积累的能力资产”。这篇文章适合谁看如果你正在用 Genkit 搭建 AI 工作流或者在 GKE 上跑 Agent 服务又或者只是好奇“codex skills”“claude agent skills”这些词到底指什么那接下来的内容应该能帮你省下不少翻文档的时间。我会从设计思路讲到实操细节包括怎么定义 skill、怎么测试、怎么排查常见问题尽量把踩过的坑都摊开说。2. Agent Skills 的整体设计与选型思路2.1 为什么是“技能”而不是“提示词模板”提示词模板和 skill 最本质的区别在于边界。模板是一段文本模型可以自由发挥skill 是一个有明确输入输出契约的单元它规定了“什么情况下触发、需要哪些参数、执行哪些步骤、返回什么结构”。这个区别在单轮对话里不明显但一旦进入多 Agent 协作或者长流程任务边界不清就会导致灾难性的连锁错误。我试过用纯提示词模板做订单处理流程结果模型在第三步突然“发挥创意”把退款逻辑套用到了换货场景上。后来改成 skill 封装每个 skill 只做一件事输入输出都用 schema 约束问题就消失了。这也是为什么 Google Cloud 的 Agent Skills 体系强调tool declaration和function calling——本质上是在用工程手段约束模型的自由度。2.2 Genkit 与 GKE 在技能体系中的角色分工Genkit 负责的是技能的定义与编排。它提供了一套声明式的 API让你用 TypeScript 或 Go 描述一个 skill 的输入 schema、输出 schema 和执行逻辑。你可以把它想象成“技能的 IDE”——写 skill、调试 skill、组合 skill 都在这里完成。GKE 负责的是技能的运行与伸缩。当你的 Agent 需要同时服务几百个请求时每个 skill 调用都可能变成一次独立的计算任务。GKE 的 pod 自动扩缩容和资源隔离机制能保证某个 skill 出问题不会拖垮整个 Agent 服务。我自己的项目里把耗时较长的“文档解析 skill”单独部署在一个 node pool 上和轻量的“意图识别 skill”隔离开整体稳定性提升非常明显。2.3 一个 skill 的粒度应该怎么定这是我最常被问到的问题。粒度太细skill 数量爆炸编排复杂度飙升粒度太粗又退化成提示词模板。我的经验法则是一个 skill 只对应一个“可独立测试的业务动作”。比如“查询订单状态”是一个 skill“根据订单状态决定是否退款”就是另一个 skill。前者可以单独 mock 数据测试后者依赖前者的输出但逻辑独立。具体判断标准有三条第一这个动作能否用一句话描述清楚且没有“然后”第二这个动作的输入是否可以用一个 schema 完整表达第三这个动作失败时是否有明确的错误码可以返回。三条都满足就适合做成独立 skill。3. 核心细节解析与实操要点3.1 Skill 的声明结构从 schema 到执行函数一个标准的 Agent Skill 通常包含四个部分元数据、输入 schema、输出 schema、执行函数。元数据里最重要的是 name 和 description这两个字段直接决定模型能否正确选择这个 skill。我见过太多人在这上面偷懒description 写得含糊不清结果模型该调用的时候不调用不该调用的时候乱调用。输入输出 schema 建议用 Zod 或 JSON Schema 严格定义。Genkit 对 Zod 的支持很好类型推导也顺畅。执行函数里要注意的是错误处理——不要直接把异常抛给模型而是返回结构化的错误信息让模型知道“这次失败了原因是参数缺失可以补充后重试”。这样 Agent 才有自我修正的机会。// 一个查询订单状态的 skill 声明示例 import { z } from zod; import { defineTool } from genkit-ai/ai; export const queryOrderStatus defineTool( { name: queryOrderStatus, description: 根据订单号查询当前状态返回状态码和预计完成时间, inputSchema: z.object({ orderId: z.string().describe(订单号格式为 ORD- 开头加 8 位数字), }), outputSchema: z.object({ status: z.enum([pending, processing, shipped, delivered, cancelled]), estimatedTime: z.string().optional(), lastUpdate: z.string(), }), }, async (input) { // 实际调用内部 API 的逻辑 const result await orderService.query(input.orderId); return { status: result.status, estimatedTime: result.eta, lastUpdate: result.updatedAt, }; } );3.2 技能编排中的上下文传递陷阱多个 skill 串联时上下文传递是最容易出问题的地方。Genkit 的 flow 机制默认会把前一个 skill 的输出作为后一个的输入候选但不会自动做字段映射。这意味着如果你不显式指定“把 A 的 output.orderId 传给 B 的 input.orderId”模型可能会自己瞎猜把整个输出对象塞进去。我的做法是在 flow 定义里用.map()显式做字段提取虽然多写几行代码但省去了大量调试时间。另外对于可选参数一定要在 schema 里标.optional()否则模型在缺少该字段时会直接报错而不是跳过。3.3 在 GKE 上部署技能服务的资源规划把 skill 服务部署到 GKE 时资源 request 和 limit 的设置直接影响成本和稳定性。我的经验是CPU request 设为实际峰值的 60%limit 设为峰值的 150%。内存则相反request 可以设高一点峰值的 80%因为内存不足会导致 OOM Kill比 CPU throttling 严重得多。另外如果你的 skill 里有调用外部 API 的逻辑记得给 pod 配置合适的terminationGracePeriodSeconds。我遇到过滚动更新时旧 pod 被 kill 导致正在进行的 API 调用中断下游服务收到半截请求报错。后来把这个值从默认的 30 秒调到 90 秒问题就没了。4. 实操过程与核心环节实现4.1 从零搭建一个可测试的 Skill 项目先初始化 Genkit 项目。我用的是 TypeScript 模板因为类型提示对写 schema 帮助很大。npm init -y npm install genkit genkit-ai/ai genkit-ai/google-cloud zod npx genkit init初始化完成后项目结构大概是这样的src/tools/放 skill 定义src/flows/放编排逻辑src/index.ts是入口。我习惯再建一个src/tests/目录每个 skill 对应一个测试文件。写第一个 skill 时建议从最简单的“回声”开始——输入什么就返回什么。这听起来很傻但能帮你快速验证整条链路schema 定义是否正确、Genkit 能否识别、本地调试工具能否调用。我当初跳过这一步直接写复杂 skill结果卡在 schema 校验上花了两个小时。4.2 本地调试与 Agent Skills 测试方法Genkit 提供了一个本地开发者 UI运行npx genkit start后会在浏览器打开一个调试面板。这个面板可以手动输入参数调用 skill也能看到每次调用的完整 trace。对于 Agent Skills 测试来说这个 trace 非常关键——你能看到模型在什么时候选择了哪个 skill传了什么参数返回了什么结果。我通常会用三种测试用例正常路径、边界值、异常输入。正常路径验证基本功能边界值测试 schema 的约束是否生效比如订单号少一位会怎样异常输入看错误处理是否返回了有意义的信息。这三种跑通skill 的基本质量就有保障了。4.3 部署到 GKE 的完整流程先写 Dockerfile。Genkit 项目编译后是 Node.js 服务基础镜像用node:20-slim就够了。注意要把NODE_ENV设为production否则 Genkit 会加载开发依赖镜像体积会大很多。FROM node:20-slim WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY dist/ ./dist/ ENV NODE_ENVproduction EXPOSE 3400 CMD [node, dist/index.js]然后构建镜像并推送到 Artifact Registry。GKE 集群需要配置好对 Artifact Registry 的访问权限这个在创建集群时勾选即可。部署时用 Deployment 加 ServiceService 类型选 ClusterIP前面再挂一个 Ingress 做外部访问。apiVersion: apps/v1 kind: Deployment metadata: name: agent-skills spec: replicas: 2 selector: matchLabels: app: agent-skills template: metadata: labels: app: agent-skills spec: containers: - name: skills image: us-central1-docker.pkg.dev/PROJECT_ID/repo/agent-skills:latest ports: - containerPort: 3400 resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1500m memory: 1Gi readinessProbe: httpGet: path: /health port: 3400 initialDelaySeconds: 10 periodSeconds: 54.4 参数计算如何确定副本数和资源配额副本数的计算取决于两个因素QPS 和单副本处理能力。假设你的 Agent 服务平均每秒收到 20 个请求每个请求平均触发 1.5 次 skill 调用那么 skill 服务的 QPS 大约是 30。单副本在压测下能稳定处理 15 QPS那么至少需要 2 个副本。考虑到突发流量我一般会再留 50% 余量所以设 3 个副本。资源配额方面用kubectl top pod观察实际使用量。如果 CPU 使用率长期在 request 的 70% 以上就该调高 request 了。内存则看 working set留 30% 余量比较安全。5. 常见问题与排查技巧实录5.1 模型不调用 Skill 或调用错误 Skill这是最高频的问题。排查顺序是这样的先看 skill 的 description 是否足够具体。如果 description 写的是“处理订单”模型很难判断该不该调用改成“根据订单号查询物流状态适用于用户询问包裹位置时”命中率会大幅提升。其次检查是否有多个 skill 的 description 语义重叠。比如同时存在“查询订单”和“获取订单详情”两个 skill模型很容易混淆。这时候要么合并要么在 description 里明确区分场景。最后看模型的 temperature 设置。温度太高会导致模型“随意发挥”把本该调用 skill 的场景当成普通对话处理。我一般把 Agent 场景的 temperature 设在 0.1 到 0.3 之间。5.2 Skill 执行超时或返回空结果超时通常有两个原因外部 API 响应慢或者 skill 内部逻辑有死循环。前者可以通过设置合理的 timeout 和重试策略解决后者需要检查代码里是否有未处理的 Promise 或者递归调用。返回空结果则多半是 schema 不匹配。比如输出 schema 要求status字段是枚举值但实际返回了一个不在枚举里的字符串Genkit 会静默丢弃这个字段。排查方法是打开 Genkit 的 debug 日志看原始返回值和 schema 校验结果。5.3 GKE 部署后 Pod 频繁重启先看kubectl describe pod的 Events 部分。如果是 OOMKilled说明内存 limit 设小了如果是 Liveness probe failed说明健康检查路径不对或者初始延迟太短。我遇到过因为 readiness probe 的 initialDelaySeconds 设成 5 秒但应用启动需要 8 秒导致 pod 一直没 ready 就被重启的情况。改成 15 秒后恢复正常。还有一个隐蔽的问题如果 skill 服务依赖外部 API而 GKE 节点的网络策略限制了出站流量pod 会一直卡在启动阶段。这时候需要检查 NetworkPolicy 和防火墙规则。5.4 常见问题速查表问题现象可能原因排查方法解决措施模型不调用 skilldescription 模糊查看 trace 中模型决策日志重写 description增加场景关键词调用错误 skill多个 skill 语义重叠对比各 skill 的 description合并或明确区分触发条件skill 执行超时外部 API 慢或死循环查看 skill 内部日志和 trace设置 timeout检查循环逻辑返回空结果schema 不匹配开启 debug 日志看校验结果调整 schema 或修正返回值Pod 频繁重启OOM 或 probe 失败kubectl describe pod 看 Events调整资源配额或 probe 参数部署后无法访问网络策略限制检查 NetworkPolicy 和 Service放行出站流量确认端口映射5.5 几个让我少走弯路的实操心得第一个心得给每个 skill 写一个“负面用例”测试。比如“查询订单状态”这个 skill负面用例就是传入一个不存在的订单号验证它返回的是结构化的“未找到”而不是抛异常。这个习惯帮我提前发现了大量边界问题。第二个心得在 GKE 上用 Horizontal Pod Autoscaler 时不要只盯 CPU。skill 服务的瓶颈往往在外部 API 的响应时间上CPU 可能很低但请求已经堆积了。我后来加了一个基于自定义指标请求队列长度的 HPA扩缩容及时性好了很多。第三个心得skill 的版本管理要和 Agent 的版本管理分开。skill 可以独立迭代只要保持输入输出 schema 兼容就行。我用的是语义化版本schema 有 breaking change 就升 major否则升 minor。这样 Agent 升级时不用把所有 skill 都重新测试一遍。6. 技能体系的扩展方向与个人体会这套 skill 体系跑通之后我陆续把很多重复性工作都封装了进去。比如“生成周报”这个 skill输入是一周内的 commit 记录和 issue 列表输出是格式化的 Markdown。以前每周手动整理要花半小时现在 Agent 自动跑我只需要审核一下。扩展的时候有个原则先有稳定流程再做 skill 封装。如果某个任务你自己都还没跑顺急着做成 skill 只会把混乱固化下来。我踩过这个坑——把一个还在频繁调整的逻辑封装成 skill结果每次调整都要改 schema、改测试、重新部署反而比直接写 prompt 更麻烦。另外skill 之间的依赖关系要尽量保持单向。A 调用 BB 就不要反过来调用 A否则调试时会陷入无限递归的噩梦。如果确实需要双向交互用事件或者消息队列解耦别在 skill 内部直接互相调用。最后分享一个我最近在用的技巧给每个 skill 加一个dryRun参数。当 dryRun 为 true 时skill 只返回“将会执行什么操作”而不真正执行。这在测试编排流程时特别有用能快速验证 skill 之间的参数传递是否正确而不用担心中间步骤产生副作用。这个参数在 schema 里是可选的默认 false对正常调用没有影响。