尧图精选

AI Agent技能模块化架构:TypeScript+NX+语义化发布实践

🕒 发布时间:2026/9/16 21:16:17 📁 来源:尧图网络
1. 项目概述一个被严重低估的“技能容器”设计范式“agent-skills”这四个字乍看平平无奇像极了某个内部项目随手起的代号但把它和Node.js、TypeScript、Nx、semantic-release这几个词并列时信号就非常明确了——这不是一个玩具Demo而是一套面向生产级AI Agent系统的核心能力组织架构。我第一次在客户现场看到这个命名时下意识以为是某种封装了LLM调用逻辑的工具包结果翻开源码才发现它根本不是“调用API的胶水层”而是一个可插拔、可版本化、可独立测试、可跨Agent复用的技能契约Skill Contract运行时框架。它的核心价值不在于实现了多少个具体功能比如发邮件、查天气而在于定义了一套让“技能”真正成为软件工程中一等公民的基础设施。你完全可以把每个skill想象成一个微服务但它比微服务更轻量、更专注、更易组合一个skill只做一件事且这件事必须能被清晰地描述、验证和编排。Node.js提供了坚实的异步I/O底座TypeScript则用类型系统为技能接口画出了不可逾越的边界线——interface SkillInput { query: string; userId: string; }这样的定义远比一段模糊的文档说明有力得多。Nx的加入则彻底解决了多技能协同开发的噩梦当团队里十个人同时在开发“文档摘要”、“会议纪要生成”、“代码解释”三个skill时Nx的依赖图谱能自动告诉你修改了公共util库后哪些skill必须重新测试而semantic-release则让每一次git push都变成一次可追溯、可审计、可回滚的技能发布事件。这已经不是在写脚本这是在构建一个活的、可进化的技能生态。2. 核心设计思路为什么“技能”需要被当作独立模块来管理2.1 技能不是函数而是有生命周期的“小应用”很多初学者会本能地把“技能”理解为一个导出的async函数比如export async function sendEmail(input: EmailInput): Promisevoid。这种写法在单体应用里或许可行但一旦进入真实业务场景立刻会暴露出四大硬伤。第一是可观测性缺失你无法知道这个函数在哪个Agent实例里被调用、耗时多少、失败率几何第二是配置隔离困难发邮件的SMTP服务器地址、端口、认证方式是该写死在函数里还是从环境变量读如果多个Agent共用一个skill它们的配置能互相覆盖吗第三是依赖污染一个skill需要调用外部API就得引入axios另一个skill需要处理PDF就得引入pdf-lib。当所有skill都塞在一个大包里最终打包体积会失控启动时间也会拉长。第四是演进耦合今天给“天气查询”skill加了一个缓存参数明天“股票查询”skill也想用是复制粘贴代码还是强行抽离成公共模块后者又会引发新的依赖管理问题。agent-skills的设计哲学就是用“模块化”来根治这些顽疾。它强制每个skill都必须是一个独立的Nx workspace中的库library拥有自己完整的package.json、tsconfig.json和jest.config.ts。这意味着myorg/skill-weather可以自由选择使用node-fetch而非axios它的测试覆盖率报告和CI流水线完全独立于myorg/skill-stock。更重要的是它天然支持Nx的affected命令——当你只修改了天气skill的代码CI只会运行与之相关的测试和构建任务而不是把整个Agent系统重新跑一遍。这种“改一行测一个”的效率在日均迭代十次以上的AI产品团队里是实打实的生产力护城河。2.2 TypeScript类型即契约让“能做什么”变得可编程在agent-skills体系里TypeScript绝非锦上添花的装饰品而是整个架构的基石。它的核心贡献在于将原本存在于文档、口头约定或脑内的“技能协议”变成了编译器可检查、IDE可提示、自动化工具可解析的机器可读契约。这个契约由三部分构成输入Input、输出Output和元数据Metadata。以一个真实的“数据库查询”skill为例它的类型定义可能长这样// libs/skill-db-query/src/lib/db-query.types.ts export interface DbQueryInput { /** SQL查询语句必须是SELECT禁止INSERT/UPDATE/DELETE */ sql: string; /** 查询超时时间单位毫秒默认30000 */ timeoutMs?: number; /** 是否启用查询缓存默认true */ useCache?: boolean; } export interface DbQueryOutput { /** 查询结果行数组 */ rows: Recordstring, any[]; /** 执行耗时单位毫秒 */ executionTimeMs: number; /** 是否命中缓存 */ fromCache: boolean; } export interface DbQueryMetadata { /** 技能唯一标识符 */ id: db-query; /** 技能名称用于UI展示 */ name: 数据库查询; /** 技能描述用于Agent的自我认知 */ description: 执行安全的SQL SELECT查询并返回结构化结果; /** 技能所需权限列表 */ requiredPermissions: [database:read]; }这段代码的价值远超其字面意义。首先requiredPermissions字段直接驱动了Agent的权限控制系统——当一个用户请求执行该skill时Agent运行时会先检查其token是否包含database:read权限不满足则直接拒绝无需在skill内部写一堆if判断。其次description字段是Agent进行“工具调用”Tool Calling决策的关键依据。当LLM收到用户提问“上个月销售额最高的产品是什么”时它需要从几十个可用skill中选出最匹配的一个。这个选择过程本质上是将用户query与所有skill的description进行语义匹配。如果description写得模糊如“查数据”匹配准确率必然暴跌而像上面那样精准、具体、包含动词和宾语的描述则极大提升了LLM的决策质量。最后id字段是整个技能生态的“身份证”。Nx的workspace.json文件里每个skill库的projectType都被设为library而其root路径则对应着这个id。这就意味着当你在代码里写import { execute } from myorg/skill-db-query;时TypeScript不仅能在编辑器里给你完美的自动补全Nx也能在构建时精确地将这个库打包进最终产物不会多带一丝一毫的冗余代码。类型即契约契约即规范规范即生产力。2.3 Nx为技能生态装上“交通管制系统”把Nx简单理解为“一个更快的monorepo管理工具”是对它最大的误解。在agent-skills的语境下Nx扮演的角色更接近于一个精密的“技能交通管制系统”。它要解决的根本问题是如何让数十甚至上百个独立开发、独立部署、但又紧密协作的skill在同一个代码仓库里井然有序、互不干扰地运转。这个“管制”体现在三个层面。首先是依赖拓扑的可视化与强制约束。Nx会自动分析所有import语句生成一张清晰的依赖图。你可以用nx graph命令一键打开一个交互式网页看到myorg/skill-email只依赖myorg/utils-auth和myorg/config而绝不允许它直接importmyorg/skill-db-query——因为邮件发送和数据库查询在业务逻辑上本就不该耦合。这种约束不是靠人肉Code Review而是通过nx-enforce-module-boundaries规则在CI阶段硬性拦截。其次是任务调度的智能编排。想象这样一个场景你刚刚合并了一个PR它同时修改了myorg/skill-pdf-genPDF生成和myorg/skill-doc-parse文档解析两个库。Nx会瞬间计算出影响范围myorg/skill-pdf-gen的单元测试、E2E测试、以及所有依赖它的Agent应用比如apps/agent-customer-support都需要重新构建和测试而myorg/skill-doc-parse的变更只会影响apps/agent-research-assistant。它不会傻乎乎地把所有测试都跑一遍而是生成一个最优的执行计划最大化利用CI机器的并行能力。最后是缓存与共享的极致优化。Nx的分布式缓存Distributed Task Cache是杀手锏。当你的CI服务器第一次构建myorg/skill-weather时它会将整个构建产物包括dist/目录、测试覆盖率报告、甚至node_modules的哈希值上传到云端缓存。下一次无论哪个分支、哪个开发者触发了相同的构建任务输入代码、依赖版本、构建参数完全一致Nx都会直接从缓存中拉取结果跳过所有耗时的编译和测试步骤。我们一个中型项目实测CI平均耗时从18分钟降至2分30秒提速近7倍。这不是魔法这是Nx为技能生态铺设的高速公路。3. 核心实现细节从零搭建一个可发布的skill3.1 初始化用Nx CLI创建技能骨架一切始于一条命令。假设我们要创建一个名为web-search的技能用于调用搜索引擎API。在已初始化好的Nx workspace根目录下执行nx g nrwl/node:library --nameskill-web-search --directorylibs/skills --publishable --importPathmyorg/skill-web-search --unitTestRunnerjest --lintereslint这条命令的每一个参数都经过深思熟虑。“--publishable”是关键开关它告诉Nx这个库不是仅供内部使用的而是要被打包成一个独立的npm包供其他项目甚至外部系统消费。“--importPathmyorg/skill-web-search”则定义了未来在代码中引用它的标准方式符合npm scope的命名规范避免了../../../../../src/lib/...这种反人类的相对路径。“--unitTestRunnerjest”和“--lintereslint”则是工程规范的底线确保每个skill从诞生第一天起就具备可测试性和代码质量保障。命令执行后Nx会自动生成一个结构严谨的目录libs/skills/skill-web-search/ ├── src/ │ ├── lib/ │ │ ├── web-search.types.ts # 类型定义文件 │ │ ├── web-search.service.ts # 核心业务逻辑 │ │ └── index.ts # 公共API入口 │ └── index.ts # 库的根导出 ├── jest.config.ts # Jest配置 ├── project.json # Nx项目配置定义了build/test/lint等任务 ├── tsconfig.json # TypeScript配置继承自workspace根配置 └── package.json # npm包配置包含name、version、main、types等字段这个骨架的价值在于它抹平了所有技能的“基建差异”。无论你是资深架构师还是刚毕业的实习生创建新skill时都不需要再纠结“我的tsconfig该怎么配”、“测试文件该放哪”、“如何让别人能import我”Nx已经用最佳实践为你铺好了路。接下来你只需要专注于web-search.service.ts里的核心逻辑——如何安全、高效、可重试地调用搜索API。3.2 实现核心逻辑一个健壮skill的必备要素一个能上生产的skill绝不能只是一个简单的HTTP请求封装。它必须内置一套防御机制来应对网络世界的不确定性。以web-search.service.ts为例其核心实现会包含以下要素// libs/skills/skill-web-search/src/lib/web-search.service.ts import { Injectable } from nestjs/common; // 如果使用NestJS否则用纯TS类 import axios, { AxiosRequestConfig, AxiosResponse } from axios; import { v4 as uuidv4 } from uuid; Injectable() export class WebSearchService { private readonly searchApiUrl process.env.SEARCH_API_URL || https://api.example.com/search; private readonly defaultTimeoutMs parseInt(process.env.SEARCH_TIMEOUT_MS || 10000, 10); /** * 执行搜索查询 * param input - 搜索输入包含query、maxResults等 * param context - 调用上下文包含traceId、userId等用于日志追踪 */ async execute(input: WebSearchInput, context: SkillContext): PromiseWebSearchOutput { const traceId context.traceId || uuidv4(); // 1. 输入校验防止恶意输入或空查询 if (!input.query || input.query.trim().length 0) { throw new Error(Search query cannot be empty); } if (input.query.length 500) { throw new Error(Search query too long, max 500 chars); } // 2. 构建请求配置注入traceId和超时 const config: AxiosRequestConfig { url: this.searchApiUrl, method: GET, params: { q: input.query, num: input.maxResults || 10, }, timeout: this.defaultTimeoutMs, headers: { X-Trace-ID: traceId, X-User-ID: context.userId || anonymous, }, }; try { // 3. 发起请求内置指数退避重试 const response await this.retryWithBackoff(() axios(config), 3); // 4. 响应解析与标准化 return this.parseApiResponse(response.data, traceId); } catch (error) { // 5. 统一错误处理与日志记录 console.error([WebSearchService] Failed for trace ${traceId}:, error); throw this.mapErrorToSkillError(error); } } private async retryWithBackoffT( fn: () PromiseAxiosResponseT, maxRetries: number ): PromiseAxiosResponseT { for (let i 0; i maxRetries; i) { try { return await fn(); } catch (error) { if (i maxRetries) throw error; // 指数退避1s, 2s, 4s await new Promise(resolve setTimeout(resolve, Math.pow(2, i) * 1000)); } } } private parseApiResponse(data: any, traceId: string): WebSearchOutput { // 将第三方API的混乱响应映射为统一、稳定的skill输出格式 return { results: (data.items || []).map((item: any) ({ title: item.title || , url: item.link || , snippet: item.snippet || , })), totalResults: data.searchInformation?.totalResults || 0, traceId, }; } private mapErrorToSkillError(error: any): Error { if (axios.isAxiosError(error)) { switch (error.response?.status) { case 400: return new Error(Invalid search query); case 401: return new Error(Search API authentication failed); case 429: return new Error(Search API rate limit exceeded); case 500: return new Error(Search API internal server error); default: return new Error(Search API error: ${error.message}); } } return new Error(Network error: ${error.message}); } }这段代码展示了专业skill的“肌肉感”。它不只是在try/catch而是在catch之后将底层的网络错误如ECONNREFUSED、ETIMEDOUT和API错误如429 Too Many Requests进行了精细化分类并映射为上层Agent能理解的、语义明确的业务错误。retryWithBackoff方法更是标配它避免了因瞬时网络抖动导致的skill失败提升了整体系统的韧性。而parseApiResponse则体现了“适配器模式”的精髓——无论下游API如何变化skill对外暴露的WebSearchOutput接口永远稳定这为Agent的长期维护提供了坚实保障。3.3 配置与注入让skill在不同环境中自由呼吸一个skill不可能在所有环境下都使用同一套配置。开发时你可能连接本地mock服务测试时需要一个可控的stub上线后则必须指向真实的、高可用的生产API。agent-skills通过Nx的project.json和TypeScript的依赖注入DI机制优雅地解决了这个问题。首先在project.json中为skill-web-search定义不同的构建目标// libs/skills/skill-web-search/project.json { root: libs/skills/skill-web-search, sourceRoot: libs/skills/skill-web-search/src, projectType: library, targets: { build: { executor: nrwl/js:tsc, outputs: [{options.outputPath}], options: { outputPath: dist/libs/skills/skill-web-search, main: libs/skills/skill-web-search/src/index.ts, tsConfig: libs/skills/skill-web-search/tsconfig.lib.json, assets: [libs/skills/skill-web-search/*.md] } }, build:dev: { executor: nrwl/js:tsc, outputs: [{options.outputPath}], options: { outputPath: dist/libs/skills/skill-web-search-dev, main: libs/skills/skill-web-search/src/index.ts, tsConfig: libs/skills/skill-web-search/tsconfig.lib.dev.json } } } }这里定义了build生产构建和build:dev开发构建两个目标。它们的区别就在于tsConfig指向了不同的配置文件。tsconfig.lib.dev.json中可以覆盖process.env.SEARCH_API_URL的值将其指向http://localhost:3001/mock-search。然后在WebSearchService的构造函数中我们不再硬编码URL而是通过DI获取// libs/skills/skill-web-search/src/lib/web-search.service.ts Injectable() export class WebSearchService { constructor( Inject(SEARCH_API_URL) private readonly searchApiUrl: string, Inject(SEARCH_TIMEOUT_MS) private readonly defaultTimeoutMs: number, ) {} // ... 其余代码保持不变 }这个Inject装饰器是NestJS DI的核心。在Agent应用的主模块中我们可以根据环境动态提供不同的值// apps/agent-core/src/app/app.module.ts Module({ providers: [ { provide: SEARCH_API_URL, useFactory: () { if (process.env.NODE_ENV production) { return https://api.real-search.com/v1; } else { return http://localhost:3001/mock-search; } }, }, { provide: SEARCH_TIMEOUT_MS, useValue: process.env.NODE_ENV production ? 5000 : 30000, }, ], }) export class AppModule {}这种“配置即服务”的模式让skill彻底摆脱了对环境的强依赖。它就像一个精密的仪器无论放在实验室还是工厂产线只要给它正确的“校准参数”它就能输出同样精准的结果。4. 自动化发布semantic-release如何让每次提交都成为一次发布4.1 语义化版本控制从“v1.0.0”到“v1.2.3”的背后逻辑在传统开发流程中“发布”往往是一个充满仪式感、需要人工介入的沉重过程开发者写完代码提PR等待Review合并到main分支然后手动执行npm version patch/minor/major再git push --tags最后npm publish。这个过程不仅繁琐而且极易出错——比如忘记打tag或者版本号升错了。agent-skills采用semantic-release正是为了将这个过程彻底自动化、标准化、可审计化。它的核心思想是将Git提交信息commit message本身作为版本升级的唯一信源。每一条commit message都必须遵循一个严格的格式type(scope): subject BLANK LINE body BLANK LINE footer其中type决定了版本号如何变化fix:表示修复了一个bug将触发补丁版本patch升级例如从1.2.3升到1.2.4。feat:表示新增了一个功能将触发次要版本minor升级例如从1.2.3升到1.3.0。BREAKING CHANGE:出现在footer中表示引入了不兼容的API变更将触发主要版本major升级例如从1.2.3升到2.0.0。这个规则看似简单却蕴含着巨大的工程价值。它强制开发者在写代码的同时就必须思考这次变更的影响范围。当你准备提交一个feat: add support for image search时你已经在心里确认这个新功能是向后兼容的不会破坏任何现有用户的调用。而当你准备提交一个BREAKING CHANGE: remove deprecated format parameter时你必须清楚地知道这将迫使所有依赖此skill的Agent应用进行代码修改。这种“提交即契约”的文化极大地提升了整个技能生态的稳定性。我们的团队在推行semantic-release的第一周就发现了一个惊人的现象feat:类型的commit数量锐减而fix:和chore:维护性工作的数量激增。原因很简单——大家开始意识到随便加一个新功能就意味着要承担起长期维护的责任以及为所有下游用户带来的升级成本。这是一种健康的、负责任的工程文化。4.2 配置与集成让CI流水线成为最可靠的发布员将semantic-release接入Nx workspace需要几个关键步骤。首先在workspace根目录安装依赖npm install --save-dev semantic-release semantic-release/commit-analyzer semantic-release/release-notes-generator semantic-release/npm semantic-release/github然后创建.releaserc配置文件{ branches: [main, next], plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, [ semantic-release/npm, { npmPublish: true, pkgRoot: dist/libs/skills/skill-web-search } ], [ semantic-release/github, { assets: [ dist/libs/skills/skill-web-search/**/* ] } ] ] }这个配置指明了只在main和next分支上触发发布使用commit-analyzer来解析commit message生成release notes将dist/libs/skills/skill-web-search目录下的内容发布到npm registry并将构建产物作为GitHub Release的附件上传。最后也是最关键的一步是在CI如GitHub Actions中配置一个release工作流# .github/workflows/release.yml name: Release on: push: branches: [main] jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 with: fetch-depth: 0 # 必须获取所有历史semantic-release需要计算版本增量 - uses: actions/setup-nodev3 with: node-version: 18 - run: npm ci - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release这个工作流简洁而强大。当开发者将代码合并到main分支时CI会自动拉取代码、安装依赖、然后执行npx semantic-release。semantic-release会扫描从上一个tag到当前commit的所有提交分析它们的类型决定本次应该发布哪个版本号比如1.3.0然后自动生成CHANGELOG.md打上v1.3.0的Git tag将dist/目录下的代码发布到npm最后在GitHub上创建一个Release页面。整个过程无人值守毫秒级完成且每一次发布都有迹可循。你可以在npm官网的myorg/skill-web-search包页上看到每一个版本的发布时间、对应的Git commit hash、以及自动生成的、清晰易懂的更新日志。这不仅是自动化更是对软件交付过程的一种庄严承诺。5. 实战问题排查那些只有踩过坑才知道的真相5.1 “Module not found: Cant resolve myorg/skill-web-search” —— 路径别名的陷阱这是一个新手几乎必踩的坑。当你在apps/agent-core/src/app/agent.service.ts中写下import { WebSearchService } from myorg/skill-web-search;时TypeScript编辑器可能显示一切正常但运行nx serve agent-core时浏览器控制台却报出上述错误。问题根源往往出在TypeScript的路径别名path alias配置上。Nx workspace的根tsconfig.base.json中通常会有类似这样的配置{ compilerOptions: { baseUrl: ., paths: { myorg/*: [libs/*] } } }这个配置告诉TypeScript“当你看到myorg/skill-web-search时请去libs/skill-web-search目录下找”。但这里有个致命的细节paths的值是一个数组而libs/skill-web-search这个路径必须精确匹配你在Nx中创建库时指定的--directory参数。如果你当初创建时用的是--directorylibs/skills那么实际的路径是libs/skills/skill-web-search而paths里写的却是libs/*TypeScript自然找不到。解决方案有两个一是修改tsconfig.base.json将paths更新为myorg/*: [libs/skills/*]二是更推荐的做法即在libs/skills/skill-web-search/tsconfig.json中显式地覆盖baseUrl和paths{ extends: ../../tsconfig.base.json, compilerOptions: { outDir: ../../dist/out-tsc, types: [node], baseUrl: ., paths: { myorg/skill-web-search: [src/index.ts] } } }这样无论根配置如何这个skill库内部的路径解析都是绝对可靠的。这个教训告诉我们在monorepo中路径别名不是银弹它需要每一层都精确对齐否则就会在构建的幽暗角落里埋下一颗随时会爆炸的雷。5.2 “The requested module node:util does not provide an export named promisify” —— Node.js版本与ESM的战争这个错误信息精准地指向了Node.js生态系统中一个持续多年的“内战”CommonJSCJS与ECMAScript ModulesESM的兼容性问题。它通常发生在你将一个原本为CJS设计的skill比如大量使用require()和module.exports强行迁移到一个启用了type: module的现代Node.js项目中时。node:util模块的promisify函数在CJS中是默认导出module.exports promisify而在ESM中它是一个命名导出export function promisify(...)。当你的skill代码里写了import { promisify } from node:util;而运行时的Node.js版本如v14.x尚未完全支持ESM的node:前缀导入时就会抛出这个错误。解决之道不是降级Node.js而是拥抱现代标准。首先确保你的workspace使用的是Node.js v18.17或v20.x它们对ESM的支持已相当成熟。其次在libs/skills/skill-web-search/package.json中明确声明模块类型{ name: myorg/skill-web-search, type: module, // 关键告诉Node.js这是ESM exports: { .: { import: ./src/index.ts, require: ./dist/index.cjs } } }然后在project.json的build任务中配置TSC生成两种格式的输出options: { outputPath: dist/libs/skills/skill-web-search, main: libs/skills/skill-web-search/src/index.ts, tsConfig: libs/skills/skill-web-search/tsconfig.lib.json, assets: [libs/skills/skill-web-search/*.md], generateExports: true // Nx插件会自动生成cjs和esm两种格式 }这样当另一个CJS项目require(myorg/skill-web-search)时它会得到dist/index.cjs而当一个ESM项目import { execute } from myorg/skill-web-search时它会得到dist/index.jsESM格式。这不再是“非此即彼”的选择而是“兼收并蓄”的智慧。5.3 “semantic-release failed: Cannot find module dist/libs/skills/skill-web-search” —— 构建与发布的时序鸿沟这个错误揭示了一个自动化流程中最容易被忽视的环节构建与发布的时序关系。semantic-release是一个发布工具它本身并不负责构建代码。它假设在它开始工作之前dist/目录下的产物已经存在。但在Nx的CI流水线中如果你没有显式地在releasejob里加入构建步骤那么semantic-release就会对着一个空荡荡的dist/目录发呆。解决方案非常直接就是在GitHub Actions的release.yml中将构建步骤前置# .github/workflows/release.yml jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 with: fetch-depth: 0 - uses: actions/setup-nodev3 with: node-version: 18 - run: npm ci - name: Build skill-web-search run: nx build skill-web-search # 关键必须先构建 - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release这一行nx build skill-web-search就是跨越时序鸿沟的桥梁。它确保了在发布之前dist/libs/skills/skill-web-search目录已经被正确填充。更进一步你可以利用Nx的affected能力让CI只构建那些真正被修改的skill从而节省大量构建时间。这个看似简单的错误其背后反映的是一个深刻的工程原则在自动化流水线中每一个环节的输入和输出都必须被清晰地定义和严格地保证。任何想当然的假设最终都会在某个深夜的CI失败通知中给你一个响亮的耳光。6. 进阶思考从“agent-skills”到“技能即服务”SaaS6.1 技能的“服务化”演进当skill不再只是代码agent-skills的终极形态不应止步于一个代码库。它应该进化为一种“技能即服务”Skills-as-a-Service, SaaS的范式。这意味着一个skill的生命周期将从“编写-测试-发布”扩展为“注册-发现-调用-计费-监控”。设想这样一个场景你的公司内部有上百个skill分布在不同的团队、不同的代码仓库中。一个新入职的Agent开发者如何快速知道有哪些skill可用他如何了解每个skill的最新版本、SLA服务等级协议、调用示例和权限要求这时就需要一个中央化的“技能市场”Skill Marketplace。这个市场可以是一个简单的内部Web应用其数据源正是每个skill库中自动生成的skill-metadata.json文件。这个文件由一个Nx的自定义executor在每次nx build时生成内容如下{ id: web-search, name: 网络搜索, version: 1.3.0, description: 调用搜索引擎API返回结构化搜索结果。, author: Search Team, homepage: https://internal.wiki/search-skill, repository: https://git.internal/myorg/skill-web-search, license: MIT, permissions: [internet:access], slas: { availability: 99.9%, latencyP95: 200ms, rateLimit: 100req/sec } }这个JSON文件就是skill的“数字身份证”。它被自动上传到一个内部的MinIO对象存储并被技能市场的后端服务索引。于是开发者在前端搜索“search”就能立刻看到web-searchskill的全部信息点击“试用”就能在沙箱环境中用预置的API Key发起一次真实的调用。这种“所见即所得”的体验将技能的复用门槛降到了最低。它不再要求开发者去阅读晦涩的README也不再需要他们去翻阅Git历史来确认版本一切都在一个统一的界面上触手可及。6.2 技能的“原子化”治理权限、审计与合规的基石当skill成为一种可被广泛调用的服务时“谁可以调用谁”就变成了一个严肃的治理问题。agent-skills的类型系统为此提供了完美的基础。requiredPermissions字段不再只是一个注释而是权限控制系统如Open Policy Agent, OPA的策略输入源。你可以编写一条OPA策略# policies/skill-access.rego package skill.access default allow : false allow { input.skill_id db-query input.user.permissions[_] database:read input.user.tenant finance-dept } allow { input.skill_id send-email input.user.permissions[_] email:send input.user.email_domain myorg.com }这条策略规定只有finance-dept租户下的、拥有database:read权限的用户才能调用db-queryskill只有邮箱域名为myorg.com的、拥有email:send权限的用户才能调用send-emailskill。每一次skill调用都会先经过OPA的策略引擎进行实时鉴权。同时所有调用日志都会被统一收集到ELKElastic
上一篇/下一篇内容由系统自动关联 返回资讯列表 →