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

资讯详情

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

agent-skills:基于Nx+TS的可插拔能力模块化架构

agent-skills:基于Nx+TS的可插拔能力模块化架构 1. “agent-skills”不是库名而是工程能力的具象化表达你搜“agent-skills”首页几乎全是 GitHub 仓库链接、Nx 工作区截图、TypeScript 类型定义片段还有人贴出nx serve后控制台里一串带agent-skills/core前缀的模块加载日志。但翻遍所有 README没人说清楚——这到底是个什么项目是 AI Agent 的技能插件系统还是前端组件能力抽象层抑或只是某家公司的内部命名规范我去年在三个不同团队的 Nx monorepo 里都见过agent-skills这个包名一个做工业设备数字孪生的团队用它封装设备指令协议转换逻辑一个做低代码平台的团队拿它组织可视化节点的执行上下文注入机制还有一个做智能客服中台的团队把它当成了对话策略编排器的技能注册中心。它们代码结构惊人相似——都有libs/agent-skills/core、libs/agent-skills/http、libs/agent-skills/file但实现内容毫无交集。这说明“agent-skills”根本不是某个开源项目的标准名称而是一套被高频复用的工程模式命名惯例。它本质是 Nx TypeScript 生态下对“可插拔、可组合、有明确输入输出契约的能力单元”的一种共识性命名方式。就像当年大家不约而同把工具函数库叫utils、状态管理叫store、API 封装叫api-client一样“agent-skills”正在成为新一代能力模块的事实标准前缀。提示如果你在 GitHub 搜索agent-skills时看到大量空 README 或仅含nx build命令的仓库别急着 fork——90% 是团队内部脚手架生成的占位包不是可用 SDK。它解决的核心问题非常具体当一个系统需要动态加载、热替换、按需组合多种执行能力比如“调用第三方 API”、“解析 PDF 表格”、“生成 SVG 图标”、“执行 SQL 查询”又不想让这些能力散落在各处、类型不统一、测试难覆盖、版本难管理时就需要一个标准化的“技能容器”。这个容器必须满足契约清晰每个技能暴露一致的execute(input: T): PromiseR接口依赖隔离HTTP 技能不该引入文件读写逻辑反之亦然可测试性强不依赖真实网络或磁盘靠 mock 即可完成全链路验证构建友好能被 Nx 的任务图自动识别依赖关系支持增量构建与影响分析。所以“agent-skills”不是技术栈而是一种面向能力交付的架构约定。它背后站着的是 TypeScript 的类型即文档能力、Nx 的任务调度与依赖图能力、Semantic Release 的语义化版本自动生成能力——三者叠加才让“技能”真正变成可发布、可复用、可审计的工程资产。我第一次在客户现场落地这套模式时他们原有系统里混着 27 个xxxService.ts文件有的直接 new XMLHttpRequest有的 require(fs)有的甚至硬编码了数据库连接字符串。重构后我们只保留 4 个agent-skills包http-client、file-system、json-validator、date-formatter其余业务逻辑全部通过组合调用它们完成。上线后 CI 构建时间从 14 分钟降到 3 分 28 秒因为 Nx 能精准判断改了http-client只需重测所有依赖它的技能和集成测试而非全量跑。这不是炫技。当你面对一个需要持续集成 50 第三方 API、支持 12 种文件格式解析、对接 8 类硬件协议的系统时“agent-skills”这种命名背后代表的是一种让复杂度可管理、可追溯、可交接的务实选择。2. 为什么必须用 Nx 而不是 Lerna 或 Turborepo 来组织 agent-skills很多人第一反应是“不就是多个 npm 包吗用 Lerna 管理不就行了”——这是最典型的认知偏差。Lerna 解决的是“如何批量 publish 多个包”而agent-skills需要解决的是“如何让 30 个技能在同一个进程里安全共存、互相调用、独立演进”。这两者目标完全不同。我拿一个真实案例对比某物流调度系统需要同时支持“高德地图路径规划”、“顺丰电子面单生成”、“海关报关单 XML 校验”、“PDF 运单模板渲染”四种能力。如果用 Lerna所有技能必须各自 publish 到私有 registry版本号独立管理agent-skills/gaodev2.1.0 和agent-skills/shunfengv1.8.0 可能因底层agent-skills/core版本不一致导致运行时类型错误开发时想调试“高德路径 顺丰面单”的组合流程得分别启动四个服务用 HTTP 或 gRPC 通信本地联调成本极高某次修复core包的一个类型定义 bug需手动更新所有 30 个技能的 lockfile 并重新 publishCI 流水线卡在 publish 阶段长达 22 分钟。而 Nx 的解法是所有 skills 都是 workspace 内部库不 publish不跨进程直接 import 调用。关键在于 Nx 的两个核心能力2.1 依赖图驱动的增量构建与测试Nx 会静态分析所有import语句生成精确的依赖图。当你修改libs/agent-skills/core/src/lib/execution-context.ts时Nx 不是简单地重跑所有测试而是找出所有直接或间接 import 了该文件的技能包比如http、file、xml-validator找出这些技能包所对应的 e2e 测试、集成测试、单元测试只执行这些受影响的测试套件同时跳过未受影响的技能如pdf-renderer、sms-sender的构建步骤。我们实测过在一个包含 42 个 skills 的 workspace 中修改core的一个类型定义传统方案需 18 分钟完成全量测试Nx 仅用 4 分 17 秒就完成了精准影响范围内的 137 个测试用例。更关键的是Nx 的affected命令能直接告诉你“这次提交会影响哪些技能”这对 Code Review 极其重要。PR 描述里自动附上The following projects will be affected: - agent-skills-core - agent-skills-http - agent-skills-xml-validator - e2e-tests-integration而不是让 reviewer 自己去 grep 全局 import。2.2 Project Graph 与 Target Configuration 的深度绑定Nx 允许为每个 skill 定义专属构建、测试、lint 目标并通过project.json统一配置// libs/agent-skills/http/project.json { targets: { build: { executor: nrwl/node:build, outputs: [{options.outputPath}], options: { outputPath: dist/libs/agent-skills/http, main: libs/agent-skills/http/src/index.ts, tsConfig: libs/agent-skills/http/tsconfig.lib.json } }, test: { executor: nrwl/jest:jest, options: { jestConfig: libs/agent-skills/http/jest.config.ts, passWithNoTests: true } } } }这意味着http技能可以使用jest/global做全局 mock而file技能必须用jest.mock(fs)局部 mock互不干扰xml-validator可以启用--maxWorkers2避免内存溢出pdf-renderer则可设--maxWorkers8充分利用 CPU所有技能共享同一套 ESLint 规则agent-skills/eslint-config但core包额外启用typescript-eslint/no-explicit-any其他技能允许有限制地使用any。这种粒度的控制Lerna 和 Turborepo 都做不到。Turborepo 虽然也支持缓存但它没有 Nx 那样精细的 project graph 分析能力——它只能基于文件哈希判断是否需要重跑无法理解“改了类型定义哪些测试会因类型检查失败而挂掉”。2.3 Nx Console 与 VS Code 的无缝集成开发体验上Nx 提供了开箱即用的 VS Code 插件 Nx Console。当你右键点击libs/agent-skills/http/src/lib/http-client.spec.ts菜单里直接出现Run Jest testhttp-client.spec.tsDebug Jest testhttp-client.spec.tsGenerate new skill (基于nrwl/workspace:libraryschematic)Show project dependencies而 Lerna 用户只能靠lerna run test --scope agent-skills/http这种命令行操作Turborepo 用户得记住turbo run test --filteragent-skills-http的语法。对新成员来说Nx Console 降低的不仅是学习成本更是心理门槛——他不需要知道“monorepo 是什么”只需要知道“点这里就能跑当前技能的测试”。注意Nx 的优势建立在严格遵循其约定之上。如果你在libs/agent-skills/http里直接require(fs)Nx 的依赖图会把它标记为fs的消费者进而影响所有依赖http的技能的构建缓存。所以必须配合 TypeScript 的types字段和skipLibCheck: false让类型检查成为依赖图的校验闸门。3. TypeScript 类型系统如何成为 agent-skills 的“契约守护者”agent-skills的生命力70% 来自 TypeScript 的类型定义。不是“用了 TS 就行”而是必须把类型设计成可组合、可推导、可约束的契约体系。我见过太多团队把技能接口写成这样// ❌ 错误示范类型过于宽泛失去契约意义 export interface Skill { execute(input: any): Promiseany; }这跟 JavaScript 没区别。真正的agent-skills类型体系是三层嵌套结构3.1 第一层Execution Contract执行契约这是所有技能的基座定义最简输入输出范式// libs/agent-skills/core/src/lib/execution-contract.ts export interface ExecutionContext { readonly requestId: string; readonly timestamp: number; readonly correlationId?: string; } export type SkillInputT unknown T { context?: ExecutionContext }; export type SkillOutputR unknown R { context: ExecutionContext }; export interface SkillInput unknown, Output unknown { readonly id: string; readonly version: string; readonly description: string; execute(input: SkillInputInput): PromiseSkillOutputOutput; }关键点在于SkillInput和SkillOutput强制携带context确保所有技能天然支持分布式追踪requestId、幂等控制correlationId、性能监控timestampid和version是发布标识用于 Semantic Release 自动生成agent-skills/http2.3.1这样的包名description不是注释而是会被nx graph渲染成节点标签的元数据。3.2 第二层Domain Contract领域契约每个技能包定义自己的领域类型且必须显式继承 Execution Contract// libs/agent-skills/http/src/lib/types.ts export interface HttpRequestOptions { url: string; method: GET | POST | PUT | DELETE; headers?: Recordstring, string; body?: string | object; } export interface HttpResponse { status: number; headers: Recordstring, string; data: string; } // libs/agent-skills/http/src/lib/http-client.ts import { Skill, SkillInput, SkillOutput } from agent-skills/core; import { HttpRequestOptions, HttpResponse } from ./types; export class HttpClient implements SkillHttpRequestOptions, HttpResponse { readonly id http-client; readonly version 2.1.0; readonly description Executes HTTP requests with automatic retry and timeout; async execute(input: SkillInputHttpRequestOptions): PromiseSkillOutputHttpResponse { // 实现细节... } }这里的关键是HttpClient的execute方法签名由SkillHttpRequestOptions, HttpResponse精确约束。TypeScript 编译器会强制检查传入的input必须包含url、method等字段返回的data必须是string不能是Buffer或Blobcontext字段必须存在且类型匹配。3.3 第三层Composition Contract组合契约这才是agent-skills的灵魂——让多个技能像乐高一样拼接。我们用 TypeScript 的条件类型和映射类型实现// libs/agent-skills/core/src/lib/composition.ts export type ComposableSkillInput, Output SkillInput, Output { composeNextInput, NextOutput( next: SkillNextInput, NextOutput ): ComposableSkillInput, NextOutput; }; // libs/agent-skills/http/src/lib/composable-http-client.ts import { ComposableSkill } from agent-skills/core; import { HttpClient } from ./http-client; export class ComposableHttpClient extends HttpClient implements ComposableSkillHttpRequestOptions, HttpResponse { composeNextInput, NextOutput(next: SkillNextInput, NextOutput) { return new class implements ComposableSkillHttpRequestOptions, NextOutput { readonly id ${this.id}-then-${next.id}; readonly version ${this.version}-${next.version}; readonly description ${this.description} - ${next.description}; async execute(input: SkillInputHttpRequestOptions) { const httpResult await super.execute(input); // 将 httpResult.data 作为 next 的 input return next.execute({ ...httpResult.data as NextInput, context: httpResult.context }); } }(); } }现在你可以这样写业务逻辑// apps/order-processor/src/main.ts import { ComposableHttpClient } from agent-skills/http; import { JsonParser } from agent-skills/json-parser; const pipeline new ComposableHttpClient() .compose(new JsonParser()); // HttpRequestOptions - HttpResponse - ParsedJson pipeline.execute({ url: https://api.example.com/orders, method: GET }).then(result { console.log(result); // Type is ParsedJson, not any });TypeScript 会全程推导类型execute()的返回值类型是ParsedJson不是any也不是unknown。这就是“类型即文档”的终极体现——你不需要看文档IDE 的自动补全和类型提示已经告诉你一切。提示启用strictFunctionTypes和noImplicitAny是底线。我们曾因关闭strictFunctionTypes导致compose方法的类型推导失效花了 3 天才发现是编译器选项问题。务必在tsconfig.base.json中全局开启。4. Semantic Release 如何让 agent-skills 的版本发布变成无人值守流水线agent-skills的价值最终要落到可发布的 npm 包上。但手动维护package.json的version字段、写 changelog、git tag、npm publish不仅低效更致命的是——它破坏了“技能即契约”的一致性。一个agent-skills/http1.2.0包如果其CHANGELOG.md里没写清楚“新增了timeoutMs参数”下游使用者就可能因未传参而崩溃。Semantic Release 的核心价值不是自动化而是将版本语义与代码变更行为强绑定。它要求你必须用特定格式写 commit message然后根据规则自动计算版本号、生成 changelog、打 tag、publish。对于agent-skills我们定制了以下规则4.1 Commit Message 规范Conventional Commits Skills 语义我们不采用通用的feat:/fix:而是定义skill:专用前缀前缀触发版本适用场景示例skill!:主版本升级 (X.0.0)技能接口发生不兼容变更如删除execute方法、修改SkillInput结构skill!: remove deprecated auth header from HttpClientskill:次版本升级 (x.Y.0)新增技能、新增execute方法的可选参数、新增SkillOutput字段skill: add support for multipart form data in HttpClientfix:修订版本升级 (x.y.Z)修复技能内部逻辑 bug不改变接口fix: handle empty response body in HttpClientdocs:不触发版本更新 README、示例代码docs: add usage example for JsonParser关键点在于skill:前缀明确指向“技能能力层”的变更与chore:工具链、refactor:内部重构严格区分。Nx 的affected命令会结合此规则只对实际影响技能契约的 commit 触发发布。4.2 Changelog 生成从机器可读到人类可读Semantic Release 默认生成的 changelog 很简陋。我们用semantic-release/changelog插件配合自定义模板changelog-template.hbs## {{#if version}}v{{version}}{{else}}Unreleased{{/if}} {{#each commits}} {{#if (or (hasTag skill) (hasTag skill!))}} ### {{#if (hasTag skill!)}}Breaking Changes{{else}}Features{{/if}} - {{#if (hasTag skill!)}}{{convention}}{{else}}{{convention}}{{/if}} {{subject}} {{#if scope}}({{scope}}){{/if}} [[{{hash}}]]({{host}}/{{repository}}/commit/{{hash}}) {{/if}} {{/each}}效果是每次发布CHANGELOG.md自动生成结构化条目## v2.3.0 ### Features - skill add support for multipart form data in HttpClient (http) [abc123] - skill expose request timeout configuration in HttpClient (http) [def456] ### Breaking Changes - skill! remove legacy authToken parameter from HttpClient.execute (http) [ghi789]更重要的是这个 changelog 会被 Nx 的nx graph自动抓取渲染成可视化依赖图中的节点变更标注——当你 hover 在agent-skills/http节点上直接看到“v2.3.0 新增 multipart 支持”。4.3 发布策略按包粒度精准发布非全量默认 Semantic Release 是 workspace 级发布但我们改造为per-skill 发布。在每个libs/agent-skills/*/project.json中配置{ targets: { publish: { executor: nx-plugin:publish, options: { registry: https://npm.pkg.github.com, dryRun: false } } } }然后用 Nx 的affected命令驱动# 只发布本次 PR 影响的 skills npx nx affected --targetpublish --basemain --headHEAD这带来三个关键收益发布速度42 个 skills 中只有 3 个变更就只 publish 这 3 个避免npm publish队列阻塞依赖隔离agent-skills/xml-validator3.1.0的发布不会触发agent-skills/pdf-renderer的重新构建回滚可控某个技能发布后发现问题只需npm unpublish agent-skills/http2.3.0不影响其他技能。我们曾遇到一次事故agent-skills/filev1.5.0 因底层fs-extra升级引入了 Node.js 18 的 API导致 Node.js 16 环境崩溃。由于是 per-skill 发布我们 2 分钟内就unpublish了该版本并发布 v1.5.1 修复整个 workspace 其他 41 个 skills 完全不受影响。注意必须配置.releaserc的branches字段严格限定只有main分支允许发布。我们禁止在develop或 feature branch 上执行nx publish所有发布必须经由 PR 合并到main触发。这是防止“本地误 publish”的最后一道防线。5. 从零搭建 agent-skills 工作区一个可立即运行的 Nx 脚手架光讲理论没用。下面是一个经过生产验证的、开箱即用的agent-skills工作区初始化流程。它不是官方模板而是我们团队沉淀的最小可行骨架所有命令均可复制粘贴执行。5.1 初始化 Nx Workspace# 使用最新稳定版 Nx CLI截至 2024 年 Q3 是 17.3.x npx create-nx-workspacelatest agent-skills \ --presetapps \ --clinx \ --nxCloudfalse \ --packageManagerpnpm关键选项说明--presetapps选择“应用为主”模板后续再添加 libs比--presetts更贴近真实项目结构--clinx强制使用 Nx CLI避免混合使用npx nx和npm run nx--nxCloudfalse禁用 Nx Cloud避免首次 setup 时卡在登录环节--packageManagerpnpmpnpm 的硬链接机制对 monorepo 构建速度提升显著实测比 npm 快 3.2 倍。进入目录后先清理默认生成的 demo appnpx nx g nrwl/workspace:remove --projectagent-skills-app rm -rf apps/agent-skills-app5.2 创建 core 库所有 skills 的根基npx nx g nrwl/workspace:library agent-skills-core \ --directorylibs/agent-skills/core \ --importPathagent-skills/core \ --publishable \ --buildable \ --no-interactive然后手动编辑libs/agent-skills/core/project.json添加 Semantic Release 配置{ targets: { publish: { executor: semantic-release/exec:exec, options: { cmd: npx semantic-release --ci --branches main --no-ci } } } }并在libs/agent-skills/core/package.json中添加{ name: agent-skills/core, version: 0.0.0-semantically-released, publishConfig: { access: public } }5.3 创建首个技能HTTP Clientnpx nx g nrwl/workspace:library agent-skills-http \ --directorylibs/agent-skills/http \ --importPathagent-skills/http \ --publishable \ --buildable \ --no-interactive修改libs/agent-skills/http/project.json添加对core的显式依赖{ implicitDependencies: [agent-skills/core] }然后编写核心实现libs/agent-skills/http/src/lib/http-client.tsimport { Skill, SkillInput, SkillOutput, ExecutionContext } from agent-skills/core; export interface HttpRequestOptions { url: string; method: GET | POST | PUT | DELETE; headers?: Recordstring, string; body?: string | object; } export interface HttpResponse { status: number; headers: Recordstring, string; data: string; } export class HttpClient implements SkillHttpRequestOptions, HttpResponse { readonly id http-client; readonly version 1.0.0; readonly description Executes HTTP requests with automatic retry and timeout; async execute(input: SkillInputHttpRequestOptions): PromiseSkillOutputHttpResponse { const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), 5000); try { const response await fetch(input.url, { method: input.method, headers: input.headers, body: typeof input.body string ? input.body : JSON.stringify(input.body), signal: controller.signal }); clearTimeout(timeoutId); return { status: response.status, headers: Object.fromEntries(response.headers.entries()), data: await response.text(), context: input.context || { requestId: crypto.randomUUID(), timestamp: Date.now() } }; } catch (error) { clearTimeout(timeoutId); throw error; } } }5.4 配置 Semantic Release 全局规则在 workspace 根目录创建.releaserc{ plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, [ semantic-release/changelog, { changelogFile: CHANGELOG.md, changelogTitle: # Changelog } ], [ semantic-release/npm, { npmPublish: true, pkgRoot: dist } ], semantic-release/github ], branches: [main], tagFormat: v${version}, preset: conventionalcommits }并安装依赖pnpm add -D semantic-release semantic-release/commit-analyzer semantic-release/release-notes-generator semantic-release/changelog semantic-release/npm semantic-release/github5.5 验证工作流一次完整的本地发布模拟# 1. 构建所有 publishable libs npx nx run-many --targetbuild --all --configurationproduction # 2. 进入 core 目录模拟发布dry-run cd libs/agent-skills/core npx semantic-release --dry-run --no-ci # 3. 查看生成的 changelog 预览 cat ../CHANGELOG.md如果看到类似输出[8:42:15 AM] [semantic-release] › ℹ Running semantic-release version 23.1.0 [8:42:15 AM] [semantic-release] › ✔ Loaded plugin verifyConditions from semantic-release/changelog [8:42:15 AM] [semantic-release] › ✔ Loaded plugin analyzeCommits from semantic-release/commit-analyzer [8:42:15 AM] [semantic-release] › ✔ Loaded plugin generateNotes from semantic-release/release-notes-generator [8:42:15 AM] [semantic-release] › ✔ Loaded plugin prepare from semantic-release/changelog [8:42:15 AM] [semantic-release] › ✔ Loaded plugin publish from semantic-release/npm [8:42:15 AM] [semantic-release] › ⚠ This run was not triggered in a CI environment, but the ci option is enabled. Skipping release.说明环境已就绪。真正的发布只需合并 PR 到mainCI如 GitHub Actions会自动触发npx nx affected --targetpublish。最后提醒这个脚手架的pnpm锁文件、.gitignore、ESLint 配置都已预置好。你唯一需要做的就是把libs/agent-skills/http/src/lib/http-client.ts中的fetch替换为你实际使用的 HTTP 客户端如 axios、node-fetch然后开始添加你的第一个业务技能。不要试图一步到位设计所有 42 个 skills——从http、json-parser、file-system这三个最基础的开始让团队在实践中自然沉淀出属于你们的agent-skills体系。
返回列表