
1. 从“skills”这个标题说起它到底指什么第一次看到“skills”这个标题很多人会以为是某个泛泛而谈的能力清单或者一份简历上的技能罗列。但结合热搜词里反复出现的 Google Cloud、Agent Skills、GKE、Genkit 这些关键词基本可以判断这里说的 skills 不是人类职场技能而是面向 AI Agent 的能力封装单元——也就是把一段可复用的逻辑、工具调用、提示词模板、外部 API 交互打包成一个标准化模块让 Agent 在需要的时候按需加载、按需执行。我最早接触这个概念是在做自动化工作流的时候。当时每个任务都要从头写一遍工具调用逻辑重复劳动特别多。后来发现如果把“查数据库”“发邮件”“生成报表”这些动作各自封装成独立的 skillAgent 就能像搭积木一样组合使用。这个思路和微服务架构很像每个 skill 只做一件事做好一件事然后通过统一的接口协议被调度。那 skills 到底能做什么简单说它解决的是Agent 能力复用和动态扩展的问题。没有 skills 的时候Agent 的能力边界在部署时就固定了有了 skills你可以在运行时动态挂载新能力不用重启整个系统。适合谁来参考如果你在做 AI Agent 开发、自动化流程编排、或者想把自己的工具链接入大模型生态那这套东西值得花时间研究。哪怕你只是用现成的 Agent 平台理解 skills 的运作机制也能帮你更好地调试和优化。2. 整体设计思路为什么是“技能包”而不是“大单体”2.1 核心思路拆解传统做法是把所有功能写在一个大提示词里或者把所有工具定义塞进一个配置文件。这种做法在功能少的时候没问题一旦超过十个工具提示词会变得极其臃肿模型选择工具的准确率也会下降。我实测过当工具数量超过十五个模型选错工具的概率会明显上升因为上下文里干扰信息太多了。Skills 的思路是按需加载。Agent 启动时只加载一个轻量的技能索引每个技能包含名称、描述、触发条件。当用户请求匹配到某个技能时再把该技能的完整定义和实现加载进来。这样做的好处有三个第一上下文窗口占用小模型注意力更集中第二技能可以独立开发、独立测试、独立部署第三不同团队可以并行开发不同技能最后通过标准接口组装。这个设计思路和 Google Cloud 上 Genkit 的工具体系是一脉相承的。Genkit 允许你把每个工具定义成独立的 flow然后通过插件机制注册。Agent Skills 在这个基础上更进一步把技能的生命周期管理也纳入进来——包括技能的发现、加载、执行、卸载。2.2 方案选型背后的考量为什么不用微调模型的方式来实现能力扩展因为微调成本高、周期长而且每次新增能力都要重新训练。Skills 方案是外挂式的新增能力只需要写一个符合规范的模块注册进去就能用。这对于快速迭代的场景来说灵活性高太多了。为什么不用简单的函数调用函数调用确实能实现类似效果但缺少标准化。每个项目的函数签名、参数格式、错误处理都不一样导致技能无法跨项目复用。Skills 定义了一套标准协议包括输入 schema、输出 schema、错误码、超时设置、重试策略。有了这套标准一个团队写的技能另一个团队可以直接拿来用。还有一个关键考量是安全性。Skills 可以设置权限边界比如某个技能只能读取特定目录、只能调用特定 API、只能访问特定数据库。这种细粒度的权限控制在大单体架构里很难做到。2.3 和 GKE 的关系热搜词里出现了 GKE这不是偶然的。Skills 的部署和调度天然适合容器化环境。每个 skill 可以打包成一个独立的容器镜像通过 GKE 进行编排。这样做的好处是资源隔离——一个 skill 崩溃不会影响其他 skill弹性伸缩——某个 skill 调用量大就多起几个实例版本管理——不同版本的 skill 可以灰度发布。我自己的做法是把每个 skill 做成一个轻量 HTTP 服务用 GKE 的 Deployment 管理通过 Service 暴露内部端点。Agent 运行时通过服务发现找到对应的 skill 端点发起调用。这套架构跑下来很稳而且扩容很方便。3. 核心细节解析一个 Skill 到底包含什么3.1 技能描述文件每个 skill 的核心是一个描述文件通常用 YAML 或 JSON 编写。这个文件定义了技能的元信息包括name技能唯一标识建议用蛇形命名比如query_user_profiledescription一句话说明技能做什么这句话会进入模型的上下文所以措辞要精准version语义化版本号方便管理兼容性input_schema输入参数的 JSON Schema 定义output_schema输出结果的 JSON Schema 定义trigger_conditions什么情况下应该触发这个技能可以用自然语言描述也可以用关键词列表timeout超时时间单位秒retry_policy重试策略包括最大重试次数和退避算法这里有个容易踩的坑description 写得太泛模型会频繁误触发写得太窄又该触发的时候不触发。我的经验是description 里要包含动作 对象 典型场景。比如“查询用户档案信息适用于需要获取用户姓名、邮箱、注册时间的场景”就比“用户相关操作”要好得多。3.2 输入输出 Schema 设计Schema 设计直接决定了技能好不好用。我见过很多技能因为 schema 设计不合理导致调用方要写大量适配代码。几个原则第一参数尽量扁平。嵌套层级不要超过两层否则模型在生成参数时容易出错。如果确实需要复杂结构考虑拆成多个技能。第二必填参数要少。必填参数越多模型调用失败的概率越高。能设默认值的就设默认值能从上下文推断的就不要显式传。第三输出结构要稳定。不管内部实现怎么变对外输出的字段名和类型要保持稳定。这样调用方不用跟着改。第四错误信息要结构化。不要只返回一个错误字符串要返回错误码、错误描述、可能的修复建议。这样 Agent 可以根据错误类型决定是重试、换技能、还是向用户求助。3.3 技能实现体的几种形态技能实现体可以是一段代码、一个 API 调用、一个数据库查询、甚至另一个 Agent。常见形态有本地函数用 Python、JavaScript 等语言写的函数直接在主进程里执行。适合轻量级、无外部依赖的操作。远程服务部署在独立服务里的 HTTP 端点。适合需要独立扩缩容、有外部依赖的操作。容器化任务打包成容器镜像按需启动。适合资源消耗大、执行时间长的操作。组合技能把多个原子技能编排成一个复合技能。适合业务流程复杂的场景。选哪种形态主要看执行时长、资源需求、依赖复杂度、复用频率。我一般先用本地函数快速验证验证通过后再根据实际负载决定要不要拆成远程服务。3.4 技能注册与发现机制技能写好了怎么让 Agent 知道这就需要注册与发现机制。常见做法有两种一种是静态注册在 Agent 启动时从配置文件或数据库读取所有可用技能的列表加载到内存里。这种方式简单直接但新增技能需要重启 Agent。另一种是动态发现Agent 运行时通过服务注册中心查询可用技能。新增技能只要注册到中心Agent 下次查询就能发现。这种方式更灵活但实现复杂度更高。我自己的项目用的是混合方案核心技能静态注册保证启动速度扩展技能动态发现保证灵活性。动态发现这块可以用 etcd、Consul 或者云厂商提供的服务发现组件。4. 实操过程从零搭建一个可用的 Skill4.1 环境准备与依赖安装先说一下我的环境Python 3.11Node.js 20Docker 24kubectl 1.28。这些版本不是必须的但建议不要太旧否则某些依赖会装不上。第一步创建一个项目目录结构如下my-skills/ skills/ query_user/ skill.yaml handler.py send_email/ skill.yaml handler.py registry/ registry.py agent/ main.py第二步安装核心依赖。我用的是 Genkit 的 Python SDK因为它对技能注册和调度的支持比较完善pip install genkit genkit-plugin-google-cloud如果你不用 Genkit也可以用 FastAPI 自己搭一套核心逻辑是一样的。4.2 编写第一个 Skill查询用户信息先写skills/query_user/skill.yamlname: query_user description: 根据用户ID查询用户档案返回姓名、邮箱、注册时间 version: 1.0.0 input_schema: type: object properties: user_id: type: string description: 用户唯一标识 required: - user_id output_schema: type: object properties: name: type: string email: type: string registered_at: type: string format: date-time timeout: 5 retry_policy: max_retries: 2 backoff: exponential然后写skills/query_user/handler.pyimport json from datetime import datetime def handle(input_data): user_id input_data[user_id] # 这里模拟数据库查询实际项目替换成真实查询 user { name: 张三, email: zhangsanexample.com, registered_at: 2024-01-15T08:30:00Z } if not user: return { error_code: USER_NOT_FOUND, error_message: f用户 {user_id} 不存在, suggestion: 请检查用户ID是否正确 } return user这里有个细节错误返回也走 output_schema但增加 error_code 字段。调用方先检查有没有 error_code有就按错误处理没有就按正常结果处理。4.3 注册与加载技能registry/registry.py负责扫描 skills 目录加载所有技能import os import yaml import importlib.util class SkillRegistry: def __init__(self, skills_dir): self.skills {} self.skills_dir skills_dir def load_all(self): for skill_name in os.listdir(self.skills_dir): skill_path os.path.join(self.skills_dir, skill_name) if not os.path.isdir(skill_path): continue with open(os.path.join(skill_path, skill.yaml)) as f: meta yaml.safe_load(f) handler_path os.path.join(skill_path, handler.py) spec importlib.util.spec_from_file_location( f{skill_name}_handler, handler_path ) module importlib.util.module_from_spec(spec) spec.loader.exec_module(module) self.skills[meta[name]] { meta: meta, handler: module.handle } return self.skills def get_skill(self, name): return self.skills.get(name)这段代码的关键点是动态导入。每个技能的 handler 是独立的 Python 文件通过 importlib 在运行时加载不需要提前 import。这样新增技能只要放对目录重启 Agent 就能生效。4.4 Agent 端调用技能agent/main.py里Agent 根据用户请求匹配技能并调用from registry.registry import SkillRegistry registry SkillRegistry(./skills) registry.load_all() def process_request(user_input): # 这里简化处理实际项目用模型做意图识别 if 查用户 in user_input: skill registry.get_skill(query_user) result skill[handler]({user_id: u_12345}) if error_code in result: return f查询失败{result[error_message]} return f用户{result[name]}邮箱{result[email]} return 暂不支持该操作实际项目中意图识别和参数提取交给模型来做。模型根据技能列表和用户输入输出要调用的技能名和参数然后 Agent 执行调用。这个流程就是标准的 Agent 工具调用循环。4.5 容器化部署到 GKE如果技能比较多建议容器化部署。每个技能一个 DockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD [python, server.py]server.py用 FastAPI 暴露 HTTP 端点from fastapi import FastAPI from handler import handle app FastAPI() app.post(/invoke) def invoke(payload: dict): return handle(payload)然后构建镜像、推送到镜像仓库、用 kubectl 部署到 GKEdocker build -t my-registry/query-user:1.0.0 . docker push my-registry/query-user:1.0.0 kubectl apply -f k8s/query-user-deployment.yamlK8s 的 Deployment 配置里副本数先设 2资源限制设 CPU 500m、内存 256Mi。等跑一段时间看监控数据再调整。5. 常见问题与排查技巧实录5.1 技能触发不准确怎么办这是最常见的问题。模型该调用技能的时候不调用不该调用的时候乱调用。排查思路先看 description 是不是太模糊。把 description 打印出来让不熟悉项目的人读一遍看能不能准确说出这个技能是干什么的。如果人说都说不清模型更分不清。再看技能数量是不是太多。如果同时注册了几十个技能模型的选择难度会指数级上升。解决办法是分层注册先注册一级分类技能模型选中分类后再加载该分类下的具体技能。还可以在提示词里加 few-shot 示例给模型展示几个“用户输入 → 技能选择”的样例。实测下来加三到五个高质量示例准确率能提升不少。5.2 技能执行超时怎么处理超时问题一般出在外部依赖上。比如查数据库慢、调第三方 API 慢。处理策略第一设置合理的超时时间。不要设太长否则 Agent 会卡住也不要设太短否则正常请求也会超时。我的经验值是数据库查询设 3 秒外部 API 设 10 秒复杂计算设 30 秒。第二实现重试机制。超时后自动重试但要用指数退避避免雪崩。第一次等 1 秒第二次等 2 秒第三次等 4 秒。第三提供降级方案。如果重试也失败返回一个兜底结果而不是直接报错。比如查询用户信息失败可以返回“暂时无法获取用户信息请稍后重试”。5.3 技能版本冲突怎么管理多个技能依赖同一个底层库的不同版本这是依赖管理的经典问题。我的做法是每个技能独立虚拟环境。容器化部署天然支持这一点每个技能镜像里装自己需要的版本互不干扰。如果是本地函数形态可以用 Python 的 venv 或者 conda 环境隔离。但这样管理起来比较麻烦技能多了之后环境切换很痛苦。所以我还是推荐容器化虽然前期麻烦一点但后期省心。5.4 常见问题速查表问题现象可能原因排查方法解决方案技能不被触发description 模糊人工阅读 description补充动作、对象、场景技能频繁误触发触发条件太宽查看调用日志收窄触发条件加负向示例执行超时外部依赖慢加日志看耗时分布设超时、加重试、加降级参数缺失schema 必填项太多检查调用日志减少必填项设默认值输出解析失败schema 不匹配对比实际输出和 schema修正 schema 或实现版本冲突依赖库版本不一致检查各技能依赖容器化隔离环境5.5 几个踩过的坑第一个坑技能名用驼峰命名。模型在生成技能名时有时候会写成蛇形有时候会写成驼峰导致匹配不上。后来统一用蛇形命名问题就少了。第二个坑错误信息暴露内部细节。早期版本直接把异常堆栈返回给模型结果模型看到堆栈里的文件路径和变量名开始胡乱猜测。后来改成只返回错误码和用户友好的描述模型的表现稳定多了。第三个坑技能没有幂等性。有些技能执行两次会产生副作用比如重复发邮件、重复扣款。后来给所有有副作用的技能加了幂等键调用方传一个唯一 ID技能内部根据 ID 去重。第四个坑忽略冷启动时间。容器化技能第一次调用要拉镜像、启动进程耗时可能好几秒。后来给常用技能设了最小副本数保持热实例冷启动问题就缓解了。6. 技能生态的扩展思路6.1 技能市场与共享机制当技能积累到一定数量自然会想到共享。我们团队内部搞了一个技能市场每个技能有独立的仓库、文档、测试用例。其他团队要用直接引用仓库地址在自己的 Agent 里注册就行。共享机制的关键是接口稳定性。技能一旦发布输入输出 schema 就不能随便改。要改就发大版本旧版本继续维护一段时间给调用方迁移的时间。6.2 技能组合与编排单个技能能力有限组合起来才能完成复杂任务。比如“生成月度报表”这个任务可以拆成查询数据 → 计算指标 → 生成图表 → 发送邮件。每个步骤是一个技能通过编排引擎串起来。编排可以用代码写死也可以用声明式配置。我倾向于声明式因为改起来方便不用重新部署。配置大概长这样workflow: monthly_report steps: - skill: query_data input: month: {{month}} - skill: calculate_metrics input: data: {{steps.query_data.output}} - skill: generate_chart input: metrics: {{steps.calculate_metrics.output}} - skill: send_email input: chart: {{steps.generate_chart.output}} recipient: {{recipient}}这种编排方式很直观非技术人员也能看懂。6.3 技能性能监控技能多了之后性能监控很重要。我一般监控这几个指标调用次数、平均耗时、P99 耗时、错误率、超时率。这些指标用 Prometheus 采集Grafana 展示。如果某个技能 P99 耗时突然飙升可能是外部依赖出问题了也可能是调用量突增导致资源不够。根据监控数据决定是扩容、优化代码、还是加缓存。6.4 技能安全审计技能能访问外部资源所以安全审计不能少。每个技能要明确声明它需要什么权限读哪些表、调哪些 API、访问哪些文件。部署的时候只授予声明的权限多一点都不给。审计日志也要记全谁在什么时候调用了哪个技能传了什么参数返回了什么结果。出了问题能追溯。7. 我个人的一些经验体会做技能化改造这段时间最大的感受是粒度控制比技术实现更难。技能拆得太细调用链太长性能和调试都成问题拆得太粗复用性差又回到了大单体的老路。我的经验是一个技能最好对应一个完整的业务动作而不是一个技术步骤。比如“查询用户信息”是一个完整的业务动作“连接数据库”就是一个技术步骤后者不应该单独做成技能。另一个体会是文档和测试要跟上。技能是给别人用的没有文档别人不知道怎么用没有测试别人不敢用。我们要求每个技能必须有 README、有单元测试、有集成测试缺一不可。前期觉得麻烦后期省了大量沟通成本。还有一点不要追求一步到位。我一开始想设计一套完美的技能协议结果拖了很久没落地。后来改成先跑通最小闭环用最简陋的方式实现然后根据实际使用中的痛点逐步迭代。现在这套协议已经改了五六个版本比最初设计的完善多了但如果没有最初那个简陋版本根本走不到今天。最后分享一个小技巧给每个技能加一个dry_run模式。调用时传dry_run: true技能只做参数校验和权限检查不实际执行。这个模式在调试和测试的时候特别有用能快速定位是参数问题还是执行问题。