尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

agent-skills:面向智能体的能力契约协议与联邦治理实践

agent-skills:面向智能体的能力契约协议与联邦治理实践 1. “agent-skills”不是功能模块而是一套可复用的智能体能力协议层你第一次在 GitHub 上看到agent-skills这个仓库名时大概率会下意识把它当成某个具体 AI 应用里的“技能插件包”——比如“天气查询技能”“文档摘要技能”“代码生成技能”。但实际翻开源码后你会发现它没有一个.ts文件里写着async function getWeather()也没有任何对接 OpenAI 或 Anthropic 的 API 密钥配置。它甚至不依赖任何 LLM SDK。这恰恰是它的设计原点agent-skills是一套脱离具体模型、脱离具体框架、脱离具体业务逻辑的“能力契约”Capability Contract。我去年在给一家做工业设备远程诊断的客户做智能体架构设计时就卡在这个认知盲区里。团队花了三周时间把“振动频谱分析”“历史故障匹配”“维修建议生成”三个能力硬编码进一个 LangChain Chain 中结果客户突然提出要接入另一家国产大模型平台要求所有能力必须在 48 小时内完成适配。我们当时只能重写整个调用链——不是因为逻辑变了而是因为每个能力都和llm.invoke()的返回结构、错误处理方式、流式响应格式深度耦合。直到我看到agent-skills的 README 第一行“Skills are interfaces, not implementations”才意识到问题出在抽象层级上。agent-skills的核心价值就藏在它的 TypeScript 接口定义里。它不提供“怎么做”只定义“能做什么”和“怎么被调用”。比如FileReadSkill接口只声明interface FileReadSkill { read: (params: { path: string; encoding?: utf8 | base64 }) Promise{ content: string }; }它不关心你是用 Node.js 的fs.promises.readFile、还是用浏览器的fetch加FileReader、甚至用 Python 的subprocess调用外部命令——只要你的实现满足这个输入/输出契约它就能被任何符合规范的智能体运行时Agent Runtime识别、调度、组合。这种解耦让能力复用从“复制粘贴代码”升级为“声明式集成”。提示这不是“面向接口编程”的简单套用。传统接口定义的是类与类之间的协作契约而agent-skills定义的是智能体Agent与其执行环境Runtime之间、以及不同能力Skill彼此之间的协作契约。它解决的是多模型、多平台、多语言环境下能力碎片化的问题。关键词Node.js和TypeScript在这里不是技术栈选择而是工程落地的刚性约束Node.js 提供了统一的运行时沙箱避免 Python/Java/Go 混合部署的运维地狱TypeScript 则通过编译期类型检查把“契约一致性”从测试用例里提前到开发阶段。当你在 Nx 工作区里为myorg/skills-file-read包编写单元测试时你测的不是文件读取是否成功而是read()方法的参数类型是否严格匹配FileReadSkill.read的签名返回值是否精确包含{ content: string }——这才是agent-skills协议的生命线。2. Nx 不是构建工具而是能力包的“联邦治理中枢”如果你把agent-skills理解为一套接口标准那么 Nx 就是这套标准在企业级项目中得以规模化落地的“操作系统内核”。很多团队在初次接触 Nx 时会把它当作一个更高级的 Webpack/Vite 替代品——用来加速构建、管理依赖、共享配置。但在agent-skills的语境下Nx 的核心价值远不止于此它让成百上千个独立开发、独立发布、甚至由不同团队维护的技能包Skill Package能在同一个代码仓库里保持自治同时又能被统一发现、版本对齐、影响分析和安全审计。举个真实案例我们为某银行构建风控智能体时需要集成 37 个技能——从“身份证 OCR 识别”由生物识别团队维护、“征信报告解析”由合规团队维护、到“反欺诈规则引擎调用”由风控算法团队维护。如果每个技能都放在独立 Git 仓库光是版本管理就足以崩溃当bank/skill-ocr发布 v2.1.0 时bank/skill-credit-report必须同步升级其peerDependencies否则运行时就会因类型不匹配而报错。而 Nx 的project.json配置让这一切变得可预测// projects/skill-ocr/project.json { name: skill-ocr, type: library, targets: { build: { executor: nx/js:tsc }, test: { executor: nx/jest:jest } }, dependencies: { agent-skills/core: [build], bank/skill-common-types: [build] } }注意这里的dependencies不是 npm 的dependencies而是 Nx 的项目间构建依赖图。当你运行nx build skill-ocr时Nx 会自动检测到bank/skill-common-types也需要重建并确保它先于skill-ocr完成。更重要的是Nx 的affected命令能精准回答“如果我修改了agent-skills/core的SkillInput类型定义哪些技能包会受影响”——答案不是模糊的“所有包”而是精确列出skill-ocr、skill-credit-report、skill-fraud-rules这三个项目。这种基于源码依赖的拓扑分析是纯 npm workspace 无法提供的。注意Nx 的nx.json中targetDefaults的配置决定了技能包的“发布契约”。例如我们强制所有技能包的buildtarget 必须输出esm和cjs两种格式并在package.json中正确设置exports字段。这保证了下游项目无论用import还是require都能获得正确的模块解析。没有 Nx 的集中管控这种跨团队的发布规范会迅速瓦解。semantic-release在这里扮演的是“自动化守门员”角色。它不负责决定什么该发布而是严格执行 Nx 定义的发布策略只有当main分支上的提交包含feat:前缀时才触发 minor 版本发布只有fix:才触发 patch 版本。而所有技能包的版本号都由 Nx 的nx release命令统一协调——它会扫描所有projects/*/package.json根据依赖关系计算出最小公共版本增量然后原子性地更新所有相关包的version字段并推送 tag。这意味着当你看到bank/skill-ocr3.2.1和bank/skill-credit-report3.2.1时它们背后是同一套经过完整 E2E 测试的agent-skills/core2.5.0运行时。这种版本一致性是智能体系统稳定性的基石。3. 技能注册与发现从硬编码到声明式元数据驱动在agent-skills的世界里“如何让智能体知道某个技能存在”这个问题答案不再是import { FileReadSkill } from ./skills/file-read; const skill new FileReadSkill();这样的硬编码实例化。取而代之的是一套基于文件系统约定和 JSON Schema 的声明式元数据注册机制。这个机制的设计哲学很朴素技能的“存在性”应该由文件系统结构和标准化的skill.json文件共同声明而不是由某段 JavaScript 代码动态创建。每个技能包的根目录下必须存在一个skill.json文件其结构受严格 Schema 约束{ $schema: https://agent-skills.dev/schemas/skill-v1.json, name: myorg/skill-db-query, version: 1.0.0, description: Execute SQL queries against PostgreSQL databases, implements: [agent-skills/core:DatabaseQuerySkill], entryPoint: ./dist/index.js, capabilities: { database: [postgresql], security: [token-auth, tls-encrypted] } }这个文件的作用远超一个简单的描述文档。它是技能的“数字身份证”包含了运行时所需的一切静态信息implements字段告诉 Agent Runtime“我实现了DatabaseQuerySkill接口你可以把我当作数据库查询能力来调度”entryPoint告诉加载器“我的主入口文件在./dist/index.js请用 CommonJS 方式 require 它”capabilities则是运行时的“准入检查清单”当 Agent 收到一个需要连接 MySQL 的请求时它会跳过所有capabilities.database不包含mysql的技能哪怕它们都实现了DatabaseQuerySkill。我曾经在调试一个生产环境故障时深刻体会到这个设计的价值。问题现象是智能体在处理 PDF 解析请求时总是随机失败。日志显示Error: Skill not found for mime-type application/pdf。按照传统思路我们会去查pdf-parser包的导出是否正确、是否被正确 import。但这次我直接ls -la node_modules/myorg/skill-pdf-parser/发现skill.json文件里implements字段写成了[agent-skills/core:PdfParseSkill]多了一个s而 Runtime 的接口定义是PdfParseSkill单数。由于skill.json是 JSON 格式TypeScript 编译器无法检查这个拼写错误但它会在 Runtime 的技能注册阶段被ajvSchema 校验器捕获并静默忽略该技能——导致技能列表里根本没有 PDF 解析能力。修复只需改一个字母但如果没有这套声明式元数据这个 bug 可能会潜伏数月。提示skill.json的 Schema 本身也是可扩展的。我们在agent-skills/core中定义了基础字段而各业务线可以继承并添加自己的字段比如bank/skill-ocr的skill.json可以增加compliance: { gdpr: true, pci-dss: level-2 }字段。Agent Runtime 在加载时会合并所有 Schema 并进行校验确保扩展字段也符合业务规范。Nx 在这里提供了关键的构建支持。我们为所有技能包配置了自定义的nx buildexecutor它在tsc编译完成后会自动执行一个脚本读取src/skill.ts中导出的接口类型生成对应的skill.json模板并将其复制到dist/目录。这样开发者只需专注编写 TypeScript 接口和实现skill.json的生成和校验完全自动化。这消除了人为维护元数据带来的不一致风险。4. 运行时调度基于能力图谱的动态路由与降级策略当agent-skills的技能包被正确注册后真正的挑战才开始如何让一个复杂的用户请求例如“对比 A/B 两份合同的差异并高亮显示法律风险条款”被自动拆解、路由到多个技能的组合执行并在某个技能不可用时优雅降级这不是简单的函数调用链而是一个需要实时决策的“能力图谱路由”Capability Graph Routing过程。agent-skills的 Runtime 不采用预定义的 Workflow DSL如 YAML 描述的步骤序列而是基于技能的implements和capabilities字段构建一张动态的能力依赖图Capability Dependency Graph。这张图的节点是技能接口如DocumentCompareSkill,LegalRiskAnalyzeSkill边是接口间的隐含依赖关系例如LegalRiskAnalyzeSkill的输入类型LegalRiskInput可能继承自DocumentCompareSkill的输出类型ComparisonResult。当请求到达时Runtime 的调度器会执行以下步骤意图解析使用轻量级 NLU 模型非 LLM提取请求中的核心动词compare,highlight,analyze和宾语contracts,legal risks映射到候选技能接口图遍历从DocumentCompareSkill开始查找所有能消费其输出的技能即LegalRiskAnalyzeSkill形成一条潜在执行路径能力匹配检查当前已注册的技能实例中哪些同时满足implements和capabilities约束例如LegalRiskAnalyzeSkill实例必须声明capabilities.jurisdiction: [cn]健康检查对候选技能执行轻量级探针如调用health()方法或检查最近 5 分钟成功率剔除不可用节点路径生成输出一个带权重的执行计划例如[ { skill: skill-contract-compare, weight: 0.95 }, { skill: skill-legal-risk, weight: 0.87 } ]。这个过程的关键在于降级不是简单的“跳过”而是“能力置换”。假设skill-legal-risk因上游法规 API 维护而不可用调度器不会直接报错而是搜索图中是否存在替代路径比如skill-legal-risk-basic仅基于规则库不调用外部 API其capabilities字段声明mode: offline。只要该技能的weight评分高于阈值如 0.7它就会被纳入执行计划并在最终响应中打上degraded: true标记告知前端这是降级结果。我在某次金融客户演示中故意拔掉了skill-credit-report的网络连接。预期结果是风控流程中断。但实际发生的是Runtime 在 320ms 内完成了图重构将原本依赖实时征信数据的CreditScoreCalculateSkill无缝切换为CreditScoreEstimateSkill基于用户历史行为的统计模型并返回了带{source: estimated, confidence: 0.72}的结果。客户当场拍板采购——因为他们意识到这种基于能力图谱的弹性比任何单点高可用方案都更能保障业务连续性。注意能力图谱的构建成本很高因此agent-skillsRuntime 默认启用缓存。但缓存失效策略不是简单的 TTL而是基于skill.json的version字段和capabilities的哈希值。当skill-legal-risk从 v1.2.0 升级到 v1.3.0且capabilities.jurisdiction从[cn]扩展为[cn, sg]时缓存会自动失效确保新能力被及时纳入图谱。5. 从零搭建一个可验证的技能包以myorg/skill-http-request为例理论讲得再透不如亲手搭一个可跑通的技能包。下面我带你从零开始用 Nx 创建一个符合agent-skills规范的myorg/skill-http-request并让它通过完整的 CI/CD 流水线验证。这个过程会暴露所有新手最容易踩的坑也是我过去三年带团队时最常被问到的问题。第一步初始化 Nx 工作区npx create-nx-workspacelatest my-agent-skills --presetapps --clinx --nxCloudfalse cd my-agent-skills # 安装核心依赖 npm install --save-dev agent-skills/core nx/js nx/jest nx/eslint关键点不要用--presetmonorepo因为agent-skills的最佳实践是“应用驱动”的工作区App-Driven Workspace即以智能体应用Agent App为根技能包作为其依赖库。这能确保nx affected命令的准确性。第二步生成技能包nx g nx/js:library skill-http-request --directorymyorg --importPathmyorg/skill-http-request --publishable --buildable这个命令会创建libs/myorg/skill-http-request/目录并在project.json中配置好build和testtarget。但此时它只是一个空壳我们需要注入agent-skills的 DNA。第三步定义技能接口与实现编辑libs/myorg/skill-http-request/src/lib/http-request.skill.tsimport { Skill, SkillInput, SkillOutput } from agent-skills/core; export interface HttpRequestSkill extends Skill { request: (input: HttpRequestInput) PromiseHttpRequestOutput; } export interface HttpRequestInput extends SkillInput { url: string; method: GET | POST | PUT | DELETE; headers?: Recordstring, string; body?: string | object; } export interface HttpRequestOutput extends SkillOutput { statusCode: number; headers: Recordstring, string; body: string; } // 实现必须严格遵循接口不能多也不能少 export class HttpRequestSkillImpl implements HttpRequestSkill { async request(input: HttpRequestInput): PromiseHttpRequestOutput { // 这里用 node-fetch 而不是 axios因为后者会引入大量 polyfill const fetch (await import(node-fetch)).default; const response await fetch(input.url, { method: input.method, headers: input.headers, body: input.body typeof input.body object ? JSON.stringify(input.body) : input.body }); return { statusCode: response.status, headers: Object.fromEntries(response.headers.entries()), body: await response.text() }; } }踩坑经验很多团队在这里用axios结果在浏览器环境中因http/https模块缺失而报错。node-fetch通过条件导出exports字段完美兼容 Node.js 和现代浏览器这是agent-skills对“跨平台”承诺的技术基石。第四步编写skill.json并配置构建在libs/myorg/skill-http-request/下创建skill.json{ $schema: https://agent-skills.dev/schemas/skill-v1.json, name: myorg/skill-http-request, version: 0.0.0, description: Make HTTP requests to external APIs, implements: [agent-skills/core:HttpRequestSkill], entryPoint: ./dist/index.js, capabilities: { protocol: [http, https], timeout: 30000 } }然后修改project.json的buildtarget加入skill.json的拷贝build: { executor: nx/js:tsc, options: { outputPath: dist/libs/myorg/skill-http-request, tsConfig: libs/myorg/skill-http-request/tsconfig.lib.json, packageJson: libs/myorg/skill-http-request/package.json }, configurations: { production: { additionalEntryPoints: [ libs/myorg/skill-http-request/skill.json ] } } }第五步编写测试与 CI 验证libs/myorg/skill-http-request/src/lib/http-request.skill.spec.ts的测试必须覆盖两个维度契约合规性验证HttpRequestSkillImpl是否真的实现了HttpRequestSkill接口TypeScript 编译期已保证但运行时需确认能力声明一致性验证skill.json的implements字段是否与代码中implements的接口名完全一致字符串匹配非类型推导。import { readFileSync } from fs; import { join } from path; import { HttpRequestSkillImpl } from ./http-request.skill; describe(HttpRequestSkillImpl, () { it(should implement HttpRequestSkill interface, () { const skill new HttpRequestSkillImpl(); expect(skill).toHaveProperty(request); // 运行时检查确保 request 方法签名匹配 expect(typeof skill.request).toBe(function); }); it(should have correct skill.json declaration, () { const skillJsonPath join(__dirname, .., .., .., skill.json); const skillJson JSON.parse(readFileSync(skillJsonPath, utf8)); expect(skillJson.implements).toContain(agent-skills/core:HttpRequestSkill); }); });CI 流水线.github/workflows/ci.yml的关键检查点- name: Validate skill.json schema run: | npm install -g ajv-cli ajv validate -s https://agent-skills.dev/schemas/skill-v1.json -d libs/myorg/skill-http-request/skill.json - name: Check skill.json implements consistency run: | # 提取 skill.json 中的 implements 接口名 IMPLEMENTS$(jq -r .implements[0] libs/myorg/skill-http-request/skill.json | cut -d: -f2) # 检查 ts 文件中是否 export 了同名接口 if ! grep -q export interface ${IMPLEMENTS} libs/myorg/skill-http-request/src/lib/http-request.skill.ts; then echo ERROR: skill.json declares implements ${IMPLEMENTS} but interface not found in .ts file; exit 1; fi这个验证流程把“技能契约”的遵守从开发者的自觉变成了 CI 的强制门禁。每次 PR都必须通过这两道关卡才能合并。这就是agent-skills在大型团队中保持质量的底层逻辑。6. 生产环境陷阱TypeScript 类型擦除与运行时契约漂移即使你严格按照上述步骤开发、测试、发布了一个技能包在生产环境运行时仍可能遭遇一个幽灵般的 BugTypeScript 编译后的 JavaScript 代码与你在.ts文件中定义的接口契约出现了不可见的漂移Drift。这个 Bug 不会报语法错误也不会在单元测试中暴露但它会让 Runtime 的能力发现机制彻底失效。根源在于 TypeScript 的类型擦除Type Erasure机制。我们来看一个典型场景// skills/file-read/src/lib/file-read.skill.ts export interface FileReadSkill extends Skill { read: (params: { path: string; encoding?: utf8 | base64 }) Promise{ content: string }; }TypeScript 编译器会把这个接口完全擦除生成的file-read.skill.js里没有任何关于FileReadSkill的痕迹。而agent-skillsRuntime 的技能发现逻辑依赖于skill.json中的implements字段字符串与代码中实际导出的类/函数的implements属性运行时属性进行匹配。但如果开发者在实现类时忘了手动添加这个属性// 错误没有声明 implements 属性 export class FileReadSkillImpl { async read(params: { path: string; encoding?: utf8 | base64 }) { // ... } }那么 Runtime 在扫描时就会认为这个类“没有实现任何技能接口”从而忽略它。而 TypeScript 编译器对此毫无察觉因为implements是一个运行时字符串属性不是类型系统的一部分。我们团队为此专门开发了一个 Babel 插件agent-skills/babel-plugin-implements-check它在构建阶段自动为所有导出的类注入implements属性// 输入 export class FileReadSkillImpl { async read(params) { /* ... */ } } // 输出Babel 插件注入 export class FileReadSkillImpl { static implements [agent-skills/core:FileReadSkill]; async read(params) { /* ... */ } }这个插件的配置被集成到 Nx 的nx/js:tscexecutor 中成为构建流水线的默认环节。它解决了类型擦除带来的契约漂移问题但引入了新的挑战如何确保static implements数组中的字符串与skill.json中的implements字段完全一致如果skill.json写的是[agent-skills/core:FileReadSkill]而插件注入的是[agent-skills/core:FileReadSkill]少了符号依然会匹配失败。我们的解决方案是“单点权威”Single Source of Truthskill.json是唯一权威所有其他地方都必须从它派生。因此Babel 插件的配置不是硬编码字符串而是读取skill.json文件// babel.config.json { plugins: [ [agent-skills/babel-plugin-implements-check, { skillJsonPath: ./skill.json }] ] }插件在运行时会解析skill.json提取implements数组并将其注入到目标类的static implements属性中。这样skill.json的任何变更都会 100% 同步到运行时代码。最后一个致命陷阱node.js版本兼容性。agent-skills的 Runtime 依赖node:util模块的promisify和types功能。但node:util在 Node.js 16 才成为稳定 API。如果你的engines字段写的是node: 14.0.0那么在 Node.js 14 环境下import { promisify } from node:util就会报错The requested module node:util does not provide an export named promisify。我们的做法是在project.json的buildtarget 中强制指定node版本build: { executor: nx/js:tsc, options: { tsConfig: tsconfig.base.json, outputPath: dist/libs/myorg/skill-http-request }, configurations: { production: { compilerOptions: { target: ES2020, lib: [ES2020, DOM] } } } }并配合 CI 中的nvm use 18.17.0命令确保构建环境与生产环境完全一致。TypeScript 的target和lib选项不是为了兼容老浏览器而是为了精确控制 Node.js 运行时的 API 可用性边界。我在实际操作中发现最有效的防御措施是在每个技能包的README.md顶部用固定格式声明其运行时契约## 运行时契约 - **Node.js 版本**: 18.17.0 - **TypeScript 版本**: 5.0.0 - **核心依赖**: agent-skills/core^2.5.0 - **能力接口**: agent-skills/core:HttpRequestSkill - **能力声明**: skill.json#implements 必须与此处一致这个声明不是文档而是契约快照。当 Runtime 加载技能时会读取这个README.md并与当前环境比对不匹配则拒绝加载。这比任何 CI 检查都更直接、更可靠。
返回列表