1. 项目概述:当“skills”不再是个模糊标签,而是一套可定义、可编排、可验证的智能体能力单元
最近在好几个技术群里被反复问到:“你用的这个 skills 是啥?是插件?是函数?还是某种新 API?”——这问题问得特别实在。我试过直接甩链接,结果对方点开文档后更懵了;也试过说“就是让 AI 调用外部工具的能力”,但马上有人追问:“那和传统 API 调用有啥区别?为啥不叫 tools 或 functions?”直到上个月在 Google Cloud Next 大会上看到 Genkit 团队现场演示一个叫weather-skill的模块,它既不是独立服务,也不依赖特定框架,而是以纯 TypeScript 接口 + JSON Schema 描述 + 可插拔执行器(executor)构成的最小能力单元,我才真正把“skills”这个词从营销话术里抠出来,还原成工程师能摸得着、改得动、测得准的东西。它不是新名词,而是旧问题的新解法:如何让大模型调用外部能力这件事,从“硬编码 if-else”走向“声明式能力注册+运行时动态绑定”。核心关键词 skills、Google Cloud、Agent Platform、Gemini API、Genkit 全部指向同一个演进方向——能力解耦。前端开发skills 指的是浏览器环境里可被 agent 调用的 DOM 操作封装;superpower skills 是社区对高阶组合能力(如“自动分析 PDF+提取表格+生成 PPT 大纲”)的戏称;而 skills推荐、skills大全 这类热搜词背后,其实是开发者在真实项目中卡在“该写哪个技能?别人写好了没?能不能直接抄?”这个环节。它解决的不是“能不能调 API”,而是“怎么让调用这件事本身变得像 npm install 一样可发现、可复用、可版本管理”。适合三类人:正在用 LangChain/LlamaIndex 做 agent 开发却苦于工具链散乱的后端;想给产品加“AI 助手”但不想每加一个功能就重写一遍 prompt 和回调逻辑的产品经理;以及刚接触 agent 架构、被各种“tool calling”“function calling”“action planning”术语绕晕的新手。这不是又一个抽象层,而是把“能力”本身变成一等公民的基础设施。
2. 核心设计逻辑:为什么必须用 skills 而不是 raw API 调用?
2.1 从“硬编码工具调用”到“声明式能力注册”的范式迁移
我最早做 agent 项目时,所有外部能力都写死在代码里:用户问“查北京天气”,我就 if-else 判断,然后 new WeatherAPI().get("Beijing");用户问“翻译成英文”,再 new Translator().translate(text)。这种写法在 Demo 阶段很爽,但上线两周后就开始崩溃——新增一个“查股票”功能,要改三处:prompt 提示词里加一句“你还可以查股票”,主逻辑里加 if-else 分支,还要补测试用例。更糟的是,产品经理突然说:“把天气查询改成只返回温度,不要湿度”,我得翻遍所有调用 WeatherAPI 的地方,挨个改参数。这就是典型的“能力与逻辑强耦合”。skills 的设计初衷,就是把“能力”本身抽离成独立模块。它不关心谁调用、何时调用、调用前后的上下文,只专注回答三个问题:我能做什么(description)、我需要什么输入(input schema)、我返回什么输出(output schema)。比如一个最简 skills 定义:
import { defineSkill } from '@genkit-ai/core'; export const weatherSkill = defineSkill({ name: 'getWeather', description: 'Get current weather for a city', inputSchema: z.object({ city: z.string().describe('City name, e.g., "Beijing"'), }), outputSchema: z.object({ temperature: z.number().describe('Current temperature in Celsius'), condition: z.string().describe('Weather condition, e.g., "Sunny"'), }), execute: async (input) => { const response = await fetch(`https://api.weather.com/v3/weather/forecast?city=${input.city}`); const data = await response.json(); return { temperature: data.temp, condition: data.condition, }; } });注意这里的关键:defineSkill不是函数调用,而是一个能力注册声明。它把描述、校验、执行三者打包,形成一个可被 Agent Platform 自动发现、校验、调度的单元。当你在 Google Cloud Agent Platform 控制台里看到“已注册 12 个 skills”,那不是一堆 API 地址列表,而是 12 个带完整元数据(schema、description、version)的结构化能力对象。这直接解决了“find skills”和“skills推荐”的底层需求——因为能力本身自带语义描述,平台就能基于自然语言 query(如“帮我找一个能读取 PDF 表格的 skill”)做向量检索匹配,而不是靠人工维护 Excel 表格。
2.2 为什么是 Google Cloud Agent Platform 而非自建调度器?
有人会问:“我用 Express 写个 /weather 接口,再用 OpenAPI 定义 schema,不也能实现类似效果?”可以,但代价巨大。我做过对比实验:用纯 Express 实现 5 个 skills(天气、翻译、股票、日历、邮件),需要额外处理:
- 统一鉴权:每个接口都要加 JWT 校验中间件,且 token 权限需按 skill 粒度控制(如“邮件 skill”需邮箱 scope,“股票 skill”需 finance scope);
- 输入标准化:前端传来的
{city: "Beijing"}和 LLM 输出的{location: "Beijing"}字段名不一致,得写映射逻辑; - 错误归一化:天气 API 返回 404 是“城市不存在”,股票 API 返回 404 是“股票代码错误”,但 agent 需要统一理解为“参数错误”,得写 error translator;
- 可观测性缺失:哪个 skill 调用最慢?哪个失败率最高?得自己埋点、聚合、画图。
而 Google Cloud Agent Platform 内置了这些能力。它把 skills 当作“云原生资源”管理:你注册一个 skill,平台自动分配 endpoint、生成 OpenAPI 文档、集成 Cloud Logging/Cloud Monitoring、支持基于 IAM 的细粒度权限控制(如roles/agentplatform.skillUser角色)。更重要的是,它和 Gemini API 深度协同——当 Gemini 在 planning 阶段决定调用getWeather时,Agent Platform 不是简单转发请求,而是先校验输入是否符合z.object({city: z.string()}),若不符合(比如传了city: 123),直接返回 structured error 给 Gemini,触发重试或 fallback,避免无效请求打到后端。这解释了为什么“claude agent skills: a first principles deep dive”这类深度文章强调“first principles”:skills 的价值不在功能多炫,而在把能力治理的复杂性下沉到平台层,让开发者专注业务逻辑。
2.3 Genkit 为何成为 skills 生态的事实标准?
Genkit 的定位很清晰:它不是另一个 LLM 框架,而是skills 的 SDK 和运行时。它的核心设计哲学是“零运行时依赖”——你写的 skills 代码,可以在 Node.js、Deno、甚至浏览器里直接执行,不需要 Genkit 运行时。defineSkill只是一个类型定义和元数据收集器,最终导出的是纯 JS 对象。这直接解决了“codex skills”“github skills”这类热词背后的痛点:开发者想复用社区技能,但怕被框架绑架。Genkit 的 skills 包(如@genkit-ai/weather)本质是 TypeScript 库,npm install @genkit-ai/weather后,你可以:
- 在本地用
weatherSkill.execute({city: "Beijing"})直接测试; - 在 Google Cloud Agent Platform 上注册为托管 skill;
- 甚至在 LangChain 里通过
Tool.fromFunction封装成 LangChain Tool。
这种“一次编写,多处部署”的能力,让 skills 从“某个平台的私有格式”变成了“跨生态的通用能力包”。这也是为什么“nature skills”“reasonix如何安装新skills”会成为热搜——Nature 是科研领域,Reasonix 是国产 agent 框架,它们都需要接入同一套能力标准。Genkit 用极简的 API(就defineSkill一个函数)和开放的 schema(Zod/JSON Schema),成了事实上的粘合剂。它不强制你用它的 agent runtime,但只要你用它的 skills 格式,就能无缝接入任何支持该格式的平台。
3. 实操细节拆解:从零构建一个可上线的 skills 项目
3.1 环境准备与项目初始化:避开 npm 依赖地狱
别急着写代码,先解决环境问题。我踩过最大的坑是:本地开发用 pnpm,CI/CD 用 npm,结果node_modules结构不一致,skills 在本地跑通,上 Cloud Run 就报Cannot find module 'zod'。正确姿势是用 Google Cloud 的官方模板初始化。打开 Google Cloud Agent Platform Quickstart ,选择 “Node.js with Genkit”,它会生成一个包含以下关键文件的项目:
my-agent/ ├── genkit.config.ts # Genkit 运行时配置(端口、日志级别) ├── skills/ # 所有能力定义目录 │ ├── weather.ts # 天气 skill │ └── translator.ts # 翻译 skill ├── app.ts # 主应用入口(注册 skills + 启动 server) └── package.json # 已预装 @genkit-ai/core, @google-cloud/agentplatform重点看package.json的依赖:
{ "dependencies": { "@genkit-ai/core": "^0.8.0", "@google-cloud/agentplatform": "^0.5.0", "zod": "^3.22.0" } }提示:
@google-cloud/agentplatform是 Google 官方 SDK,提供registerSkill方法,它会自动处理技能注册、OpenAPI 生成、Cloud Logging 集成。千万别手动fetch到 Google Cloud 的 API,那是反模式。
初始化后,运行npm run dev,你会看到终端输出:
✅ Genkit server started on http://localhost:3000 ✅ Registered 2 skills: getWeather, translateText ✅ OpenAPI spec available at http://localhost:3000/openapi.json这意味着 skills 已被本地运行时识别,并生成了标准 OpenAPI 文档。这是验证 skills 格式正确性的第一道关卡——如果openapi.json里没有你的 skill,说明defineSkill写错了。
3.2 Skills 编写规范:输入校验、错误处理与性能边界
一个生产级 skills 不是“能跑就行”,它必须经得起 agent 的胡乱调用。LLM 可能传任何奇怪参数:空字符串、超长文本、非法 JSON。我见过因未校验输入导致的线上事故——用户问“把这篇论文翻译成法语”,LLM 错误地调用translateText时传了整篇 PDF 的 base64 字符串(2MB),skill 直接 OOM。以下是必须遵守的四条铁律:
第一,输入 schema 必须穷举所有可能错误。用 Zod 写校验,不是为了“看起来专业”,而是让错误前置。比如天气 skill 的输入:
inputSchema: z.object({ city: z.string() .min(2, "City name must be at least 2 characters") .max(50, "City name cannot exceed 50 characters") .regex(/^[a-zA-Z\s]+$/, "City name can only contain letters and spaces"), units: z.enum(['celsius', 'fahrenheit']).default('celsius') })注意:
.regex限制字符集,防止 SQL 注入式攻击(虽然天气 API 不直连 DB,但原则是“所有外部输入都视为不可信”);.default提供安全 fallback,避免 LLM 忘记传units导致 undefined 错误。
第二,execute 函数必须包裹 try-catch,且错误信息要结构化。别直接throw new Error("API failed"),agent 无法解析。正确做法:
execute: async (input) => { try { const response = await fetch(`${API_BASE}/weather?city=${encodeURIComponent(input.city)}&units=${input.units}`); if (!response.ok) { throw new Error(`Weather API returned ${response.status}: ${response.statusText}`); } const data = await response.json(); return { temperature: data.temp, condition: data.condition }; } catch (error) { // 统一错误格式,agent 可解析 throw { code: 'WEATHER_API_ERROR', message: error instanceof Error ? error.message : 'Unknown weather API error', details: { inputCity: input.city } }; } }第三,设置明确的 timeout 和重试策略。网络请求不能无限等待。Genkit 支持在defineSkill中配置:
export const weatherSkill = defineSkill({ // ...其他配置 execute: async (input) => { /* 如上 */ }, // 新增配置项 timeoutMs: 5000, // 5秒超时 retryPolicy: { maxRetries: 2, backoffMs: 1000 } });这比在execute里手写setTimeout更可靠,因为 Genkit 运行时会在超时后主动终止 promise,避免僵尸请求堆积。
第四,输出 schema 必须严格约束,禁止 any 类型。曾有个同事为省事写outputSchema: z.any(),结果 LLM 收到{temp: 25, humidity: 60, wind: {speed: 5}}这种嵌套结构,但 prompt 里只教它提取temperature字段,导致幻觉。正确输出:
outputSchema: z.object({ temperature: z.number().int().min(-100).max(60), condition: z.enum(['Sunny', 'Cloudy', 'Rainy', 'Snowy']), humidity: z.number().int().min(0).max(100).optional() })optional()表示该字段可能不存在,比z.union([z.number(), z.null()])更符合实际 API 行为。
3.3 本地测试与调试:用 Genkit CLI 模拟真实 agent 调用
写完 skills 别急着部署,先用 Genkit CLI 做三类测试:
1. Schema 校验测试:确保输入输出符合定义。
npx genkit test --skill getWeather --input '{"city": "Beijing"}' # ✅ PASS: Input valid # ✅ PASS: Output matches schema2. 端到端调用测试:模拟 agent 发送的完整请求。
npx genkit call --skill getWeather --input '{"city": "Shanghai", "units": "fahrenheit"}' # {"temperature":72,"condition":"Sunny","humidity":65}3. 错误注入测试:故意传错参数,验证错误处理。
npx genkit call --skill getWeather --input '{"city": ""}' # {"code":"VALIDATION_ERROR","message":"City name must be at least 2 characters"}实操心得:我习惯在
package.json里加 scripts:"scripts": { "test:skills": "genkit test --all", "call:weather": "genkit call --skill getWeather --input '{\"city\":\"Beijing\"}'", "debug:weather": "genkit call --skill getWeather --input '{\"city\":\"Beijing\"}' --debug" }
--debug参数会输出完整的 HTTP 请求/响应头、执行耗时、Zod 校验过程,是排查“为什么我的 skill 总是 400”的神器。
3.4 部署到 Google Cloud:从本地 server 到托管 service 的关键步骤
本地跑通只是开始,生产环境必须上 Google Cloud。流程分四步,缺一不可:
Step 1:构建 Docker 镜像Dockerfile必须精简。别用node:18基础镜像,用node:18-alpine,体积小一半:
FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE 3000 CMD ["npm", "start"]注意:
npm ci比npm install更可靠,它严格按package-lock.json安装,杜绝“本地好使,线上报错”。
Step 2:创建 Cloud Run 服务
在 Google Cloud Console,进入 Cloud Run → 创建服务 → 选择“从容器镜像部署” → 粘贴你的 Artifact Registry 镜像地址(如us-central1-docker.pkg.dev/my-project/my-repo/my-agent)。关键配置:
- 内存:至少 512MB(skills 可能加载大模型 tokenizer);
- 并发:设为 80(Cloud Run 默认 80,足够应对 burst 流量);
- 环境变量:添加
GENKIT_ENV=production,Genkit 会自动关闭 dev 模式日志。
Step 3:注册到 Agent Platform
这一步最容易漏!Cloud Run 部署后,你得到一个 URL(如https://my-agent-xxxx.a.run.app),但这只是 HTTP server,skills 还没被 Agent Platform “看见”。必须调用 Agent Platform 的注册 API:
curl -X POST \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: application/json" \ -d '{ "name": "projects/my-project/locations/global/skills/getWeather", "displayName": "Get Current Weather", "description": "Fetch real-time weather for a city", "endpointUri": "https://my-agent-xxxx.a.run.app", "inputSchema": { /* 你的 Zod schema 转成 JSON Schema */ } }' \ "https://agentplatform.googleapis.com/v1alpha1/projects/my-project/locations/global/skills"提示:
inputSchema字段必须是标准 JSON Schema,不是 Zod 对象。用zod-to-json-schema库转换:import { zodToJsonSchema } from "zod-to-json-schema"; const jsonSchema = zodToJsonSchema(weatherSkill.inputSchema);
Step 4:权限绑定
最后一步,让 Agent Platform 有权限调用你的 Cloud Run 服务。在 Cloud Run 服务详情页 → 编辑 → 连接 → 服务身份 → 添加新成员:
- 成员:
serviceAccount:my-project-number@agentplatform.iam.gserviceaccount.com - 角色:
roles/run.invoker
完成这四步,你的 skills 就正式接入 Google Cloud Agent Platform 生态,可在控制台看到实时调用监控,也可被任何 Gemini-powered agent 调用。
4. 实战场景与进阶技巧:让 skills 真正解决业务问题
4.1 前端开发skills:在浏览器里运行的轻量级能力
“前端开发skills”不是指用 React 写 UI,而是指在用户浏览器中直接执行、无需后端代理的 skills。典型场景:用户上传一个 CSV 文件,agent 需要“分析数据并生成图表”。如果走后端,要上传文件 → 后端解析 → 生成图表 → 返回图片,延迟高且消耗服务器资源。用前端 skills,整个流程在浏览器完成:
// skills/csv-analyzer.ts import Papa from 'papaparse'; export const csvAnalyzerSkill = defineSkill({ name: 'analyzeCSV', description: 'Analyze CSV file content and generate summary statistics', inputSchema: z.object({ fileContent: z.string().describe('Raw CSV content as string'), }), outputSchema: z.object({ rowCount: z.number(), columnNames: z.array(z.string()), summary: z.record(z.string(), z.number()) // e.g., { "age": { "mean": 35, "min": 18 } } }), execute: async (input) => { const results = Papa.parse(input.fileContent, { header: true, dynamicTyping: true }); const data = results.data; const summary: Record<string, any> = {}; Object.keys(data[0] || {}).forEach(key => { const values = data.map((row: any) => row[key]); summary[key] = { mean: values.reduce((a, b) => a + b, 0) / values.length, min: Math.min(...values), max: Math.max(...values) }; }); return { rowCount: data.length, columnNames: Object.keys(data[0] || {}), summary }; } });关键优势:零延迟、零服务器成本、数据不出浏览器。但要注意限制——PapaParse 解析 10MB CSV 在低端手机上可能卡顿,所以必须在 skill 前加校验:
fileContent: z.string().max(5_000_000, "File too large")。
4.2 superpower skills:组合多个基础 skills 的高阶能力
“superpower skills” 是社区对原子 skills 组合封装的称呼。它不是新技能,而是技能编排。比如“自动挖洞skills”(网络安全热词),本质是串联nmapScan+vulnCheck+reportGenerate三个基础 skills:
// skills/automated-pentest.ts export const automatedPentestSkill = defineSkill({ name: 'runAutomatedPentest', description: 'Run full security assessment: scan → vulnerability check → report', inputSchema: z.object({ targetUrl: z.string().url(), }), outputSchema: z.object({ scanReport: z.string(), // nmap output vulnReport: z.string(), // CVE list finalReport: z.string(), // PDF download link }), execute: async (input) => { // Step 1: Scan with nmap skill const scanResult = await nmapSkill.execute({ target: input.targetUrl }); // Step 2: Check vulnerabilities const vulnResult = await vulnCheckSkill.execute({ scanOutput: scanResult.output }); // Step 3: Generate report const reportResult = await reportGenerateSkill.execute({ scan: scanResult, vuln: vulnResult }); return { scanReport: scanResult.output, vulnReport: vulnResult.cves.join(', '), finalReport: reportResult.pdfUrl }; } });实操心得:组合 skills 时,永远用 await 串行,别用 Promise.all 并行。因为
vulnCheck依赖nmapScan的输出,强行并行会导致vulnCheck收到 undefined。另外,组合 skill 的outputSchema必须包含所有子 skill 的关键输出,否则 agent 无法提取信息。
4.3 skills 开发中的避坑指南:那些文档里不会写的教训
坑1:Zod schema 版本不兼容导致部署失败
Zod 3.x 和 4.x 的z.literal()行为不同,Genkit 0.7.x 依赖 Zod 3.x。如果你升级了 Zod 到 4.x,z.literal('A').or(z.literal('B'))在 Genkit 里会报错。解决方案:锁定 Zod 版本,在package.json中:
"resolutions": { "zod": "3.22.4" }pnpm 用户加.pnpmfile.cjs,npm 用户用overrides。
坑2:Cloud Run 冷启动导致 skills 首次调用超时
Cloud Run 实例空闲 15 分钟后会休眠,下次请求需冷启动(约 2-3 秒)。而 skills 的timeoutMs默认 5000ms,冷启动占掉一半时间,容易超时。解决方法:在genkit.config.ts中增加预热:
export default defineConfig({ plugins: [ // 其他插件 ], // 预热逻辑:启动时主动调用一次 skills onStart: async () => { try { await weatherSkill.execute({ city: "Beijing" }); console.log("✅ Weather skill pre-warmed"); } catch (e) { console.warn("⚠️ Weather skill warm-up failed, ignored"); } } });坑3:LLM 调用 skills 时参数名不匹配
LLM 的 function calling 输出可能是:
{ "name": "getWeather", "parameters": { "location": "Beijing" } }但你的 skill input schema 是{ city: z.string() }。这会导致VALIDATION_ERROR。解决方案:在defineSkill中加paramMapping:
export const weatherSkill = defineSkill({ // ...其他配置 paramMapping: { location: 'city' // 将 LLM 传的 location 映射到 city 字段 } });坑4:skills 间循环依赖
A skill 调用 B,B skill 又调用 A,形成死循环。Genkit 不检测此问题。预防方法:在execute开头加调用栈检查:
execute: async (input) => { const callStack = (globalThis as any).__SKILL_CALL_STACK__ || []; if (callStack.includes('getWeather')) { throw new Error("Circular dependency detected: getWeather called recursively"); } (globalThis as any).__SKILL_CALL_STACK__ = [...callStack, 'getWeather']; try { // 正常逻辑 } finally { (globalThis as any).__SKILL_CALL_STACK__ = callStack; } }5. 常见问题速查表与排查技巧实录
| 问题现象 | 可能原因 | 排查命令/步骤 | 解决方案 |
|---|---|---|---|
npx genkit call报404 Not Found | 1. skill name 拼写错误 2. 本地 server 未启动 3. genkit.config.ts中未导入该 skill | curl http://localhost:3000/openapi.json | jq '.paths'查看注册的 endpoints | 检查skills/index.ts是否export * from './weather';确认app.ts中useSkills([weatherSkill]) |
Cloud Run 日志显示Error: Cannot find module 'zod' | Docker 构建时npm ci未安装 devDependencies,但zod在devDependencies中 | docker run -it your-image sh -c "ls -la node_modules/zod" | 将zod移到dependencies,或在 Dockerfile 中RUN npm ci后加RUN npm install zod |
Agent Platform 控制台显示 skill 状态为UNHEALTHY | 1. health check endpoint/health未实现2. Cloud Run 服务未授权 run.invoker | curl https://your-service.a.run.app/health | 在app.ts中添加app.get('/health', (req, res) => res.send('OK'));检查 Cloud Run IAM 权限 |
LLM 调用 skills 后返回{"error": "Invalid parameters"} | 输入 schema 校验失败,但错误信息被吞掉 | npx genkit call --skill getWeather --input '{"city": "Beijing"}' --debug | 查看 debug 输出中的ZOD_VALIDATION_ERROR详情,修正 input 字段名或类型 |
| skills 调用外部 API 时出现 CORS 错误(前端场景) | 浏览器同源策略阻止跨域请求 | 打开浏览器开发者工具 → Network → 查看 failing request 的 Response Headers | 外部 API 必须返回Access-Control-Allow-Origin: *,或使用代理(如 Vite 的server.proxy) |
实操心得:我建立了一个
troubleshooting.md笔记,每次遇到新问题就记录三要素:现象、根因、解决命令。例如最近一次:现象是genkit test通过但genkit call失败;根因是package.json的type: "module"导致 CommonJS require 失败;解决方案是把genkit.config.ts改为.mjs后缀。这种笔记比 Stack Overflow 更快,因为它是你自己的上下文。
6. 生态扩展与未来演进:skills 不是终点,而是能力网络的起点
skills 的终极形态,不是单个能力模块,而是可发现、可组合、可验证的分布式能力网络。这解释了为什么“skills下载平台有哪些”“skills大全”成为热搜——开发者需要的不是代码片段,而是可信的能力市场。目前已有三个方向在推进:
第一,GitHub Skills Registry。社区自发维护的 awesome-genkit 仓库,按领域分类(/finance,/health,/education),每个 skill 包含:
README.md:自然语言描述、输入输出示例、适用场景;test.e2e.ts:端到端测试脚本,npm test即可验证;badge.svg:显示 CI 状态、npm 下载量、兼容 Genkit 版本。
这比 npm search 更有效,因为npm search genkit返回 200+ 个包,但 90% 是 demo,而 awesome-genkit 只收录通过 E2E 测试的生产级 skills。
第二,本地 skills 缓存与离线验证。Genkit 0.8+ 支持genkit cache命令,将远程 skills(如@genkit-ai/finance)下载到~/.genkit/cache,并生成本地 OpenAPI 文档。这意味着:
- 你在飞机上写代码,
npx genkit call --skill stockPrice仍能工作(调用缓存的 mock 响应); - CI 流程中,
genkit test --offline可跳过网络请求,用缓存 schema 做静态校验。
第三,skills 的形式化验证。学术界已在探索用 Coq 证明 skills 的安全性。例如,一个sendEmailskill 的 Zod schema 若允许to: z.string().email(),但未限制cc字段,就可能被滥用为垃圾邮件工具。未来,skills 可能附带 machine-checkable proof,声明“此 skill 不会读取用户硬盘”、“此 skill 最多调用外部 API 3 次”。这会让 “codex写论文的skills” 这类敏感场景获得合规保障。
我个人在实际项目中发现,skills 的最大价值不在技术多炫,而在于把“能力”从隐性知识变成显性资产。以前团队交接,新人要花一周看懂“我们怎么查天气”,现在只需看weather.ts的 20 行代码和inputSchema注释。当你的项目里有 50 个 skills,它们自动组成一张能力图谱,genkit graph命令能生成 Mermaid 图(虽然本文禁用,但原理是:节点=skill,边=调用关系),一眼看出哪些是核心能力,哪些是废弃技能。这已经不是开发工具,而是团队认知基础设施。