Agent Skills实战:从零搭建可扩展的AI智能体技能体系
1. 从“skills”这个热词说起它到底是什么为什么突然火了最近几个月不管是在技术社区、开发者群聊还是在做AI应用的朋友圈子里“skills”这个词出现的频率高得离谱。有人叫它 Agent Skills有人叫它 Claude Agent Skills还有人直接简称为 skills。热搜词里甚至出现了“今天学会了skills打开新世界”这种非常情绪化的表达。作为一个在AI应用开发一线摸爬滚打多年的人我一开始也以为这不过是又一个被炒起来的概念直到我自己动手把一套 skills 体系跑通、接进实际项目之后才意识到这东西确实值得认真聊一聊。先把话说清楚这里说的 skills不是指某个具体的软件或者某个平台的专属功能而是一种给AI Agent智能体扩展能力的方式。你可以把它理解成给一个通用的大脑装上一个个“技能插件”。一个没有 skills 的 Agent就像一个刚毕业的高材生脑子好使但什么具体活儿都不会干而给它挂上 skills 之后它就能查数据库、调API、生成特定格式的文档、执行一套固定的业务流程。核心价值就在这儿——把“通用智能”变成“可落地的专业能力”。那为什么是现在火我的判断是三个条件同时成熟了。第一大模型本身的能力到了一个临界点它能理解足够复杂的指令也能在多个步骤之间保持上下文第二Agent 框架开始标准化像 Google 的 Genkit、以及围绕 GKE 部署 Agent 的整套工具链让“挂载技能”这件事有了统一的接口第三真实业务场景的需求爆发了——大家不再满足于让AI聊聊天而是要它真正干活。这三股力量一叠加skills 自然就成了焦点。这篇文章适合谁看如果你是一个开发者想给自己的 Agent 加技能但不知道从哪下手如果你是一个技术负责人在评估要不要把 skills 体系引入团队或者你只是一个对AI应用感兴趣、想搞明白“这东西到底怎么用”的人那接下来的内容应该都能帮到你。我会从设计思路、核心细节、实操过程到问题排查一层层拆开讲尽量做到你看完就能照着做。2. 内容整体设计与思路拆解为什么是“技能化”这条路2.1 从“一个大模型打天下”到“技能组合”的思维转变早期做AI应用大家的思路很朴素找一个能力最强的大模型把需求写成 prompt然后祈祷它输出正确的结果。这个模式在简单场景下能用但一旦业务复杂起来就崩了。原因很简单——prompt 是有长度限制的而业务逻辑是无限的。你不可能把所有规则、所有数据格式、所有边界情况都塞进一段提示词里。skills 的思路完全不同。它把“能力”从 prompt 里抽出来变成一个个独立的、可复用的模块。每个 skill 负责一件事比如“查询订单状态”“生成周报”“调用地图接口算路线”。Agent 在运行时根据任务需要动态决定调用哪个 skill。这个转变的本质是从“单体应用”走向“微服务架构”——只不过服务的主体从代码变成了AI能力。我打个生活化的比方。以前的模式像是你雇了一个全能管家你得把所有家规、所有物品位置、所有办事流程一次性口头交代给他他记不住就出错。skills 模式则是你家里有一本操作手册每页写一个技能管家需要做什么就翻到对应那页照着做。手册可以随时增补管家也不用一次记住所有东西。2.2 方案选型为什么我最终选了 Genkit GKE 这套组合市面上能实现 skills 体系的方案不少有纯代码框架的有基于云服务的也有自己从头搭的。我前后试过三种路子最后稳定在Genkit 做技能编排 GKE 做部署运行这个组合上。说说我的选型逻辑。第一种路子是纯 prompt 工程用一个大 prompt 模拟多技能。这个方案上手最快但扩展性极差技能一多就互相干扰而且没法做真正的工具调用。我试过在一个项目里塞了七八个“伪技能”结果模型经常张冠李戴把A技能的规则用到B技能上。放弃。第二种是自己写调度层用代码判断该调哪个模型、该执行哪个函数。这个方案可控性最强但开发成本高每加一个技能都要改调度逻辑而且和模型能力的对接要自己处理维护起来很累。适合对性能有极致要求的场景但对大多数团队来说不划算。第三种就是 Genkit 这类框架。它把“定义技能”“注册技能”“让模型自主选择技能”这几件事标准化了。你只需要按照它的规范写 skill 的描述和实现剩下的编排、调用、上下文管理它帮你处理。再配合 GKE 部署技能可以独立扩缩容某个技能调用量大就多开几个实例不影响其他技能。这个组合的开发效率、可维护性、弹性三者平衡得最好。提示选型没有绝对的对错关键看你的团队规模和业务阶段。小团队快速验证用框架最划算大团队有历史包袱可能需要自研调度层。但无论哪种技能化的思路是一致的。2.3 技能粒度怎么定太粗和太细都是坑设计 skills 体系时最容易犯的错误是粒度没把握好。技能定得太粗比如一个 skill 叫“处理用户请求”那它内部逻辑会复杂到无法维护和写一个大函数没区别。技能定得太细比如“把字符串转成大写”也做成一个 skill那技能数量会爆炸Agent 选择技能的开销反而成了瓶颈。我的经验是一个 skill 对应一个完整的、有明确输入输出的业务动作。判断标准很简单如果这个动作在业务上是一个独立步骤有清晰的开始和结束那它就适合做成一个 skill。比如“根据用户ID查询最近三笔订单”就是一个好粒度“查询订单”太粗“查询订单表的第一列”太细。还有一个实操中的技巧先按最粗的粒度把流程跑通然后在实际使用中观察哪些环节经常出问题、哪些环节需要独立调整再把这些环节拆出来做成独立 skill。这是自下而上的演化式设计比一开始就追求完美粒度要靠谱得多。3. 核心细节解析与实操要点一个 skill 到底由什么组成3.1 技能描述决定 Agent 会不会用你的技能一个 skill 最核心的部分不是它的代码实现而是它的描述。这听起来反直觉但确实如此。因为 Agent 是靠描述来判断“当前任务该不该调用这个技能”的。描述写得不好技能实现得再完美也没用因为 Agent 根本不知道什么时候该用它。一个好的技能描述要包含三个要素做什么、什么时候用、输入输出是什么。我见过太多人只写了“做什么”结果 Agent 在错误的场景下调用它。举个例子一个“生成报告”的技能如果描述只写“生成报告”那 Agent 可能在用户只是想聊聊天的时候也去调它。正确的写法应该是“当用户明确要求生成周报、月报或项目总结且提供了必要的数据来源时调用此技能生成结构化报告”。描述的语言也有讲究。要用自然语言不要用代码注释那种简写。因为 Agent 理解的是自然语言你写gen_report(data)它不一定能准确理解场景。写成完整的句子把触发条件说清楚这比省那几个字重要得多。3.2 输入输出契约技能之间协作的基础skills 体系里技能不是孤立的它们经常需要串联。比如“查询订单”的输出会成为“生成对账单”的输入。这就要求每个技能的输入输出格式必须严格定义否则串联时就会出错。我的做法是用 JSON Schema 定义每个技能的输入输出。输入方面明确哪些字段是必填、哪些是可选、每个字段的类型和取值范围。输出方面同样定义清楚结构。这样做的好处是当 Agent 把技能A的输出传给技能B时如果格式不匹配系统能在调用前就发现并报错而不是等到执行到一半才崩。这里有个容易忽略的点错误输出也要定义。技能执行失败时返回什么是抛异常还是返回一个带错误码的结构我建议统一返回结构包含success字段和error字段。这样 Agent 能根据错误信息决定是重试、换技能还是向用户报告而不是直接卡死。3.3 技能实现把业务逻辑封装成可调用的单元技能的实现部分就是具体的代码逻辑。这部分相对直接但有几个实操要点值得强调。第一技能实现要无状态。也就是说同一个技能用同样的输入调用两次应该得到同样的结果除非业务本身依赖时间等外部因素。不要把状态存在技能内部状态应该由 Agent 的上下文管理。这样做的好处是技能可以水平扩展GKE 上多开几个实例不会有状态同步问题。第二技能要能独立测试。我习惯给每个 skill 写单元测试mock 掉外部依赖验证输入输出符合契约。这样在接入 Agent 之前就能保证技能本身是对的排查问题时也能快速定位是技能的问题还是编排的问题。第三超时和重试要处理好。技能调用外部服务时网络抖动是常态。设置合理的超时时间配合指数退避的重试策略。但要注意不是所有技能都适合重试——查询类技能重试没问题但“扣款”这种有副作用的技能重试可能导致重复扣款需要幂等设计。3.4 技能注册与发现让 Agent 知道有哪些技能可用技能写好了还得让 Agent 知道它们的存在。这就是技能注册环节。在 Genkit 这类框架里通常有一个统一的注册中心你把技能注册进去框架会自动把技能列表和描述提供给 Agent。这里有个规模问题当技能数量到几十上百个时把所有技能描述都塞给 Agent 会占用大量上下文而且会降低 Agent 选择的准确率。解决办法是技能分组。按业务域把技能分成若干组Agent 先选组再在组内选技能。或者用向量检索的方式根据当前任务语义匹配最相关的几个技能只把这些技能的描述提供给 Agent。这两种方式我都用过前者实现简单后者更灵活可以根据实际情况选。4. 实操过程与核心环节实现从零搭一套可用的 skills 体系4.1 环境准备与依赖安装假设你从零开始我按我的实际搭建过程走一遍。首先需要一个开发环境Node.js 是必须的因为 Genkit 的生态主要围绕 JS/TS。我用的版本是 Node 20 LTS太老的版本可能不支持某些新特性。node -v # 确认版本在 20 以上 npm install -g genkit-cli # 安装 Genkit 命令行工具然后初始化项目。我习惯用 TypeScript类型检查能在早期发现很多契约不匹配的问题。mkdir my-skills-project cd my-skills-project npm init -y npm install genkit genkit-ai/google-cloud npm install -D typescript ts-node types/node npx tsc --initGKE 那边需要提前准备好集群和 kubectl 配置。如果你只是本地验证可以先跳过 GKE用本地运行模式。等技能跑通了再上云。注意Genkit 的版本更新比较快安装时留意一下官方文档的版本对应关系。我有一次用了不匹配的版本技能注册一直报错排查了半天才发现是版本问题。4.2 定义第一个技能从最简单的开始我建议第一个技能选一个无外部依赖、逻辑简单的比如“格式化日期”或者“计算两个数的和”。目的是先把整条链路跑通确认注册、调用、返回都正常再逐步加复杂度。下面是一个技能定义的示例用 TypeScript 写import { defineTool } from genkit; export const calculateSum defineTool( { name: calculateSum, description: 当用户需要计算两个数字之和时调用此技能。输入两个数字返回它们的和。, inputSchema: { type: object, properties: { a: { type: number, description: 第一个加数 }, b: { type: number, description: 第二个加数 }, }, required: [a, b], }, outputSchema: { type: object, properties: { sum: { type: number, description: 两个数字的和 }, }, }, }, async (input) { return { sum: input.a input.b }; } );这段代码里description是给 Agent 看的inputSchema和outputSchema是契约最后的 async 函数是真正的实现。注意 description 里我明确写了“当用户需要计算两个数字之和时调用”这就是触发条件的说明。4.3 注册技能并接入 Agent定义好技能后需要把它注册到 Genkit 的运行时里然后配置一个 Agent 来使用这些技能。import { genkit } from genkit; import { googleAI } from genkit-ai/google-cloud; import { calculateSum } from ./skills/calculateSum; const ai genkit({ plugins: [googleAI()], model: googleai/gemini-pro, }); // 注册技能 ai.defineTool(calculateSum); // 定义一个使用技能的流程 export const agentFlow ai.defineFlow( { name: agentFlow, inputSchema: { type: string }, outputSchema: { type: string }, }, async (userInput) { const response await ai.generate({ prompt: userInput, tools: [calculateSum], }); return response.text; } );这里的关键是tools参数把技能传进去模型就能在需要时调用。Genkit 会自动处理“模型决定调用技能 - 执行技能 - 把结果返回给模型 - 模型生成最终回复”这个循环。4.4 本地测试与调试跑起来之后用 Genkit 的开发者界面来测试。启动命令genkit start -- ts-node index.ts它会启动一个本地服务打开浏览器就能看到界面。在界面里输入“帮我算一下 3 加 5 等于几”观察 Agent 是否调用了calculateSum技能返回结果是否正确。调试时重点看几个地方Agent 有没有选中正确的技能、输入参数是否符合 schema、技能返回后 Agent 有没有正确使用结果。如果 Agent 没调用技能八成是 description 写得不够清楚如果调用了但参数错了检查 inputSchema 的定义。4.5 部署到 GKE让技能真正跑在生产环境本地验证通过后就可以部署到 GKE 了。部署的核心是把技能服务打包成容器然后用 Kubernetes 的 Deployment 和 Service 管理。Dockerfile 大概长这样FROM node:20-slim WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY . . RUN npm run build EXPOSE 8080 CMD [node, dist/index.js]构建镜像并推送到镜像仓库然后写 Kubernetes 的部署配置。我一般会给技能服务配一个 HorizontalPodAutoscaler根据 CPU 使用率自动扩缩容。这样某个技能调用量突增时不会拖垮整个系统。apiVersion: apps/v1 kind: Deployment metadata: name: skills-service spec: replicas: 2 selector: matchLabels: app: skills-service template: metadata: labels: app: skills-service spec: containers: - name: skills-service image: your-registry/skills-service:latest ports: - containerPort: 8080 resources: requests: memory: 256Mi cpu: 250m limits: memory: 512Mi cpu: 500m提示GKE 上的技能服务建议开启健康检查liveness 和 readiness 都要配。我有一次没配 readinessPod 还没初始化完就被打流量进来导致一批请求失败。5. 常见问题与排查技巧实录我踩过的那些坑5.1 技能选择错误Agent 总是调错技能这是最常见的问题。表现是 Agent 在该调用技能A的时候调了技能B或者该调用的时候没调用。根本原因通常是技能描述之间的边界不清晰。排查方法把经常混淆的几个技能的 description 拿出来对比看是不是有重叠的表述。比如“查询用户信息”和“查询用户订单”如果都写了“查询用户相关数据”Agent 就容易混。解决办法是在描述里明确区分场景比如前者写“当需要获取用户的基本资料如姓名、邮箱时调用”后者写“当需要获取用户的购买记录时调用”。另一个技巧是在描述里加反例。比如“此技能不适用于查询订单查询订单请使用 queryOrder 技能”。这能显著降低误调率。5.2 参数传递失败schema 不匹配Agent 调用技能时传的参数不符合 inputSchema导致技能执行失败。常见原因是 schema 定义得太严格比如要求数字但 Agent 传了字符串。我的经验是在 schema 允许的范围内尽量宽松。比如数字类型可以接受字符串形式的数字在技能实现里做转换。另外给每个字段写清楚 descriptionAgent 会根据 description 来构造参数描述越清楚传参越准确。如果问题持续存在可以在技能实现里加一层参数校验和修正逻辑把常见的格式问题自动处理掉而不是直接报错。5.3 技能超时外部依赖拖慢整体响应技能调用外部API时如果对方响应慢整个 Agent 的响应就会被拖住。解决办法是设置合理的超时并且超时后要有降级策略。比如查询类技能超时可以返回缓存数据或者提示用户稍后重试而不是让整个流程挂起。在 GKE 上还可以通过配置 Pod 的 terminationGracePeriodSeconds 和就绪探针确保慢请求不会堆积。5.4 常见问题速查表问题现象可能原因排查方向解决办法Agent 不调用技能描述不清晰检查 description 是否说明触发条件补充场景说明和反例调用错误技能技能边界模糊对比多个技能的描述明确区分各自适用场景参数格式错误schema 过严查看实际传入参数放宽 schema增加转换逻辑技能执行超时外部依赖慢检查外部服务响应时间设超时加降级策略技能结果未被使用输出格式不符检查 outputSchema确保输出符合契约部署后技能不可用健康检查缺失查看 Pod 状态配置 liveness 和 readiness5.5 几个独家避坑技巧第一个技巧给技能加版本号。技能描述和实现都可能迭代加个版本号Agent 调用时能知道用的是哪个版本出问题也好回滚。我一般在技能名后面加_v1、_v2这样的后缀。第二个技巧记录技能调用日志。每次技能被调用记录下输入、输出、耗时、是否成功。这些日志在排查问题时是金矿。我用 GKE 的日志服务收集这些数据配合简单的查询就能定位大部分问题。第三个技巧定期审查技能使用率。有些技能可能从来不被调用说明描述有问题或者业务上不需要有些技能调用量异常高可能需要优化性能或者拆分。我每个月会看一次技能调用统计这能发现很多隐藏问题。6. 技能体系的扩展与演进从能用走向好用6.1 技能组合把多个技能串成工作流单个技能能做的事有限真正的威力在于组合。比如“处理退款”这个业务可能需要“查询订单”“验证退款条件”“执行退款”“发送通知”四个技能串联。Genkit 支持定义 flow把多个技能按顺序或条件组合起来。组合时要注意错误传播。如果中间某个技能失败了后续技能要不要执行我的做法是定义清楚每个步骤的失败策略是终止整个流程还是跳过继续还是走备用分支。这些策略要在 flow 定义时明确不能靠默认行为。6.2 技能市场与复用跨项目共享技能当团队里多个项目都需要类似技能时建一个内部技能库就很划算。把通用技能抽出来发布到内部仓库其他项目直接引用。这能大幅减少重复开发。技能复用的关键是接口稳定。一旦某个技能被多个项目依赖它的输入输出契约就不能随便改。要改就得走版本管理老版本继续保留新项目用新版本。这个规矩一开始就要立好否则后期会乱。6.3 性能优化让技能调用更快更稳技能调用的性能瓶颈通常在两个地方模型选择技能的时间和技能执行的时间。前者可以通过减少候选技能数量来优化后者可以通过缓存、并行调用、异步处理来优化。我做过一个优化把几个无依赖的技能改成并行调用整体响应时间从 3 秒降到了 1.2 秒。判断哪些技能可以并行很简单看它们之间有没有数据依赖没有就可以并行。6.4 安全与权限技能不能随便调技能能执行实际操作所以权限控制很重要。不是所有 Agent 都能调用所有技能。我的做法是给技能打标签Agent 配置里声明它能调用哪些标签的技能。比如“财务类”技能只有财务 Agent 能调“查询类”技能所有 Agent 都能调。另外涉及敏感操作的技能要加二次确认。比如“执行退款”技能Agent 调用后不是直接执行而是生成一个待确认的任务由人工确认后才真正执行。这在业务上是必须的技术上也不难实现。7. 我个人的一些实操体会这套 skills 体系我在两个项目里完整落地过一个是对内的运营自动化一个是对外的客服助手。最大的体会是技能化的价值不在于技术多先进而在于它让AI能力变得可管理、可迭代、可协作。以前改一个AI功能要动整个 prompt牵一发动全身现在改一个技能影响范围清晰可控测试也简单。另一个体会是不要追求一步到位。我第一个项目一开始设计了二十多个技能结果一半没用上还增加了 Agent 的选择负担。后来砍到八个核心技能效果反而更好。技能体系是长出来的不是设计出来的。先跑通最小闭环再根据实际需求慢慢加。最后分享一个小技巧给每个技能写一句“人话版”的说明放在团队文档里。不是给 Agent 看的是给团队成员看的。这样非技术的同事也能理解系统能做什么提需求时更准确减少很多沟通成本。这个习惯我坚持了很久收益远超预期。