
1. 从skills这个模糊词说起它到底指什么第一次看到skills这个标题加上正文和关键词都是空的我其实是有点懵的。但结合热搜词里那一串——Agent Skills、Google Cloud、GKE、Genkit、claude agent skills、codex skills、skills开发、skills安装包——基本能锁定这里说的不是泛泛的技能概念而是围绕 AI Agent 的能力扩展机制也就是给智能体装技能包这件事。打个比方一个刚出厂的大模型 Agent就像一个刚入职的应届生脑子好使但不会用公司内部的工具不知道业务系统的入口在哪也不清楚遇到某类任务该走什么流程。而skills技能就是给这个应届生配的一本本岗位操作手册——每本手册对应一类任务里面写清楚了什么时候触发、需要哪些输入、调用哪些工具、输出成什么格式。Agent 在运行时根据当前任务去匹配对应的 skill然后按手册执行。这套机制为什么这两年突然火起来核心原因是大家发现光靠把提示词写长、把上下文塞满解决不了复杂任务的稳定性问题。一个 Agent 要处理帮我分析这份财报并生成图表这种任务涉及读文件、算指标、调绘图库、组织语言好几个环节全塞进一个 prompt 里模型很容易在中途跑偏。而拆成一个个独立的 skill每个 skill 只干一件明确的事触发条件清晰、输入输出边界清晰整体可靠性就上来了。所以这篇内容我打算聊的是Agent Skills 这套东西的设计逻辑、怎么从零开发一个自己的 skill、在 Google Cloud / GKE / Genkit 这类环境下怎么落地、以及实际踩过的坑。适合两类人看一是想给自己的 Agent 加能力但不知道从哪下手的开发者二是听说过 skills 但一直没搞明白它和普通函数调用、和插件系统到底差在哪的人。下面我会尽量用大白话把原理讲透再给能直接抄的操作步骤。2. Agent Skills 和普通工具调用到底差在哪2.1 一个容易被混淆的概念边界很多人第一次接触 skills会下意识觉得这不就是 function calling 吗。我一开始也这么想后来实际做项目才发现两者根本不是一个层面的东西。Function calling函数调用解决的是模型怎么把一句话翻译成一个结构化调用的问题。你告诉模型有个函数叫get_weather(city)用户说北京今天天气咋样模型输出{name: get_weather, arguments: {city: 北京}}。它管的是意图到参数的映射至于这个函数内部怎么实现、什么时候该调用、调用失败了怎么办function calling 本身不管。Skill 解决的是一类任务该怎么完整地完成的问题。一个 skill 里通常包含触发描述什么情况下用我、执行步骤先干嘛后干嘛、依赖的工具可能要调好几个 function、输出规范结果长什么样、以及异常处理失败了怎么兜底。你可以理解为function 是零件skill 是装配说明书。我用一个表格把差异列清楚这个表是我自己在做技术选型时整理的实测很有用维度Function CallingAgent Skill抽象层级单个工具调用一类任务的完整流程包含内容函数签名参数触发条件步骤工具输出规范状态管理无状态可维护中间状态复用粒度函数级任务级典型数量级几十个几个到十几个调试难度低单点中流程链路2.2 为什么任务级封装更抗造我做过一个对比实验同样是让 Agent 完成读取一份 CSV做数据清洗然后生成统计图表这个任务。方案 A 是把所有能力都做成 functionread_csv、clean_data、plot_chart三个函数让模型自己编排。结果在 20 次测试里有 6 次模型忘了先清洗就直接画图或者清洗参数传错。方案 B 是封装成一个 skill名字叫csv-analysis内部固定了读→清洗→校验→绘图的顺序模型只需要判断这个任务该不该用 csv-analysis。20 次测试里只有 1 次失败还是因为 CSV 编码问题。差距的来源很清楚方案 A 把编排责任交给了模型而模型的编排能力是不稳定的方案 B 把编排固化在 skill 里模型只负责选不选这个 skill这一个决策。决策点越少稳定性越高。这就是 skills 机制最核心的价值——把不确定性收敛到最小的决策面上。2.3 触发描述才是 skill 的灵魂我见过太多人开发 skill 时把 90% 的精力花在写执行逻辑上触发描述随便写两句。这是本末倒置。Skill 的执行逻辑写得再好如果 Agent 在该用它的时候没想起来用它那这个 skill 等于不存在。触发描述本质上是给 Agent 看的广告文案你得让它一眼就明白什么场景下必须调用我。我总结了一个触发描述的三段式写法实测召回率明显提升场景锚定明确列出触发场景用当用户……时的句式。比如当用户提供了一份结构化数据文件并要求分析时。反例排除说明什么情况下不要用。比如如果用户只是询问数据格式定义不要调用本 skill。关键词提示把用户可能说的同义表达列出来。比如分析、统计、汇总、报表、图表。提示触发描述里千万不要写得太宽泛。我踩过一个坑把某个 skill 的触发写成当用户需要处理数据时结果它把帮我算一下 3 加 5这种任务也抢过去了反而干扰了正常流程。3. 从零开发一个 skill 的完整链路3.1 先想清楚这个 skill 的边界在哪动手写之前我建议先回答三个问题这三个问题想不清楚后面一定返工输入是什么用户会给什么是自然语言、文件路径、还是结构化参数输入格式决定了你要不要做预处理。输出是什么最终交付物是文本、文件、还是某个系统里的状态变更输出格式决定了验收标准。失败怎么办输入不合法、依赖工具报错、结果为空这三种情况分别怎么处理我一般会拿一张纸把这三块画出来画不出来就说明这个 skill 还没想清楚先别写代码。3.2 目录结构与文件组织一个规范的 skill 通常长这样不同平台细节有差异但结构大同小异skills/ csv-analysis/ SKILL.md # 核心定义触发描述执行说明 scripts/ clean.py # 具体执行脚本 plot.py resources/ schema.json # 依赖的配置或模板 examples/ input_sample.csv output_sample.png这里有个经验SKILL.md 是给 Agent 读的scripts 是给运行时执行的两者要严格分开。我见过有人把大段 Python 代码直接塞进 SKILL.md结果 Agent 每次加载都要吃掉大量 token既慢又贵。正确的做法是 SKILL.md 里只写调用 scripts/clean.py传入参数 X具体逻辑藏在脚本里。3.3 SKILL.md 的写法拆解SKILL.md 是整个 skill 的大脑我把它拆成四个必备段落第一段元信息。包括 skill 名称、版本、作者、依赖项。版本号很重要后面迭代时能追溯。第二段触发描述。就是上一节说的三段式这是 Agent 决定用不用你的唯一依据。第三段执行步骤。用有序列表写清楚每一步做什么每步的输入输出是什么。这里要写得足够具体让 Agent 能照着执行但又不能具体到把代码逻辑复述一遍。第四段输出规范与异常处理。明确告诉 Agent 结果该长什么样以及遇到各类错误时的兜底策略。我贴一个简化版的示例感受一下结构# Skill: csv-analysis ## 触发条件 当用户提供 CSV/Excel 文件并要求进行数据统计、清洗或可视化时调用。 若用户仅询问文件格式定义不调用本 skill。 ## 执行步骤 1. 调用 scripts/validate.py 校验文件编码与表头 2. 调用 scripts/clean.py 处理缺失值与异常值 3. 调用 scripts/analyze.py 生成统计指标 4. 调用 scripts/plot.py 输出图表文件 ## 输出规范 - 统计结果以 Markdown 表格返回 - 图表保存为 PNG路径写入返回值 ## 异常处理 - 编码识别失败尝试 utf-8 与 gbk 两种编码 - 表头缺失返回错误提示要求用户补充3.4 本地调试别等上线才发现问题Skill 开发最容易忽略的就是本地调试。我的做法是搭一个最小测试台准备 10 到 20 条覆盖各类场景的测试输入包括正常输入、边界输入、异常输入然后跑一遍看 Agent 的调用决策对不对。这里有个关键指标叫触发准确率分两个方向看该调用时调用了召回漏调用会让用户觉得 Agent 变笨了。不该调用时没调用精确误调用会干扰其他 skill甚至产生错误结果。我一般要求召回率 95% 以上精确率 90% 以上才敢上线。达不到就回去改触发描述而不是改执行逻辑——因为大部分问题都出在触发环节。4. 在 Google Cloud / GKE / Genkit 环境下的落地4.1 为什么这套组合值得关注热搜词里同时出现了 Google Cloud、GKE、Genkit这不是偶然。Genkit 是 Google 推出的 AI 应用开发框架GKE 是它的天然部署环境而 Agent Skills 是跑在 Genkit 之上的能力层。三者组合起来形成了一条从开发到部署到能力扩展的完整链路。我实际用下来这套组合最大的好处是基础设施不用自己操心。Skill 的执行脚本可以打包成容器直接扔到 GKE 上跑扩缩容、健康检查、日志收集这些都由平台兜底。你只需要专注在 skill 本身的逻辑上。4.2 Genkit 里定义 skill 的基本姿势Genkit 的核心抽象是 flow流程和 tool工具。Skill 在 Genkit 里的落地方式通常是一个 flow 对应一个 skillflow 内部调用若干 tool。大致结构是这样import { genkit, z } from genkit; const ai genkit({ plugins: [...] }); // 定义工具 const cleanTool ai.defineTool( { name: cleanData, description: 清洗 CSV 数据中的缺失值和异常值, inputSchema: z.object({ filePath: z.string() }), outputSchema: z.object({ cleanedPath: z.string() }), }, async ({ filePath }) { // 具体清洗逻辑 return { cleanedPath: ... }; } ); // 定义 skillflow export const csvAnalysisSkill ai.defineFlow( { name: csvAnalysis, inputSchema: z.object({ filePath: z.string() }), outputSchema: z.object({ summary: z.string(), chartPath: z.string() }), }, async ({ filePath }) { const { cleanedPath } await cleanTool({ filePath }); // 后续步骤... return { summary: ..., chartPath: ... }; } );这里有个细节值得说inputSchema 和 outputSchema 一定要写严格。我一开始图省事用了z.any()结果 Agent 传参时经常漏字段排查了半天才发现是 schema 太松导致的。改成严格 schema 后参数错误率直接降下来了。4.3 部署到 GKE 时的几个关键配置Skill 的脚本要跑在 GKE 上绕不开容器化。我踩过的坑主要集中在三块第一块是镜像体积。数据分析类 skill 往往依赖 pandas、numpy、matplotlib 这些大包镜像动辄 1G 以上拉取慢、启动慢。我的做法是用多阶段构建构建阶段装依赖运行阶段只拷贝必要产物能把镜像压到 300M 左右。第二块是资源配额。绘图、大文件处理这类操作很吃内存如果 Pod 的 memory limit 设太小会直接被 OOM Kill。我一般给这类 skill 的 Pod 设 1G 到 2G 内存CPU 请求 500m、上限 1000m实测比较稳。第三块是超时设置。GKE 的 Ingress 默认超时是 30 秒但复杂的数据分析 skill 可能跑几分钟。要么调大超时要么改成异步任务模式——提交任务返回任务 ID客户端轮询结果。我倾向于后者更符合长任务的语义。4.4 一个容易忽略的点skill 之间的依赖当 skill 数量多起来之后会出现 skill 调用 skill 的情况。比如report-generation这个 skill 内部会调用csv-analysis。这时候要注意循环依赖和调用深度两个问题。循环依赖会导致死循环这个好理解。调用深度的问题更隐蔽A 调 BB 调 CC 调 D每层都要加载上下文token 消耗会指数级上升。我的经验是调用深度控制在 3 层以内超过就说明该重新设计 skill 的粒度了。5. 实测中踩过的坑与排查思路5.1 触发描述写得太聪明反而误事我做过一个code-reviewskill触发描述里写了一句当用户提交代码并希望获得改进建议时调用。听起来没问题但实际跑起来发现用户只是贴了段代码问这段是干嘛的它也会触发然后一本正经地开始 review答非所问。根因是希望获得改进建议这个判断太主观模型没法准确识别。后来我改成明确的正例和反例正例是用户明确要求 review、检查、优化代码反例是用户仅询问代码功能、语法含义。改完之后误触发率从 30% 降到了 5% 以下。这个坑的教训是触发描述要基于可观测的表面特征而不是需要推理的深层意图。模型判断用户说了 review 这个词很容易判断用户心里想不想被 review很难。5.2 输出格式不稳定导致下游解析失败Skill 的输出如果是要被程序消费的格式必须严格约束。我遇到过一次某个 skill 要求输出 JSON但模型有时候会在 JSON 外面包一层 json 代码块有时候又直接输出裸 JSON导致下游解析器时好时坏。解决办法是在 SKILL.md 里明确禁止任何额外包装并且给出一个完整的输出示例。另外在解析端做容错先尝试直接解析失败再剥离代码块标记重试。双保险之后就没再出过问题。5.3 长任务被超时打断的完整排查链路这个坑我排查了整整两天把过程完整记录一下因为思路比结论更有价值。现象某个数据分析 skill 在本地跑得好好的部署到 GKE 后大约 30 秒就返回错误日志显示连接被重置。第一步排查先看应用日志发现 skill 内部逻辑并没有报错是请求在传输层被切断了。这排除了业务代码问题。第二步排查看 GKE 的 Ingress 配置发现默认超时确实是 30 秒。但奇怪的是我把超时调到 300 秒后问题依旧。第三步排查继续往上游看发现前面还有一层负载均衡它的空闲超时是 60 秒。但现象是 30 秒就断还是对不上。第四步排查最后在客户端侧发现客户端 SDK 自己有个 30 秒的默认超时而且它不读服务端的超时配置。这才是真正的元凶。修复方案客户端超时调到 600 秒服务端 Ingress 调到 300 秒负载均衡调到 120 秒三层对齐。同时把长任务改成异步模式彻底规避超时问题。这个案例的通用经验是超时问题一定要把链路上每一层的超时都列出来对齐任何一层短了都会成为瓶颈而且报错现象往往指向的不是真正的那一层。5.4 依赖版本漂移引发的昨天还好好的有一次线上 skill 突然开始报错代码一行没改。排查发现是基础镜像里的某个依赖库自动升级了新版本改了 API。这就是典型的依赖漂移。防御手段是锁定版本requirements.txt 里所有依赖都写死版本号基础镜像用带具体 tag 的而不是 latest。另外定期做依赖更新主动升级而不是被动挨打。我现在每个 skill 的依赖清单都会单独维护升级前先在测试环境跑一遍全量用例。6. 关于 skills 开发的一些个人体会Skills 这个东西入门容易精通难。写第一个能跑的 skill 可能半小时就够了但要写出一个触发准、执行稳、好维护的 skill需要反复打磨。我最大的体会是skill 的质量不取决于执行逻辑多精妙而取决于边界定义多清晰。一个只做一件事、触发条件明确、输入输出规范的笨 skill比一个啥都能干但边界模糊的聪明 skill 有用得多。这跟写函数是一个道理——单一职责原则在 Agent 时代依然成立。另外别指望一次写对。我的习惯是每个 skill 上线后持续收集两类数据误触发案例和漏触发案例每周复盘一次据此迭代触发描述。这个过程通常要持续两三轮skill 才会真正稳定下来。最后分享一个我常用的自检清单每次发布新 skill 前过一遍触发描述里有没有明确的正例和反例输入输出 schema 是不是足够严格异常路径有没有覆盖空输入、格式错误、依赖失败依赖版本有没有锁死长任务的超时链路有没有对齐有没有准备覆盖各类场景的测试用例这六条过完基本能避开八成以上的常见问题。剩下的两成就得靠实际跑起来慢慢磨了。