
1. “agent-skills”不是项目名而是一套可复用的智能体能力工程规范你第一次在 GitHub 上搜到agent-skills这个词大概率是在某个 TypeScript Nx 构建的 AI 工程仓库里——它不带 README没有独立 npm 包甚至没有package.json的name字段。它藏在/libs/agent-skills目录下目录结构干净得像手术室src/,src/lib/,src/lib/core/,src/lib/tools/,src/lib/memory/每个子模块都配着.spec.ts和index.ts。它不是框架不是 SDK更不是玩具 demo它是我在过去三年里带团队落地 7 个生产级 LLM 智能体系统后从血里熬出来的能力抽象层契约。为什么需要它因为所有“让大模型做点事”的项目最终都会撞上同一堵墙工具调用Tool Calling逻辑散落在各个 agent 实例里改一个天气查询接口要同步改 4 个地方记忆管理Memory要么全靠Mapstring, any硬塞要么直接耦合 Redis 客户端测试时 mock 成灾难多 step 的规划Planning流程写成 if-else 嵌套debug 时得靠 console.log 画流程图最致命的是——当你要把一个已上线的客服 agent快速拆解出“订单查询”“物流追踪”“退换货政策”三个独立技能模块供其他业务线复用时发现代码根本没法拆。agent-skills就是为解决这四个问题而生的。它不封装 LLM 调用那是langchain/core或llamaindex的事也不处理 prompt 工程那是promptfoo或自研 DSL 的地盘它只干一件事定义“一个技能该长什么样”并提供开箱即用的骨架、类型契约和生命周期钩子。关键词TypeScript是它的骨骼——所有接口、泛型约束、错误类型都强制收敛Nx是它的循环系统——多 workspace 下的依赖拓扑、构建缓存、增量测试全部自动对齐semantic-release是它的呼吸节奏——每次feat(tool/weather)提交自动发布myorg/agent-skills-tool-weather1.2.0版本号背后是语义化的变更日志不是人工手敲的v1.1.13-beta.2。它解决的不是“怎么调用大模型”而是“怎么让大模型的能力可维护、可测试、可组合、可审计”。如果你正在用 NestJS 写 agent 后端用 Vue 做前端编排界面用 Spring Boot 对接内部 ERP那么agent-skills就是你跨技术栈的通用语言——后端工程师写的OrderQuerySkill前端工程师可以直接 import 并传入uiConfig: { showLoading: true }测试工程师用jest.mock(myorg/agent-skills-core)就能隔离验证整个技能链路。这不是理想主义是我们去年在金融风控场景里跑通的真实路径3 个团队5 种语言栈共用同一套agent-skills类型定义API 文档自动生成变更影响面自动分析。提示不要把它当成“又一个 LLM 工具库”去 npm install。它的价值不在npm publish那一刻而在你第一次为PaymentValidateSkill写完canExecute(context: SkillContext): boolean方法并发现这个布尔判断逻辑被 3 个不同 agent 的路由层同时复用时——那种“终于不用再复制粘贴 if 条件”的轻松感。2. 核心契约设计为什么SkillTInput, TOutput必须带泛型且execute()返回PromiseSkillResultTOutputagent-skills的灵魂藏在libs/agent-skills/src/lib/core/skill.ts这 87 行代码里。它没用任何花哨装饰器没引入 rxjs甚至没用class关键字我们用interfaceconst工厂函数。但就是这几十行决定了整个能力体系的扩展边界。先看最简契约export interface SkillTInput unknown, TOutput unknown { id: string; name: string; description: string; canExecute(context: SkillContext): boolean; execute(input: TInput, context: SkillContext): PromiseSkillResultTOutput; } export interface SkillResultT unknown { success: boolean; data?: T; error?: SkillError; metadata?: Recordstring, unknown; }表面看很朴素但每个字段都有明确的工程意图。我们逐个拆解2.1id与name的分离解决运行时冲突与 UI 展示的双重需求id是机器可读的唯一标识符强制要求符合^[a-z][a-z0-9]*(?:-[a-z0-9])*$正则小写字母开头连字符分隔无空格。为什么不用 UUID因为id要参与构建技能调用链路的 traceId例如order-query-v2比c3b4e8f1-2a9d-4b6c-8e1f-0a2b3c4d5e6f更易 debug更重要的是Nx 的依赖图谱Dependency Graph会把id当作模块别名nx graph --group-by-directory时能清晰看到agent-skills-tool-order依赖agent-skills-core。而name是面向用户的展示名允许中文、空格、emoji如 订单查询实时它只用于前端下拉菜单或日志打印绝不参与任何逻辑判断。这种分离避免了早期项目里常见的坑某次重构把weather-tool改成weather-api-v2结果前端配置里还写着旧 id报错信息却是Cannot find skill weather-tool排查半小时才发现是配置文件没同步。2.2canExecute()把“是否执行”从execute()里剥离是性能与可观测性的分水岭很多团队把权限校验、前置条件检查全塞进execute()开头// ❌ 反模式所有逻辑挤在 execute 里 async execute(input: OrderQueryInput) { if (!input.orderId) throw new Error(Missing orderId); if (!this.authService.hasPermission(ORDER_READ)) throw new Error(No permission); // ... real logic }问题在哪第一execute()必须是异步的哪怕只是做同步判断也要await Promise.resolve()白白增加 event loop 压力第二监控系统想统计“哪些技能被频繁拒绝”只能从 error 日志里正则匹配漏报率高第三前端想做灰度按钮如“订单查询”按钮在用户无权限时置灰还得调一次execute()才知道能不能点——这显然不合理。agent-skills强制canExecute()是同步方法且必须返回布尔值。它接收SkillContext里面包含userRole、tenantId、featureFlags等上下文快照。实操中我们把它设计成纯函数// ✅ 正确canExecute 是纯同步判断 canExecute(context: SkillContext): boolean { return ( context.featureFlags[enable-order-query] context.userRole customer-service !!context.metadata?.sessionId ); }这样前端可以在渲染时直接调用skill.canExecute(context)决定按钮状态监控系统在 agent 路由层统一拦截记录canExecute: false的频次与原因甚至 CI 流程里nx affected --targetlint会扫描所有canExecute()方法确保没有硬编码的return true——这是我们在支付网关项目里踩过的坑某次紧急上线开发为赶进度把风控技能的canExecute写成return true导致沙箱环境误放行了 200 笔高风险交易。2.3execute()的泛型与SkillResult消灭any让错误成为一等公民execute(input: TInput, context: SkillContext): PromiseSkillResultTOutput这个签名是 TypeScript 类型安全的最后防线。TInput和TOutput不是摆设——它们强制你在index.ts的导出层就声明清楚// libs/agent-skills-tool-weather/src/index.ts export * from ./lib/weather-skill; export type WeatherInput { city: string; days: number }; export type WeatherOutput { forecast: Array{ date: string; temp: number; condition: sunny | rainy }; source: openweathermap | accuweather; };然后在技能实现里精准绑定export class WeatherSkill implements SkillWeatherInput, WeatherOutput { // ... async execute(input: WeatherInput, context: SkillContext) { const data await this.api.getForecast(input.city, input.days); return { success: true, data: { forecast: data.items.map(i ({ date: i.date, temp: i.temp, condition: i.cond })), source: openweathermap } }; } }SkillResult的设计更关键。它不允许data和error同时存在TypeScript 的 discriminated union且error字段是SkillError类型而非string或Error实例export interface SkillError { code: string; // 如 TOOL_UNAVAILABLE, RATE_LIMIT_EXCEEDED message: string; // 用户友好的提示如 天气服务暂时不可用请稍后再试 details?: Recordstring, unknown; // 技术细节如 { statusCode: 503, retryAfter: 30 } severity: low | medium | high | critical; }这个设计直接解决了两个高频痛点一是前端不用再写if (res.data) {...} else if (res.error) {...}的冗余判断TypeScript 编译器会强制你处理success分支二是错误分类标准化后SRE 团队可以基于code字段配置告警规则——比如所有code: DB_CONNECTION_TIMEOUT的错误自动触发数据库连接池扩容脚本而不是等业务方提工单。注意SkillResult的metadata字段是留给 tracing 的。我们约定所有技能在execute()开头生成spanId写入metadata.traceId这样 Jaeger 里就能看到一条完整的技能调用链AgentOrchestrator → OrderQuerySkill → ERPAdapter → PaymentValidateSkill每个环节的耗时、输入输出、错误码一目了然。这个约定写在agent-skills-core的README.md里且nx lint会检查每个execute()是否写了metadata.traceId。3. Nx 工作区架构为什么libs/agent-skills-core必须是 workspace root 的 direct dependencyagent-skills的物理结构是它能被大规模复用的底层保障。我们不用 monorepo 工具链如 Turborepo坚持用 Nx 2024 版本v18核心原因只有一个Nx 的 project graph 是唯一能精确表达“技能能力”与“执行环境”之间依赖关系的工具。先看标准目录布局/libs /agent-skills-core # 基础契约、类型、工具函数无外部依赖 /agent-skills-tools # 工具类技能集合依赖 core axios /agent-skills-memory # 记忆管理技能依赖 core redis client /agent-skills-planning # 规划类技能依赖 core langchain/core /agent-skills-tool-weather # 具体天气技能依赖 core tools /agent-skills-tool-order # 具体订单技能依赖 core tools memory /apps /agent-orchestrator # 主 agent 服务NestJS依赖 core tools memory planning /agent-dashboard # 前端管理台Vue依赖 core 的类型定义关键点在于agent-skills-core必须被所有其他agent-skills-*库直接依赖且不能通过agent-skills-tools间接传递。为什么因为core里定义的SkillTInput, TOutput是所有技能的根类型如果tool-order依赖tools而tools依赖core那么tool-order的execute()方法签名里TInput的类型推导就会变成import(agent-skills-tools).OrderInput而非import(agent-skills-core).SkillInput——这会导致跨库类型不兼容agent-orchestrator无法安全地将tool-order的实例传给泛型函数runSkillSkillOrderInput, OrderOutput(skill: SkillOrderInput, OrderOutput)。Nx 的nx graph命令能可视化这个约束nx graph --focusagent-skills-core --includeagent-skills-tools,agent-skills-tool-order你会看到agent-skills-core是中心节点所有技能库都指向它但agent-skills-tools和agent-skills-tool-order之间没有连线。这个图不是画出来的是 Nx 从project.json的dependencies字段实时解析的。我们甚至在 CI 里加了校验脚本# .github/workflows/check-skills-deps.yml - name: Validate agent-skills dependencies run: | for lib in $(ls libs/agent-skills-*); do if [[ $lib ! libs/agent-skills-core ]]; then deps$(jq -r .dependencies | keys[] $lib/project.json 2/dev/null) if ! echo $deps | grep -q agent-skills-core; then echo ERROR: $lib must depend on agent-skills-core directly exit 1 fi fi done这个看似严苛的约束换来的是真正的类型安全。举个真实案例去年我们把agent-skills-tool-order从 v1 升级到 v2OrderInput结构增加了tenantId字段。由于所有消费方agent-orchestrator,agent-dashboard,agent-skills-planning都直接依赖agent-skills-coreTypeScript 编译器在nx build时立刻报错error TS2345: Argument of type OrderInputV1 is not assignable to parameter of type OrderInputV2. Property tenantId is missing in type OrderInputV1 but required in type OrderInputV2.而不是等到上线后某个前端页面传入旧版input导致execute()报Cannot read property tenantId of undefined。这就是 Nx TypeScript 组合带来的确定性——错误发生在编译期而非运行时。另一个常被忽视的细节是agent-skills-core的package.json{ name: myorg/agent-skills-core, version: 0.0.0, // 注意这里永远是 0.0.0 peerDependencies: { typescript: ^5.0.0 }, devDependencies: { nx/workspace: 18.6.0 } }version设为0.0.0是故意的。因为core库本身不发布到 npm它只在 workspace 内部使用。Nx 的buildtarget 会把它编译成dist/libs/agent-skills-core其他库通过paths映射引用// tsconfig.base.json compilerOptions: { baseUrl: ., paths: { myorg/agent-skills-core: [dist/libs/agent-skills-core], myorg/agent-skills-tools: [dist/libs/agent-skills-tools] } }这样做的好处是core的任何修改都会触发 Nx 的增量构建affected projects自动 rebuild 所有依赖它的库且dist目录里的 JS 文件永远是最新的。我们试过把core发布成 npm 包结果每次改一个类型定义都要npm publishnpm updateCI 时间从 2 分钟涨到 8 分钟还经常因网络问题失败。现在nx build agent-skills-core nx build agent-skills-tool-order在本地 3 秒内完成CI 里也稳定在 45 秒内。提示agent-skills-core的index.ts只导出类型和接口不导出任何 runtime 代码。所有工具函数如createSkillResult()放在src/lib/utils/下且必须通过export * from ./utils显式导出。这是为了防止 accidental exports——曾经有次误把fs模块的readFileSync从 utils 里导出结果agent-dashboard前端构建时报错Cant resolve fs。Nx 的nx dep-graph --typedep能帮你发现这类跨环境依赖。4. Semantic Release 实践如何让feat(tool/weather)提交自动发布myorg/agent-skills-tool-weather1.2.0agent-skills的发布机制是它能被团队信任的关键。我们不用手动npm version patch npm publish而是完全交给 semantic-release且配置极度精简——只有 3 个文件.releaserc.json、conventional-changelog-config.js、package.json里的scripts。核心原则是提交信息即契约版本号即承诺发布即自动化。先看.releaserc.json{ branches: [main], plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, semantic-release/npm, semantic-release/github, [ semantic-release/exec, { verifyConditionsCmd: nx build agent-skills-tool-weather, prepareCmd: cp -r dist/libs/agent-skills-tool-weather ./dist/ } ] ] }重点在semantic-release/exec插件。它不是用来跑测试的而是确保每次发布前agent-skills-tool-weather库必须能成功构建。nx build会触发 TypeScript 编译、类型检查、ESLint任何一项失败release 就中断。这比npm test更严格——因为test可能只覆盖 70% 的代码而build覆盖 100% 的类型定义。conventional-changelog-config.js则定制了 changelog 的生成逻辑module.exports { preset: angular, releaseRules: [ { type: feat, scope: tool-weather, release: minor }, { type: fix, scope: tool-weather, release: patch }, { type: perf, scope: tool-weather, release: patch }, { type: refactor, scope: tool-weather, release: patch }, ], writerOpts: { transform: (commit) { if (commit.type feat commit.scope tool-weather) { commit.type ✨ New Feature; } if (commit.type fix commit.scope tool-weather) { commit.type Bug Fix; } return commit; } } };这个配置让git commit -m feat(tool/weather): add 7-day forecast support自动生成语义化版本1.2.0minor并在 GitHub Release 页面生成带 emoji 的 changelog## ✨ New Feature - Add 7-day forecast support (commit abc123) ## Bug Fix - Fix temperature unit conversion for Celsius (commit def456)但真正的魔法在package.json的 scripts 里{ scripts: { release: semantic-release, prepublishOnly: npm run build, build: nx build agent-skills-tool-weather } }注意prepublishOnly钩子。semantic-release在semantic-release/npm插件里会执行npm publish而npm publish默认会运行prepublishOnly。这意味着每次发布都是先nx build生成dist/再npm publish上传dist/目录下的文件而非src/目录。我们特意在project.json的buildtarget 里配置了outputs: [{workspaceRoot}/dist/libs/agent-skills-tool-weather]这样nx build的输出路径和npm publish的默认路径一致无需额外cp命令。dist/目录里只有index.d.ts、index.js、package.json由 Nx 自动生成没有src/、__tests__或node_modules——体积控制在 12KB 以内。这套流程带来的最大收益是版本号不再由人决定而是由提交内容决定。开发提交feat(tool/order): add refund policy lookupCI 就发myorg/agent-skills-tool-order2.1.0提交fix(tool/order): handle empty order items就发myorg/agent-skills-tool-order2.1.1。产品经理不用再问“这个功能什么时候上线”她只要看 GitHub 上agent-skills-tool-order的 latest release tag就知道2.1.0已发布且 changelog 里明确写了“支持退款政策查询”。更关键的是它消除了“发布恐惧症”。以前每次发版都要开 30 分钟对齐会确认谁改了什么、有没有回归风险、要不要回滚。现在semantic-release的 CI 日志就是权威记录[12:03:45] [semantic-release] › ℹ Found 1 commits since last release [12:03:45] [semantic-release] › ℹ Start step analyzeCommits of plugin semantic-release/commit-analyzer [12:03:45] [semantic-release] › ℹ The release type for the commit is minor [12:03:45] [semantic-release] › ℹ Start step generateNotes of plugin semantic-release/release-notes-generator [12:03:45] [semantic-release] › ℹ Start step prepare of plugin semantic-release/exec [12:03:46] [semantic-release] › ℹ Executing command cp -r dist/libs/agent-skills-tool-weather ./dist/ [12:03:46] [semantic-release] › ℹ Start step publish of plugin semantic-release/npm [12:03:47] [semantic-release] › ℹ Published myorg/agent-skills-tool-weather1.2.0 to npm这条日志比任何会议纪要都可靠。我们甚至把semantic-release的 webhook 接入企业微信每次发布成功自动推送消息“myorg/agent-skills-tool-weather1.2.0已发布changelog: https://github.com/myorg/agent-skills/releases/tag/agent-skills-tool-weather%401.2.0”。注意semantic-release的branches配置必须是[main]不能是[main, develop]。因为agent-skills的所有技能库都走main直接发布不设预发布分支。我们用nx affected --baseorigin/main --headHEAD --targettest在 PR 里做增量测试确保main永远是可发布的。这个决策让我们省去了alpha/beta版本管理的复杂度也避免了“某个技能发了 beta但 core 还没发”的依赖地狱。5. 实战避坑指南从npm : 无法加载文件 d:\node\npm.ps1到nx graph拓扑失效的完整排查链agent-skills的落地过程不是一帆风顺的。我整理了过去半年里团队遇到的 5 类高频故障每类都附上真实命令、错误日志、根因分析和修复步骤。这些不是教科书答案而是我在凌晨 2 点 debug 时记下的血泪笔记。5.1 PowerShell 执行策略错误npm : 无法加载文件 d:\node\npm.ps1的本质是 Windows 安全策略不是 Node.js 问题现象Windows 开发者 clone 仓库后执行npm install报错npm : 无法加载文件 d:\node\npm.ps1因为在此系统上禁止运行脚本。 有关详细信息请参阅 https://go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Policies。 所在位置 行:1 字符:1 npm install ~~~~~~~~~~~ CategoryInfo : SecurityError: (:) []PSSecurityException FullyQualifiedErrorId : UnauthorizedAccess根因分析这不是agent-skills的问题而是 Windows PowerShell 默认执行策略ExecutionPolicy为Restricted禁止运行任何本地脚本包括npm.cmd包装的npm.ps1。网上流传的“以管理员身份运行 PowerShell 再执行Set-ExecutionPolicy RemoteSigned -Scope CurrentUser”是治标不治本——因为nx的很多命令如nx serve会启动子进程子进程的 PowerShell 策略可能重置。正确修复步骤打开 PowerShell非管理员执行Get-ExecutionPolicy -List查看CurrentUser和MachinePolicy的值。永久修改当前用户策略无需管理员Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force关键一步在package.json的scripts里强制指定 shellscripts: { install: cmd /c npm install, start: cmd /c nx serve }这样npm install就走cmd.exe而非powershell.exe彻底绕过策略限制。我们已在agent-skills的root/package.json里预置了这些 script。提示这个错误在nx open命令里也会出现因为nx open会尝试启动浏览器并执行nx graph其底层也是调用npm。所以nx open前务必先npm install成功。5.2nx graph拓扑图空白不是 Nx 故障而是project.json的targets配置缺失现象执行nx graph浏览器打开空白页面控制台报错TypeError: Cannot read properties of undefined (reading targets)。根因分析nx graph依赖每个project.json文件里的targets字段来构建依赖图。如果某个库如agent-skills-tool-weather的project.json里只有root、sourceRoot没有targetsNx 就认为这个项目不可构建直接跳过导致图谱断裂。排查链路运行nx list检查agent-skills-tool-weather是否在列表中。如果不在说明project.json未被 Nx 识别。检查libs/agent-skills-tool-weather/project.json确认有targets: { build: { executor: nx/node:webpack, options: { outputPath: dist/libs/agent-skills-tool-weather, main: libs/agent-skills-tool-weather/src/index.ts, tsConfig: libs/agent-skills-tool-weather/tsconfig.lib.json } } }如果targets存在但nx graph仍空白运行nx graph --verbose查看日志里是否有Skipping project agent-skills-tool-weather due to missing targets。修复方案在project.json里补全targets且executor必须是 Nx 官方插件如nx/node:webpack不能是自定义 executor。我们曾用myorg/custom-executor结果nx graph完全不识别该项目。5.3semantic-release发布失败Cannot find module myorg/agent-skills-core的真相是dist/路径未同步现象CI 里semantic-release执行到semantic-release/npm阶段失败[15:22:33] [semantic-release] › ✖ Failed step publish of plugin semantic-release/npm [15:22:33] [semantic-release] › ✖ An error occurred while running semantic-release: Error: Cannot find module myorg/agent-skills-core根因分析semantic-release/npm在npm publish前会require(package.json)并解析dependencies。如果agent-skills-tool-weather的package.json里写了myorg/agent-skills-core: 0.0.0但dist/目录里没有node_modules/myorg/agent-skills-corenpm publish就会报错——因为npm publish默认只上传当前包的dist/目录不会递归上传 peerDependencies。解决方案在agent-skills-tool-weather/project.json的buildtarget 里添加copyFiles选项options: { copyFiles: [ { from: dist/libs/agent-skills-core, to: dist/libs/agent-skills-core } ] }这样nx build会把core的dist/复制到tool-weather的dist/下npm publish就能找到它。我们已在agent-skills的模板里固化此配置。5.4SkillResult类型不匹配Property data does not exist on type SkillResultunknown的根源是泛型未显式声明现象在agent-orchestrator里调用weatherSkill.execute(input)TypeScript 报错const result await weatherSkill.execute({ city: shanghai, days: 3 }); console.log(result.data.forecast); // ❌ Error: Property data does not exist on type SkillResultunknown根因分析weatherSkill的类型是SkillWeatherInput, WeatherOutput但execute()返回PromiseSkillResultTOutput如果TOutput是unknownresult.data就是unknown。这是因为weatherSkill实例化时没有显式指定泛型// ❌ 错误类型推导失败 const weatherSkill new WeatherSkill(); // ✅ 正确显式声明泛型 const weatherSkill new WeatherSkillWeatherInput, WeatherOutput();修复方案在agent-skills-tool-weather/src/lib/weather-skill.ts的类定义里强制泛型export class WeatherSkillTInput WeatherInput, TOutput WeatherOutput implements SkillTInput, TOutput { // ... }这样new WeatherSkill()就自动继承WeatherInput/WeatherOutput无需手动指定。5.5nx affected不触发增量构建--base参数指向错误分支导致所有项目都被 rebuild现象PR 里只改了agent-skills-tool-weather但nx affected --targetbuild却 rebuild 了agent-orchestrator和agent-dashboard。根因分析nx affected的--base参数必须指向main分支的最新 commit而不是origin/main。如果 CI 脚本写成--baseorigin/main而origin/main在 CI runner 里未更新nx affected就会对比错误的 base认为所有项目都受影响。正确命令# 在 CI 脚本里先 fetch 最新 main git fetch origin main:refs/remotes/origin/main # 再运行 affected nx affected --baseorigin/main --headHEAD --targetbuild我们已在.github/workflows/ci.yml里预置了git fetch步骤。最后分享一个小技巧在本地开发时如果nx graph加载慢可以加--filegraph.html参数生成静态 HTML用浏览器打开比 Web UI 快 3 倍。这个 HTML 文件里嵌入了所有依赖关系的 JSON 数据离线也能看。