1. “skills”不是功能模块而是智能体能力的最小可执行单元最近两周我在三个不同客户的AI工程落地项目里反复被问到同一个问题“你们说的skills到底是什么跟function calling、tool use、agent action有什么区别”一开始我下意识想用“就是调用外部API的能力封装”来回答结果客户工程师盯着我问“那为什么Genkit文档里把skills和model binding并列写在架构图顶层为什么Google Cloud的Agent Platform控制台里skills要单独建目录、设权限、配版本号”——那一刻我意识到我们业内很多人嘴上天天说skills其实连它在现代智能体架构里的真实定位都没理清。skills这个词在2024年已经彻底脱离了“技能”这个生活化语义演变成一个严格的技术概念它是智能体Agent对外暴露的、具备完整输入校验、错误兜底、可观测性埋点、且能独立灰度发布的最小可执行能力单元。它不是一段Python函数不是个HTTP接口封装更不是prompt engineering的产物。它是一套契约——Agent Runtime承诺“只要按这个schema传参我就保证返回这个结构的结果失败时一定走这个error path”而下游系统比如前端、调度器、审计平台只认这个契约不关心内部是调OpenAI API、查数据库、还是跑本地Python脚本。你翻遍Gemini API文档找不到“skills”这个词Claude的开发者指南里它叫“tools”LangChain管它叫“toolkits”但Genkit和Google Cloud Agent Platform明确把它升格为一级公民原因很现实当一个企业级Agent要接入37个内部系统HR、CRM、ERP、BI、工单、知识库、邮件网关……如果每个对接都靠硬编码function calling运维会疯掉。skills就是那个让“接入新系统”从“改代码发版回滚预案”变成“上传yaml点发布看监控”的关键抽象层。提示别被“skills”字面意思带偏。它和“前端开发skills”“superpower skills”这些热搜词毫无技术关联。那些是内容平台的流量标签而本文讨论的是Genkit SDK里defineSkill()函数生成的对象是Google Cloud Console里Skills Registry里的一条记录是Agent Platform API/v1/skills/{name}:invoke的请求目标。我见过最典型的误解是团队把一个封装了天气查询的Python函数直接注册成skills结果上线三天就被打挂——因为没做rate limiting配置没定义timeout没加fallback逻辑更没埋trace_id。用户一句“查北京天气”触发的不是能力是雪崩。真正的skills必须自带四件套输入schemaJSON Schema、执行逻辑async function、失败策略retry/backoff/fallback、可观测字段span name, attributes, metrics。这四件套缺一不可否则它就只是个裸函数不是skills。这也解释了为什么“skills下载平台”“skills安装包”这类搜索词会突然爆发——大家开始意识到skills是可以像npm包一样复用、共享、版本管理的。一个经过生产验证的“飞书消息发送skills”比十个自己写的curl封装可靠十倍。但问题来了怎么判断一个skills是否真的“生产就绪”下一部分我们就拆解它的骨架。2. 解剖一个合格skills的四大核心构件从schema到可观测性一个能在Genkit或Google Cloud Agent Platform上稳定运行的skills绝不是写个async函数再export default就完事。它必须通过四个维度的严格校验缺一不可。我把它们称为skills的“四梁八柱”每根梁柱都有明确的技术规范和实操陷阱。2.1 输入Schema不是可选的类型声明而是能力边界的法律契约很多团队把JSON Schema当成可有可无的文档注释这是大忌。在Genkit中skills的input schema直接决定Agent Runtime如何解析用户query、如何做参数提取、如何生成fallback prompt。如果你的schema写成{ type: object, properties: { city: { type: string } } }Runtime就会认为“city”是必填字段用户说“查天气”没提城市它要么报错要么用空字符串调用你的逻辑——后者往往导致下游API返回500。而正确的做法是用JSON Schema精准表达业务意图{ type: object, required: [city], properties: { city: { type: string, minLength: 2, maxLength: 20, pattern: ^[\\u4e00-\\u9fa5a-zA-Z0-9\\s\\-]$ }, unit: { type: string, enum: [celsius, fahrenheit], default: celsius } } }看到没required明确定义必填项minLength/maxLength防注入攻击pattern限制字符集避免乱码enum约束枚举值default提供安全兜底。这已经不是类型检查而是第一道安全网。我有个客户曾因schema没加maxLength用户输入超长城市名实际是base64编码的恶意payload导致下游天气API解析超时整个Agent线程卡死。注意Genkit的defineSkill()对schema有严格校验。如果你传入的schema不符合JSON Schema Draft-07规范SDK会在genkit build阶段直接报错不会让你走到部署环节。这不是bug是设计——强制你在编译期就厘清能力边界。2.2 执行逻辑异步、幂等、带上下文的纯函数skills的执行函数必须是async的且必须满足三个隐性要求幂等性、上下文隔离、副作用可控。幂等性同一组输入参数多次调用必须返回相同结果或相同错误。这意味着不能依赖全局变量、不能读取未声明的外部状态。例如一个“查询订单状态”的skills如果内部偷偷读取了内存缓存但没声明cache key第二次调用可能返回过期数据。正确做法是把缓存逻辑显式写进函数体用input.orderId作为key。上下文隔离每个skills调用必须拥有独立的execution context。Genkit会自动注入context对象里面包含logger、tracer、config等。你必须用context.logger.info()而不是console.log()用context.tracer.startSpan()而不是自己new Trace。为什么因为Agent Platform的全链路追踪依赖这个context。我见过团队自己实现logger结果所有skills日志在Cloud Logging里散落各处根本无法关联到一次用户对话。副作用可控skills可以调用外部API、读数据库、发消息但必须明确声明。Genkit的defineSkill()支持sideEffects选项用于标记该skills是否修改状态如“创建工单”是true“查天气”是false。Platform会据此决定是否允许并发调用、是否需要事务补偿。忽略这个标记可能导致高并发下数据错乱。实操中我建议所有skills函数体遵循这个模板export const weatherSkill defineSkill({ name: getWeather, inputSchema: weatherInputSchema, outputSchema: weatherOutputSchema, // 显式声明副作用让Platform知道如何调度 sideEffects: false, execute: async (input, context) { // 1. 参数预处理如城市名标准化 const normalizedCity normalizeCityName(input.city); // 2. 调用下游服务带超时和重试 try { const response await fetch( https://api.weather.com/v3/weather/forecast/daily?city${normalizedCity}unit${input.unit}, { signal: AbortSignal.timeout(5000), // 强制5秒超时 headers: { Authorization: Bearer ${context.config.weatherApiKey} } } ); if (!response.ok) { throw new Error(Weather API error: ${response.status}); } const data await response.json(); return transformWeatherData(data); // 标准化输出结构 } catch (error) { // 3. 统一错误处理返回Platform可识别的error code context.logger.error(Weather API call failed, { error, input }); throw new SkillError(WEATHER_API_UNAVAILABLE, Weather service is temporarily down); } } });这个模板里AbortSignal.timeout(5000)是救命稻草——没有它一个慢响应的天气API就能拖垮整个Agent。而SkillError是Genkit内置的错误类型Platform能自动识别并触发fallback流程。裸抛Error对不起Platform只会当未知异常处理可能直接中断对话。2.3 失败策略不是try-catch而是面向业务的弹性设计skills的失败不是程序崩溃而是业务场景的正常分支。一个合格的skills必须预设三种失败模式并给出对应策略失败类型触发条件Platform默认行为推荐skills内策略瞬时故障Network timeout, 5xx下游服务临时不可用自动重试3次指数退避在execute内用retry库封装设置maxRetries2, baseDelay100ms业务拒绝400, 404, 422用户输入非法或资源不存在直接返回error给LLM由其生成友好提示在execute内捕获并转换为SkillErrorcode设为INVALID_INPUT永久失败401, 403凭据失效或权限不足停止重试标记skills为unhealthy在execute内捕获记录告警返回SkillErrorwithAUTH_FAILED关键点在于Platform的重试机制只对瞬时故障生效且重试发生在skills调用层面不是函数内部。如果你在execute里自己写了重试逻辑又开启Platform重试就会变成“重试套重试”放大雪崩风险。正确姿势是skills内只处理业务逻辑层的失败如参数校验、业务规则把网络层失败交给Platform统一治理。我有个血泪教训一个“发送企业微信消息”的skills内部自己实现了3次重试结果遇到网络抖动Platform又重试2次最终一条用户指令触发了6次消息发送客户投诉炸了。后来我们重构为skills内只做一次调用失败时抛出SkillErrorPlatform根据error code决定是否重试。现在稳定性从99.2%提升到99.98%。2.4 可观测性不是加log而是埋点即契约skills的可观测性不是“出了问题再看日志”而是“每个调用都自动生成可分析的数据”。Genkit强制要求skills上报四个核心指标skill.invocation.count总调用次数按skills name status分组skill.latency.ms执行耗时p50/p90/p99按skills name分组skill.error.rate错误率按skills name error_code分组skill.spanOpenTelemetry span包含skill.name、input.hash、output.size、error.code等attributes这些数据不是可选项。当你在Google Cloud Console的Skills Registry里点击一个skills看到的实时QPS图、延迟热力图、错误分布饼图全部来自这些埋点。没有它们skills就像黑盒你永远不知道它是快是慢、错在哪、谁在用。实操中最大的坑是手动拼接log字符串。比如// ❌ 错误示范log字符串无法被结构化解析 console.log([getWeather] city${input.city} statussuccess latency${Date.now()-start}ms); // ✅ 正确示范用context.logger结构化上报 context.logger.info(Weather query completed, { city: input.city, unit: input.unit, latencyMs: Date.now() - start, statusCode: 200 });前者在Cloud Logging里只是一行文本搜索city北京要正则匹配后者是结构化JSON可以直接用resource.typecloud_run_revisionjsonPayload.city北京精准过滤。差的不是写法是思维——skills的log不是给人看的是给机器分析的。3. Genkit与Google Cloud Agent Platform的skills协同机制不是部署而是注册-绑定-调度很多团队以为把skills代码push到GitHub再在Cloud Console点“Deploy”就万事大吉。实际上skills在Genkit和Agent Platform之间有一套精密的协同协议理解它才能避免“本地跑通线上报错”的经典困境。3.1 注册Registrationskills的“户口本”诞生时刻skills注册不是上传代码而是向Platform提交一份能力声明Capability Manifest。这个manifest由Genkit CLI在genkit deploy时自动生成核心内容包括name: skills唯一标识如weather.getForecast必须符合DNS命名规范小写字母、数字、短横线version: 语义化版本号如1.2.0Platform据此做灰度发布inputSchemaoutputSchema: JSON Schema字符串Platform用它做runtime参数校验metadata: 描述、作者、标签用于skills市场搜索endpoints: 实际执行端点如Cloud Run URLPlatform用它做负载均衡关键点在于注册时Platform只存manifest不存代码。你的skills逻辑依然在自己的Cloud Run服务里。Platform做的是建立“skills name → Cloud Run endpoint”的映射关系并加载schema做校验。这意味着你可以用任何语言Python/Go/Node.js实现skills只要它遵守Genkit的HTTP协议规范。我见过最蠢的错误是团队把skills代码直接打包进Agent镜像。结果每次更新skills都要重建Agent镜像、重新部署——完全违背了skills“解耦复用”的设计初衷。正确姿势是skills作为独立服务部署推荐Cloud RunAgent只通过Platform的skills registry调用它。这样更新天气skills只需gcloud run deployAgent零改动。3.2 绑定BindingAgent如何“认识”并信任一个skillsAgent不会自动发现skills。它必须通过binding显式声明依赖哪些skills。在Genkit中这通过bindSkills()完成const agent defineAgent({ model: geminiPro, skills: bindSkills([ weatherSkill, // 来自本地定义 https://us-central1-my-project.cloudfunctions.net/sendEmail, // 来自外部URL projects/my-project/skills/email.send // 来自Platform Registry ]) });这里出现三种绑定方式代表不同信任层级本地skillsweatherSkill代码在当前项目Agent Runtime直接调用零网络开销但无法跨项目复用HTTP endpointCloud Function URLAgent Runtime发起HTTP调用需自行处理鉴权、重试、超时Platform Registry IDprojects/.../skills/...Agent Runtime通过Platform的统一网关调用自动获得鉴权、限流、熔断、可观测性生产环境强烈推荐第三种。为什么因为Platform网关会自动注入x-skills-auth-tokenskills服务只需验证这个token无需自己实现OAuth2。同时网关层已集成Cloud Armor、WAF、速率限制skills服务本身可以极度轻量——专注业务逻辑不操心安全。提示绑定时指定的skills ID必须和Registry里注册的ID完全一致包括project ID。大小写、短横线、路径分隔符错一个Agent调用时就会返回SKILL_NOT_FOUND。我帮客户排查过一次线上故障根源就是skills注册时用了email-send而Agent绑定写成了email_send。3.3 调度SchedulingPlatform如何决定“谁来执行何时执行”当Agent Runtime决定调用skills时Platform的Scheduler才真正介入。它的决策逻辑远比想象复杂准入检查验证skills ID是否存在、是否ACTIVE、是否在Agent的allowedSkills列表中防止越权调用路由选择根据skills version如1.2.*匹配Registry中最新兼容版本支持灰度1.2.0流量10%1.2.1流量90%负载均衡将请求分发到skills后端的多个实例Cloud Run revision基于CPU和请求数自动扩缩容熔断保护如果skills错误率连续1分钟5%Scheduler自动切断流量返回fallback response超时控制全局timeout默认10s和skills级timeout如weather设为5s双重保障这个过程对Agent完全透明。Agent只管发请求Platform负责确保它被安全、高效、可靠地执行。这也是为什么skills能成为企业级Agent的基石——它把复杂的分布式系统问题封装成一个简单的await skill.execute(input)调用。实操中最大的调度陷阱是超时嵌套。比如skills内部调用另一个API设了8s timeout而Platform给skills的总timeout是10s那么skills只剩2s做序列化、网络传输、错误处理。结果就是skills经常超时但日志显示“执行成功”。解决方案skills内部timeout必须小于Platform分配的timeout建议留30%余量。我们标准是Platform timeout设为Tskills内部所有操作timeout总和 ≤ 0.7T。4. 从零构建一个生产级skills以“企业微信消息发送”为例的全流程实操光讲理论不够我们来实战一个真实场景为客户构建一个“发送企业微信消息给指定成员”的skills。这个需求看似简单但涉及敏感凭据管理、消息格式校验、失败重试、审计日志是检验skills设计功力的黄金场景。我会带你走完从本地开发到线上灰度的每一步包括所有踩过的坑。4.1 需求拆解与边界定义先画清能力地图在写一行代码前我们必须回答五个问题谁调用Agent非人所以不需要UI、不需要登录态但需要严格的API鉴权输入是什么成员userid字符串、消息内容markdown格式、可选的机器人key用于不同业务线输出是什么发送成功/失败状态、message_id用于后续撤回、错误详情失败场景有哪些企业微信token过期、userid不存在、消息超长、频率超限、网络超时合规要求所有消息内容必须审计留存失败记录需告警基于此我们定义skills的契约Name:wechat.sendTextMessageVersion:1.0.0Input Schema:{ type: object, required: [userid, content], properties: { userid: { type: string, minLength: 1, maxLength: 64 }, content: { type: string, minLength: 1, maxLength: 2048 }, botKey: { type: string, enum: [hr-bot, it-bot, sales-bot] } } }Output Schema:{ type: object, required: [success], properties: { success: { type: boolean }, messageId: { type: string, nullable: true }, errorCode: { type: string, nullable: true }, errorMessage: { type: string, nullable: true } } }注意botKey的enum约束——不是随便传个字符串就行必须是预定义的三个业务线。这是防止误用的关键防线。4.2 本地开发与测试用Genkit Dev Server模拟真实环境我们用TypeScript Genkit SDK开发npm init genkitlatest # 选择 skills 模板创建src/skills/wechat.tsimport { defineSkill, SkillError } from genkit-dev/genkit; import { google } from googleapis; // 从Secret Manager获取token本地开发用.env const getAccessToken async (): Promisestring { if (process.env.NODE_ENV development) { return process.env.WECHAT_ACCESS_TOKEN || ; } // 生产环境从Secret Manager读取 const auth new google.auth.GoogleAuth({ scopes: [https://www.googleapis.com/auth/cloud-platform] }); const client await auth.getClient(); const accessToken await client.getAccessToken(); return accessToken; }; export const sendTextMessageSkill defineSkill({ name: wechat.sendTextMessage, version: 1.0.0, inputSchema: { type: object, required: [userid, content], properties: { userid: { type: string, minLength: 1, maxLength: 64 }, content: { type: string, minLength: 1, maxLength: 2048 }, botKey: { type: string, enum: [hr-bot, it-bot, sales-bot] } } }, outputSchema: { type: object, required: [success], properties: { success: { type: boolean }, messageId: { type: [string, null] }, errorCode: { type: [string, null] }, errorMessage: { type: [string, null] } } }, sideEffects: true, execute: async (input, context) { const startTime Date.now(); try { // 1. 获取access token带缓存 const accessToken await getAccessToken(); // 2. 构造企业微信API请求 const url https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token${accessToken}; const payload { touser: input.userid, msgtype: text, agentid: getAgentIdByBotKey(input.botKey || hr-bot), text: { content: input.content } }; // 3. 发起HTTP调用带超时和重试 const response await fetch(url, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify(payload), signal: AbortSignal.timeout(3000) // 3秒超时留7秒给Platform }); const data await response.json(); if (data.errcode ! 0) { // 企业微信标准错误码 throw new SkillError( WECHAT_${data.errcode}, WeChat API error: ${data.errmsg} ); } context.logger.info(WeChat message sent successfully, { userid: input.userid, botKey: input.botKey, messageId: data.msgid, latencyMs: Date.now() - startTime }); return { success: true, messageId: data.msgid, errorCode: null, errorMessage: null }; } catch (error) { context.logger.error(WeChat message send failed, { error, input, latencyMs: Date.now() - startTime }); // 统一错误分类 if (error instanceof SkillError) { throw error; } else if (error.name AbortError) { throw new SkillError(WECHAT_TIMEOUT, WeChat API timeout); } else { throw new SkillError(WECHAT_UNKNOWN_ERROR, Unknown WeChat error); } } } }); // 辅助函数根据botKey映射agentid const getAgentIdByBotKey (botKey: string): number { const map: Recordstring, number { hr-bot: 1001, it-bot: 1002, sales-bot: 1003 }; return map[botKey] || 1001; };本地测试关键步骤genkit dev启动Dev Server访问http://localhost:3000/skills查看注册的skills用curl测试curl -X POST http://localhost:3000/skills/wechat.sendTextMessage:execute \ -H Content-Type: application/json \ -d {userid:zhangsan,content:Hello World}查看genkit dev控制台日志确认结构化log输出故意传错userid验证错误码是否为WECHAT_60111userid不存在注意本地开发时getAccessToken()从.env读取但.env文件绝不能提交到Git。我们在.gitignore里加了*.env并在CI/CD流程中用Secret Manager注入。4.3 生产部署与Registry注册三步走稳准狠本地验证OK后部署到Cloud Run# 1. 构建容器镜像 gcloud builds submit --tag gcr.io/my-project/wechat-skill # 2. 部署到Cloud Run gcloud run deploy wechat-skill \ --image gcr.io/my-project/wechat-skill \ --platform managed \ --region us-central1 \ --allow-unauthenticated \ --set-env-varsNODE_ENVproduction \ --set-secretsWECHAT_ACCESS_TOKENprojects/123456/secrets/wechat-token/versions/latest # 3. 获取服务URL SERVICE_URL$(gcloud run services describe wechat-skill --formatvalue(status.url)) echo $SERVICE_URL # https://wechat-skill-xxxx.a.run.app然后用Genkit CLI注册到Agent Platform Registrygenkit deploy \ --project my-project \ --location us-central1 \ --skills-path ./src/skills/wechat.ts \ --endpoint $SERVICE_URL这条命令做了三件事解析wechat.ts提取manifest调用Platform API创建skills record将$SERVICE_URL绑定到skills IDprojects/my-project/skills/wechat.sendTextMessage注册成功后去Google Cloud Console Agent Platform Skills Registry能看到这个skills的状态是ACTIVE版本是1.0.0Endpoint指向你的Cloud Run URL。4.4 灰度发布与监控用真实流量验证稳定性新skills上线绝不能“全量发布”。我们用Platform的灰度功能在Console里编辑skillswechat.sendTextMessage点击“Edit version”添加新版本1.0.1设置流量比例1.0.0占90%1.0.1占10%部署1.0.1的代码比如加了更细粒度的错误日志观察DashboardLatency1.0.1的p90是否比1.0.0低Error Rate1.0.1的WECHAT_45009频率超限错误是否减少Invocation Count10%流量是否准确分流我们曾用这个方法发现一个致命bug1.0.0版本在企业微信返回errcode0时误把data.invaliduser数组当成功处理导致消息发给了不存在的用户。1.0.1版本加了data.invaliduser?.length 0校验灰度期间错误率从12%降到0.3%确认无误后才切全量。提示灰度期间务必打开Cloud Logging的高级过滤搜索jsonPayload.skillNamewechat.sendTextMessage实时查看两个版本的日志差异。这是定位问题的最快路径。5. skills生态避坑指南那些官方文档不会告诉你的实战经验最后分享我在十几个项目中踩过的、总结出的skills实战铁律。这些不是理论是血换来的教训每一条都能帮你少掉几根头发。5.1 版本管理不要用Git tag要用Platform的semantic versioning很多团队习惯用Git tag管理skills版本比如git tag v1.2.0。这是危险的。因为Platform的skills registry不认Git它只认你注册时声明的version字段。如果你在代码里写version: 1.2.0但Git tag是v1.2.0-rc1Platform会认为这是两个skills。正确做法skills的version必须硬编码在defineSkill()里且严格遵循SemVer 2.0.0。1.2.0是补丁1.2.1是修复1.3.0是新增功能向后兼容2.0.0是破坏性变更必须新建skills ID。我们吃过亏一个skills升级到2.0.0但没改ID导致Agent调用时schema校验失败整个对话流中断。后来我们立下规矩major version变更必须同步改skills name比如wechat.sendTextMessage→wechat.sendTextMessageV2。5.2 凭据安全永远不要在skills代码里写死API Key这是初级工程师最常见的错误。把企业微信的corpsecret直接写在process.env.CORPSECRET里然后在代码里fetch(..., {headers: {Authorization:Bearer ${process.env.CORPSECRET}}})。一旦代码泄露凭据就完了。正确姿势是三层防护开发环境用.envdotenv.gitignore确保不提交CI/CD环境用Cloud Build的--secret参数注入或GitHub Actions的secrets生产环境用Google Secret Managerskills启动时动态拉取且设置rotationPeriod自动轮换更重要的是skills服务本身不应该持有凭据。最佳实践是skills服务只接收Platform网关注入的x-skills-auth-token网关层负责用这个token向Secret Manager换凭据再透传给下游。skills只管业务逻辑安全交给Platform。5.3 错误码设计用领域语义不用HTTP状态码skills的SkillErrorcode不要用400、500这种HTTP码而要用业务域语义比如WECHAT_USER_NOT_FOUND、GEMINI_QUOTA_EXCEEDED、DB_CONNECTION_TIMEOUT。原因有三可读性运维看DashboardWECHAT_USER_NOT_FOUND比404清楚十倍可操作性不同错误码可以绑定不同告警策略WECHAT_RATE_LIMITED发企业微信告警DB_CONNECTION_TIMEOUT发PagerDuty可扩展性HTTP状态码只有几十个业务错误成百上千语义码无限可扩展我们定义了一套错误码规范{SYSTEM}_{CATEGORY}_{REASON}全部大写下划线分隔。SYSTEM是系统名WECHAT, GEMINI, DBCATEGORY是错误大类USER, QUOTA, TIMEOUTREASON是具体原因NOT_FOUND, EXCEEDED, CONNECTION_REFUSED。这样一眼就能定位问题根源。5.4 性能陷阱skills不是越快越好而是“快得刚刚好”追求skills极致性能是误区。一个100ms完成的skills如果每秒被调用1000次依然会压垮下游。真正的性能优化是在正确的时间做正确的事。我们总结出skills的“黄金响应时间” 100ms纯内存计算、缓存命中——理想但少见100ms - 500ms典型HTTP调用天气、短信、邮件——可接受需监控p99500ms - 2s复杂业务逻辑如调用3个API串行——需评估是否拆分为多个skills 2s必须异步化skills返回{success: true, jobId: xxx}另起worker处理用callback或polling通知结果我有个客户一个“生成PDF报告”的skills平均耗时3.2s导致Agent超时。我们重构为skills只做任务入队100ms返回jobId后台Cloud Run worker监听Pub/Sub生成PDF后写入Cloud Storage再发通知。用户体验反而更好——用户立刻得到“已提交”稍后收到链接。5.5 技术选型别迷信“最新”选“最稳”看到热搜里“Claude agent skills”“Codex skills”别急着跟风。skills的核心是稳定、可观测、可运维不是炫技。框架选型Genkit LangChain 自研。Genkit是Google官方SDK和Agent Platform深度集成schema校验、可观测性、灰度发布开箱即用。LangChain要自己撸一堆胶水代码。部署平台Cloud Run Cloud Functions 自建K8s。Cloud Run的自动扩缩、内置HTTPS、Secret集成省去90%运维工作。Functions冷启动延迟高不适合低延迟skills。语言选型TypeScript Python Go。TypeScript的类型系统在skills schema定义、IDE智能提示、编译期错误拦截上优势巨大。Python生态丰富但类型弱Go性能好但开发效率低。记住skills不是技术秀场是生产系统的螺丝钉。选最能让你睡安稳觉的那个。我在实际使用中发现把skills当成“能力微服务”来设计而不是“函数封装”整个AI工程的可维护性会指数级提升。一个健康的skills生态应该像乐高积木——每个块都严丝合缝组合起来能搭出任何建筑拆下来又能单独升级。这需要克制、耐心和对细节的偏执。但当你看到运维