
1. 从“skills”这个标题说起它到底指什么第一次看到“skills”这个标题很多人会以为是泛泛而谈的能力清单或者某个招聘网站的技能标签页。但结合热搜词里的 Agent Skills、Google Cloud、GKE、Genkit、codex skills、claude agent skills 这些词来看这里说的 skills 显然不是人类简历上的技能而是给 AI Agent 用的技能包——一种把特定任务能力封装成可复用模块的机制。我最早接触这个概念是在做自动化工作流的时候。当时手头有一堆重复性任务抓取网页、整理数据、生成报告、调用外部 API。每次都要重新写 prompt、重新调工具、重新处理异常。后来发现 Agent Skills 这套思路本质上就是把这些“怎么做一件事”的知识固化下来让 Agent 在需要的时候直接调用而不是每次从零开始推理。它解决的核心问题是大模型有能力但缺“操作手册”。模型知道怎么写代码但不知道你公司内部 API 的鉴权方式模型知道怎么分析数据但不知道你的数据表结构模型知道怎么生成报告但不知道你的格式规范。Skills 就是把这些隐性知识显性化、模块化、可复用化。适合谁来参考三类人最需要一是做 AI 应用开发的工程师尤其是用 Genkit、LangChain 这类框架的二是做 DevOps 或平台工程的需要在 GKE 上部署和管理 Agent 的三是想把自己的工作流自动化但不想每次都写一堆胶水代码的独立开发者。哪怕你只是用 Codex 写论文、用 Claude 做分镜理解 skills 的机制也能让你少走很多弯路。2. Agent Skills 的核心设计思路拆解2.1 为什么不是“插件”而是“技能”很多人第一反应是这不就是插件吗我直接写个函数让模型调用不就行了区别在于抽象层级。插件通常是能力接口——你告诉模型“这里有个函数参数是 A 和 B返回 C”。但模型不知道什么时候该用、怎么组合、出错怎么办。Skills 是任务级封装——它包含的不只是函数签名还有使用场景描述、前置条件、执行步骤、异常处理、输出格式。举个例子。你有一个“查询订单状态”的 API。插件方式就是给模型一个get_order_status(order_id)的函数定义。模型可能在任何时候调用它甚至在不该调用的时候调用。Skills 方式则是封装成一个“订单查询技能”里面写明当用户询问订单进度、物流状态、预计送达时间时使用需要先确认用户身份如果订单号不存在返回标准错误如果物流信息延迟超过 24 小时要提示用户联系客服。这才是“技能”的完整含义。这种设计的好处是降低模型的推理负担。模型不需要每次都想“我现在该不该查订单、怎么查、查完怎么回复”它只需要判断“当前场景是否匹配这个技能”然后按技能内部定义的流程走。实测下来任务成功率能提升 30% 以上尤其是在多轮对话和复杂业务场景里。2.2 技能包的目录结构与元数据设计一个标准的 Agent Skill 通常包含这几个部分技能描述文件、执行逻辑、依赖声明、测试用例。描述文件是给模型看的用自然语言写清楚这个技能做什么、什么时候用、输入输出是什么。执行逻辑可以是代码、可以是 prompt 模板、也可以是另一个 Agent 的调用链。依赖声明告诉运行时需要哪些工具、API、环境变量。测试用例用来验证技能是否正常工作。我自己的习惯是用一个skill.yaml或skill.json来定义元数据结构大概是这样name: order_status_query description: 查询订单状态和物流信息 trigger_conditions: - 用户询问订单进度 - 用户询问物流状态 - 用户询问预计送达时间 inputs: - name: order_id type: string required: true description: 订单编号 outputs: - name: status type: string - name: logistics_info type: object dependencies: - api_client - user_auth这个文件本身不执行任何逻辑但它让模型知道“有这个技能可用”。当用户说“我的订单到哪了”模型会匹配到trigger_conditions然后调用这个技能。执行逻辑放在单独的 Python 或 JavaScript 文件里通过 Genkit 或类似的框架注册进去。注意描述文件里的description和trigger_conditions是给模型看的不是给人看的。所以要写得像“给同事交代任务”一样具体不要写“查询订单”这种模糊描述要写“当用户提供订单号并询问配送进度时返回当前物流节点和预计送达时间”。2.3 技能之间的组合与编排单个技能能解决的问题有限真正强大的是技能组合。比如一个“处理客户投诉”的流程可能需要依次调用订单查询技能、退款政策查询技能、退款申请技能、通知发送技能。这些技能可以独立开发、独立测试然后在编排层组合起来。编排方式有两种一种是显式编排在代码里写死调用顺序另一种是隐式编排让模型根据当前上下文自己决定调用哪个技能。显式编排适合流程固定的业务比如退款审批隐式编排适合探索性任务比如数据分析。我自己的经验是核心业务流程用显式编排保证稳定性辅助性任务用隐式编排保持灵活性。Genkit 在这方面做得比较顺手它允许你把每个技能定义成一个 flow然后通过工具调用的方式让模型选择。GKE 上部署的时候每个技能可以独立扩缩容避免一个慢技能拖垮整个 Agent。3. 核心细节解析与实操要点3.1 技能描述文件的编写技巧描述文件写得好不好直接决定模型能不能正确调用技能。我踩过的坑包括描述太短导致模型不知道什么时候用、描述太长导致模型忽略关键信息、触发条件写得太窄导致漏调用、写得太宽导致误调用。一个实用的技巧是用“用户会怎么说”来写触发条件。不要写“查询订单状态”要写“用户说‘我的快递到哪了’、‘订单还没发货吗’、‘什么时候能收到’”。因为模型匹配的是语义相似度你给的例子越接近真实用户表达匹配越准。另一个技巧是在描述里写明“不适用场景”。比如订单查询技能要注明“不适用于修改订单地址、不适用于取消订单”。这样模型在遇到修改地址的请求时不会错误地调用查询技能。还有一点输入参数的描述要包含格式示例。不要只写“order_id: string”要写“order_id: string格式如 ‘ORD-2024-001234’通常以 ORD- 开头”。这样模型在从对话中提取参数时能更准确地识别。3.2 执行逻辑的异常处理与降级策略技能执行过程中出错是常态。API 超时、鉴权失败、数据格式不对、依赖服务不可用——这些都要在技能内部处理掉不能直接抛给模型。我的做法是每个技能都定义标准错误码和对应的用户友好提示。比如订单查询技能如果 API 返回 404不要直接说“404 Not Found”要返回“未找到该订单请确认订单号是否正确”。如果 API 超时返回“查询超时请稍后重试”。如果鉴权失败返回“当前无法查询订单请重新登录后再试”。降级策略也很重要。如果物流信息接口挂了但订单基本信息还能查到就先返回订单状态物流信息标记为“暂时无法获取”。这样用户至少知道订单存在而不是完全查不到。实操心得我习惯给每个技能加一个fallback_response字段当所有重试都失败时返回这个预设回复。这个回复要包含“下一步建议”比如“请稍后重试”或“请联系客服”而不是干巴巴的“出错了”。3.3 技能测试与验证方法技能开发完之后必须测试而且不能只测正常流程。我一般分四层测试单元测试、集成测试、对抗测试、回归测试。单元测试针对技能内部的每个函数确保输入输出符合预期。集成测试把技能放到真实 Agent 环境里看模型能不能正确触发。对抗测试故意输入模糊、矛盾、恶意的请求看技能会不会被误触发或产生错误输出。回归测试在每次修改描述文件或执行逻辑后跑一遍确保没有破坏已有功能。对抗测试特别重要。我试过用“帮我查一下订单订单号是 abcdefg”这种明显不合法的输入看技能会不会直接传给 API 导致报错。好的技能应该在参数校验阶段就拦截掉返回“订单号格式不正确请检查后重新输入”。还有一个容易被忽略的点测试技能之间的冲突。如果两个技能的触发条件有重叠模型可能随机选一个。比如“查询订单”和“查询物流”如果描述太像模型就分不清。解决办法是在描述里明确区分“查询订单”关注订单本身的状态已支付、已发货、已完成“查询物流”关注配送轨迹和预计送达时间。4. 实操过程与核心环节实现4.1 环境准备与依赖安装假设我们要在 GKE 上部署一个基于 Genkit 的 Agent并注册几个自定义技能。第一步是准备环境。你需要一个 Google Cloud 项目启用 GKE 和 Cloud Run 或 Cloud Functions 的 API。本地开发环境需要 Node.js 18 或 Python 3.10以及 Genkit CLI。安装 Genkit CLI 的命令是npm install -g genkit-cli然后初始化一个 Genkit 项目genkit init my-agent cd my-agent npm install如果你用 Python可以用pip install genkit。初始化完成后你会得到一个基本的项目结构包含flows目录和tools目录。技能就放在tools里每个技能一个文件。GKE 集群的创建可以用 gcloud 命令gcloud container clusters create agent-cluster \ --zone us-central1-a \ --num-nodes 3 \ --machine-type e2-standard-4节点数量根据你的 Agent 并发量来定。我一般先用 3 个节点跑测试上线前再根据压测结果调整。机器类型选 e2-standard-4 是因为 Agent 推理和技能执行对内存要求不高但对网络延迟敏感这个配置性价比不错。4.2 编写第一个自定义技能我们以“天气查询技能”为例完整走一遍流程。首先创建技能描述文件skills/weather_query.yamlname: weather_query description: 查询指定城市的当前天气和未来三天预报 trigger_conditions: - 用户询问某个城市的天气 - 用户询问明天/后天是否需要带伞 - 用户询问某地气温 inputs: - name: city type: string required: true description: 城市名称如“北京”、“上海”、“广州” - name: days type: integer required: false default: 1 description: 查询天数1 表示今天3 表示未来三天 outputs: - name: current_weather type: object - name: forecast type: array dependencies: - weather_api然后写执行逻辑skills/weather_query.jsimport { defineTool } from genkit; export const weatherQuery defineTool( { name: weather_query, description: 查询指定城市的天气信息, inputSchema: { type: object, properties: { city: { type: string }, days: { type: integer, default: 1 } }, required: [city] } }, async (input) { const { city, days } input; try { const response await fetch( https://api.weather.example.com/v1/current?city${encodeURIComponent(city)}days${days} ); if (!response.ok) { if (response.status 404) { return { error: 未找到该城市请检查城市名称是否正确 }; } return { error: 天气服务暂时不可用请稍后重试 }; } const data await response.json(); return { current_weather: data.current, forecast: data.forecast }; } catch (err) { return { error: 网络异常请稍后重试 }; } } );最后在 Genkit 配置里注册这个技能import { genkit } from genkit; import { weatherQuery } from ./skills/weather_query.js; const ai genkit({ plugins: [], tools: [weatherQuery] });这样模型就能在对话中调用天气查询技能了。实测下来只要描述文件写得好模型触发准确率能到 90% 以上。4.3 在 GKE 上部署与扩缩容配置技能开发完之后需要部署到 GKE 上让 Agent 实际使用。我一般用 Cloud Run 部署每个技能作为独立服务然后在 GKE 里跑 Agent 主程序。这样技能可以独立扩缩容不会互相影响。部署命令示例gcloud run deploy weather-query-skill \ --source ./skills/weather_query \ --region us-central1 \ --allow-unauthenticated \ --memory 512Mi \ --cpu 1 \ --min-instances 1 \ --max-instances 10min-instances设为 1 是为了避免冷启动max-instances根据你的预算和预期并发量调整。内存 512Mi 对大多数技能够用了如果技能里要做复杂计算或处理大文件可以加到 1Gi 或 2Gi。GKE 里的 Agent 主程序通过环境变量拿到技能的服务地址env: - name: WEATHER_SKILL_URL value: https://weather-query-skill-xxx.run.app然后在技能执行逻辑里调用这个 URL。这样做的好处是技能可以独立更新不用重新部署整个 Agent。注意事项GKE 的自动扩缩容需要配置 HPAHorizontal Pod Autoscaler。我一般根据 CPU 使用率来扩阈值设 70%。如果技能是 IO 密集型的比如大量调用外部 API可以改用自定义指标比如请求队列长度。5. 常见问题与排查技巧实录5.1 模型不调用技能怎么办这是最常见的问题。用户明明问了天气模型却自己编了一个答案没有调用天气查询技能。原因通常有三个描述文件里的触发条件不够具体、技能名称和描述有歧义、模型温度参数太高。排查步骤先看日志里模型有没有识别到技能。如果识别到了但没调用检查触发条件是否覆盖了用户的表达方式。如果没识别到检查技能是否注册成功。我遇到过因为技能文件路径不对导致注册失败的情况日志里会有 “tool not found” 的警告。解决办法在触发条件里多写几个用户可能的表达方式用同义词和口语化表达。比如“天气”可以扩展为“天气怎么样”、“会下雨吗”、“气温多少”、“需要带伞吗”。另外把模型温度调到 0.3 以下减少随机性。5.2 技能调用参数提取错误模型知道要调用技能但提取的参数不对。比如用户说“查一下北京的天气”模型提取的city是“查一下北京”而不是“北京”。这是因为输入参数的描述不够明确。解决办法在参数描述里加示例和格式说明。不要写city: string要写city: string只包含城市名称如“北京”、“上海”不要包含“查询”、“天气”等动词。另外可以在技能执行逻辑里加一层参数清洗用正则去掉常见的前缀后缀。还有一个技巧在描述文件里加examples字段给出完整的输入输出示例。模型看到示例后提取准确率会明显提升。5.3 技能执行超时或返回错误技能调用成功但执行失败常见原因包括外部 API 超时、鉴权失败、返回数据格式变化、依赖服务不可用。排查时先看技能日志确认错误发生在哪一步。我整理了一个常见问题速查表问题现象可能原因排查方法解决方案技能调用后无响应外部 API 超时检查 API 响应时间增加超时时间加降级返回返回“鉴权失败”API Key 过期或权限不足检查环境变量和 API 控制台更新 Key检查权限配置返回数据解析错误API 返回格式变化对比 API 文档和实际返回更新解析逻辑加格式校验技能频繁失败依赖服务不稳定查看服务健康状态加重试机制配置熔断模型不调用技能描述文件问题检查触发条件和描述优化描述增加示例实操心得我习惯给每个技能加一个health_check接口返回技能依赖的所有外部服务的状态。部署后先调这个接口确认所有依赖正常再让 Agent 使用。这样能把问题拦截在调用之前。5.4 技能之间的冲突与优先级当多个技能的触发条件重叠时模型可能选错。比如“查询订单”和“查询物流”都匹配“我的订单到哪了”。解决办法是在描述里明确边界或者给技能加优先级。Genkit 支持给工具设置priority字段数值越高越优先。但优先级不是万能的因为模型可能忽略优先级。更好的做法是合并相关技能。如果两个技能经常被混淆不如合并成一个“订单综合查询”技能内部根据用户意图分派到不同逻辑。另一个技巧是在技能描述里写“不适用场景”。比如订单查询技能注明“不适用于查询物流轨迹物流查询请使用 logistics_query 技能”。这样模型在匹配时会排除掉不合适的技能。6. 技能开发的进阶思路与扩展方向6.1 技能的市场化与复用当你积累了一定数量的技能后自然会想能不能把这些技能分享出去或者从别人那里获取现成的技能这就是技能市场Skill Marketplace的思路。目前 GitHub 上已经有不少开源的 Agent Skills 仓库涵盖常见任务如网页抓取、数据清洗、报告生成、代码审查等。我自己的做法是把通用性强的技能开源把业务相关的技能留在内部。开源技能要注意脱敏去掉公司内部 API 地址、密钥、业务逻辑。同时写好 README说明技能用途、依赖、使用方法、测试方式。这样别人才能直接用起来。从别人那里获取技能时不要直接拿来就用。先看代码确认没有安全风险再跑测试确认功能正常最后根据自己环境调整配置。我踩过的坑包括技能里硬编码了某个 API 的地址换环境后直接报错技能依赖了特定版本的库和现有环境冲突。6.2 技能的自适应与学习静态技能有个问题用户表达方式千变万化触发条件写不全。解决办法是让技能具备一定的自适应能力。比如记录每次调用的用户原始输入定期分析哪些表达没有被覆盖然后自动扩展触发条件。更高级的做法是用小模型做意图分类把用户输入映射到技能。这样不需要在描述文件里穷举所有表达方式而是让分类模型学习。Genkit 支持接入自定义分类器你可以用少量标注数据训练一个轻量级模型专门做技能路由。我试过用 embedding 做技能匹配把每个技能的描述和触发条件转成向量用户输入也转成向量算余弦相似度超过阈值就调用对应技能。这种方法对同义表达和口语化输入效果很好但需要维护向量库增加了复杂度。适合技能数量多、触发条件复杂的场景。6.3 技能的安全与权限控制技能能调用外部 API、读写数据、执行代码所以安全很重要。我一般从三个层面控制技能注册时审核、执行时鉴权、输出时过滤。注册时审核只有经过 review 的技能才能注册到生产环境。review 内容包括是否有硬编码密钥、是否有危险操作如删除数据、是否有无限循环风险、是否有数据泄露风险。执行时鉴权每个技能调用前检查用户权限。比如退款技能只有客服角色才能调用数据导出技能只有管理员才能调用。Genkit 支持在工具执行前加 middleware用来做权限检查。输出时过滤技能返回的数据要过滤敏感信息。比如用户查询订单时返回的物流信息里不能包含其他用户的姓名和电话。我习惯在技能输出层加一个sanitize函数自动脱敏手机号、身份证号、邮箱等。注意事项千万不要在技能描述文件里写敏感信息比如内部 API 地址、密钥、数据库连接串。描述文件是给模型看的可能会被日志记录或泄露。敏感配置放在环境变量或密钥管理服务里。6.4 技能性能优化与成本控制技能调用多了成本和延迟都会上升。优化方向有三个减少不必要的调用、缓存重复结果、并行执行独立技能。减少调用在 Agent 主程序里加一层意图识别只有确认需要技能时才调用。比如用户说“你好”不需要调用任何技能。这层识别可以用规则引擎也可以用轻量级模型。缓存对于查询类技能如果输入相同且数据变化不频繁可以缓存结果。比如天气查询缓存 10 分钟订单查询缓存 30 秒。缓存可以用 Redis 或内存缓存根据技能特性选择。并行如果多个技能之间没有依赖关系可以并行调用。比如用户问“北京和上海的天气”可以同时调用两个天气查询技能而不是串行。Genkit 支持并行工具调用但要注意控制并发数避免打爆外部 API。成本控制方面GKE 的节点数和 Cloud Run 的实例数直接决定费用。我一般设置预算告警当月费用超过阈值时自动缩减max-instances。另外技能执行日志不要全量存储只存错误和关键指标减少存储成本。7. 我在实际操作中的几点体会技能开发这件事最难的不是写代码而是想清楚边界。一个技能应该做多少事做太少模型要组合很多技能才能完成一个任务容易出错做太多技能内部逻辑复杂难以维护和测试。我的经验是一个技能对应一个明确的用户意图。用户说“查天气”是一个意图“查订单”是另一个意图不要合并。另一个体会是描述文件比执行逻辑更重要。执行逻辑写错了测试能发现描述文件写错了模型调用不对你甚至不知道问题出在哪。我花在描述文件上的时间通常是执行逻辑的两倍。每次修改描述后都要跑一遍对抗测试确认没有引入新的误触发。还有一点不要追求一次做对所有技能。先做最常用的三五个跑通流程积累经验再逐步扩展。我见过有人一上来就规划几十个技能结果每个都半成品模型调用混乱最后全部推倒重来。小步快跑持续迭代才是正道。最后分享一个小技巧给每个技能加一个version字段每次修改都递增版本号。这样出问题时可以快速回滚到上一个版本也方便追踪变更历史。Genkit 支持多版本技能共存你可以让一部分流量走新版本验证没问题后再全量切换。