Agent技能工程化:TypeScript契约驱动的可复用技能骨架
1. 项目概述这不是一个“技能库”而是一套可复用、可验证、可演进的智能体行为骨架“agent-skills”这个名称乍看像一个泛泛而谈的术语——毕竟现在满屏都是“AI Agent”“Skill Chain”“Tool Calling”这类词。但当你把目光从营销话术拉回工程现场就会发现真正卡住团队落地的从来不是“要不要做Agent”而是“怎么让Agent可靠地完成一件事”。比如一个客服Agent要查订单它得知道该调哪个API、如何拼接参数、怎么处理超时、失败后是否重试、重试几次、重试间隔怎么设、错误码对应哪类用户提示语……这些不是LLM prompt能兜住的而是必须沉淀为结构化、可测试、可版本化的代码单元。我带过三个不同行业的Agent项目金融风控辅助、工业设备远程诊断、政务知识问答踩过最深的坑就是早期用临时函数拼凑“技能”结果两周后没人记得getOrderStatusByPhone和getOrderStatusByOrderId的区别更没人敢动那段混着业务逻辑、重试策略、日志埋点、错误分类的200行函数。后来我们彻底重构把每个原子能力抽象成独立模块——不是简单的function封装而是带类型契约、输入校验、执行上下文、可观测钩子、版本标识的完整单元。这就是agent-skills的真实定位它不是工具集是技能契约Skill Contract的工程实现范式。核心关键词里“TypeScript”不是为了赶时髦而是因为Agent技能必须经得起静态校验——你不能靠运行时抛错才发现userId字段在schema里写成了user_id“Node”是因绝大多数Agent运行时LangChain、LlamaIndex、自研Orchestrator都跑在Node生态里且I/O密集型任务API调用、文件读写、数据库交互天然适配其异步模型“Nx”解决的是多技能协同开发的痛点——当团队同时维护37个技能模块查库存、发短信、生成PDF、调OCR、连ERP没有monorepo的依赖管理、构建缓存、影响分析CI会变成噩梦“semantic-release”则直击发布混乱谁改了sendEmail技能的签名上个版本是否兼容下游服务是否已适配靠人工发版记录不可能。我们必须让每次commit都自动触发语义化版本号v2.1.0、自动生成CHANGELOG、精准推送npm包。所以如果你正面临这些问题技能代码散落在不同仓库、新人接手要花三天搞清调用链、线上报错无法快速定位是哪个技能出的问题、想加个重试逻辑却要改五个地方……那么agent-skills不是锦上添花而是手术刀。它适合两类人一是正在搭建企业级Agent平台的架构师需要一套可治理的技能基建二是独立开发者或小团队想避免重复造轮子用最小成本获得生产级技能模块——比如直接npm install org/skill-send-email导入即用类型安全日志统一错误可追踪。2. 整体设计与思路拆解为什么放弃“大而全”选择“契约驱动”的极简主义很多团队一上来就想建“Agent技能市场”规划50个技能、支持动态加载、可视化编排、拖拽配置……结果半年过去只上线了3个半成品还全是硬编码。agent-skills的设计哲学很朴素先让一个技能100%可靠再复制100个。这决定了它的整体架构不是围绕“功能数量”而是围绕“可靠性维度”展开。2.1 核心分层契约层 → 实现层 → 运行时层整个设计严格遵循三层分离契约层Contract Layer用TypeScript Interface定义技能的“法律文书”。例如SendEmailSkill契约包含export interface SendEmailSkillInput { to: string; // 必填邮箱格式校验 subject: string; // 长度≤100 body: string; // 支持Markdown但需转义HTML标签 attachments?: { filename: string; content: Buffer }[]; // 可选但若存在则必须有filename } export interface SendEmailSkillOutput { messageId: string; // 邮件唯一ID用于后续追踪 status: sent | queued | failed; // 精确状态枚举非字符串 error?: { code: INVALID_RECIPIENT | ATTACHMENT_TOO_LARGE | SMTP_TIMEOUT; message: string }; // 结构化错误 } export type SendEmailSkill SkillSendEmailSkillInput, SendEmailSkillOutput;提示契约中所有字段都带明确约束格式、长度、枚举值而非any或Recordstring, unknown。这是TypeScript发挥威力的第一道防线——编译期就能捕获90%的参数误用。实现层Implementation Layer契约的具体执行者。它不直接操作网络或文件而是通过Dependency Injection注入具体依赖export class SendEmailSkillImpl implements SendEmailSkill { constructor( private readonly smtpClient: SmtpClient, // 依赖抽象非具体实现 private readonly logger: Logger, // 统一日志接口 private readonly metrics: MetricsClient // 监控指标客户端 ) {} async execute(input: SendEmailSkillInput): PromiseSendEmailSkillOutput { // 1. 输入校验契约已定义规则此处调用校验器 const validated await this.validateInput(input); // 2. 执行核心逻辑调用smtpClient.send try { const result await this.smtpClient.send({ to: validated.to, subject: validated.subject, html: markdownToHtml(validated.body), attachments: validated.attachments }); // 3. 记录成功指标 this.metrics.increment(email.sent.success, { provider: ses }); return { messageId: result.id, status: sent }; } catch (error) { // 4. 将底层错误映射为契约定义的结构化错误 const mappedError this.mapSmtpError(error); this.logger.error(Email send failed, { input: validated, error: mappedError }); this.metrics.increment(email.sent.failed, { code: mappedError.code }); throw new SkillExecutionError(mappedError); // 抛出统一错误类型 } } }注意这里没有console.log没有new Error(发送失败)所有日志、监控、错误都走标准化接口。这意味着换掉SMTP服务商从SendGrid切到Mailgun只需替换SmtpClient实现技能逻辑零修改。运行时层Runtime Layer提供技能注册、调度、生命周期管理的薄胶水层。它负责加载所有技能实例通过Nx的workspace.json自动发现按契约名注册到全局技能注册表如SkillRegistry.register(send-email, new SendEmailSkillImpl(...))提供统一执行入口await SkillRegistry.execute(send-email, { to: ab.com, ... })自动注入上下文如requestId、traceId、用户权限Token这种分层让每个技能模块像乐高积木契约是接口卡扣实现是积木块运行时是底座。你可以单独测试契约是否合理用Jest mock所有依赖单独测试实现逻辑注入mock client单独压测运行时调度性能。2.2 为什么选Nx而不是pnpm workspace或TurborepoNx在agent-skills中承担的角色远超“多包管理”。我们对比过三种方案方案依赖图谱分析构建缓存粒度影响分析精度插件生态对agent-skills的适配性pnpm workspace基于package.json粗粒度整个包按包缓存修改一个.ts文件也重构建整个包仅能识别包级依赖变更有限需自行实现❌ 修改send-email契约所有依赖它的技能都会被强制重构建CI时间翻倍Turborepo基于文件哈希细粒度按文件缓存但需手动配置inputs/outputs能识别文件级变更但需大量配置丰富但插件多为通用场景⚠️ 需为每个技能手写turbo.json规则37个技能37份配置维护成本高Nx原生支持TS/JS依赖解析精确到导出符号按函数/类缓存改SendEmailSkillInput接口只重构建契约和直接引用它的实现精确到符号级如SendEmailSkill被修改只影响send-email技能及其测试官方插件深度集成TS、Jest、ESLint、Cypress✅ 开箱即用nx affected --targetbuild自动识别受影响技能CI提速60%实操中我们曾将一个技能的输入类型从string改为string[]Nx瞬间定位出只有bulk-send-email技能和send-email.e2e-spec.ts测试受影响其他35个技能跳过构建。而pnpm workspace会重建全部37个包。这就是工程效率的分水岭。2.3 semantic-release不是自动化而是可信发布很多人把semantic-release当成“省事工具”但在agent-skills里它是信任锚点。我们规定所有提交必须符合Angular约定feat:,fix:,chore:等否则CI直接拒绝。这带来三个硬性保障版本号即契约承诺v2.1.0意味着新增了向后兼容的功能如send-email增加cc字段但绝不破坏现有接口。v3.0.0则代表重大变更如to字段从string变为string[]下游必须升级适配。CHANGELOG即决策日志每次发布自动生成的CHANGELOG清晰列出BREAKING CHANGES哪些契约被破坏如何迁移Features新增了什么技能或能力Bug Fixes修复了哪些已知问题Performance Improvements如send-email重试逻辑优化平均耗时降低300ms发布即审计semantic-release与GitHub Actions深度集成每次发布都关联PR、作者、时间戳。当线上出现send-email失败率突增运维可立刻查是不是刚发布了v2.3.0该版本改动了什么是否有人绕过流程手动publish我们曾因一次未走CI的npm publish导致线上故障——新版本send-sms技能悄悄移除了countryCode必填校验结果海外用户收不到验证码。此后所有发布必须经过semantic-release流水线人工publish被禁用。这不是教条而是用自动化堵住人性漏洞。3. 核心细节解析与实操要点从契约定义到技能注册的每一步陷阱定义一个“可用”的技能远比写个function复杂。下面拆解最关键的四个环节每个都附真实踩坑案例。3.1 契约设计别让TypeScript的“any”成为你的敌人新手常犯的错误用any或unknown占位想着“后面再补类型”。这在agent-skills中是红线。我们强制要求所有输入/输出类型必须闭合closed即不能有any、unknown、Object且必须有明确的校验逻辑。以SearchProductSkill为例错误示范// ❌ 危险无法校验运行时才暴露问题 interface SearchProductSkillInput { query: any; // 用户随便输什么 filters: unknown; // 是对象数组还是null }正确做法// ✅ 闭合契约 interface SearchProductSkillInput { query: string; // 至少非空 filters: ProductFilter; // 具体类型 page?: number; // 可选但有默认值 pageSize?: number; // 可选但有范围限制 } interface ProductFilter { categoryIds?: string[]; // 数组元素为非空字符串 priceRange?: { min: number; max: number }; // 对象min/max有约束 inStockOnly?: boolean; // 布尔值无歧义 } // 校验器作为契约的一部分 const validateSearchInput (input: SearchProductSkillInput): ResultSearchProductSkillInput, ValidationError { if (!input.query || input.query.trim().length 0) { return Err({ code: QUERY_EMPTY, message: 搜索关键词不能为空 }); } if (input.page (input.page 1 || !Number.isInteger(input.page))) { return Err({ code: PAGE_INVALID, message: 页码必须为正整数 }); } // ...更多校验 };实操心得我们把校验器写在契约文件里而非实现层。这样所有技能使用者前端、其他技能都能复用同一套校验逻辑避免“前端校验一遍后端又校验一遍”的冗余。校验失败返回ResultT, E类似Rust的Result而非throw便于上游统一处理。3.2 实现层依赖注入不是炫技是解耦刚需agent-skills禁止在技能实现中直接require(nodemailer)或import { createClient } from redis。所有外部依赖必须通过构造函数注入。原因有三可测试性测试send-email技能时我们注入MockSmtpClient断言send方法是否被调用、参数是否正确无需真实发邮件。环境隔离本地开发用MockSmtpClient测试环境用TestSmtpClient发到MailHog生产环境用AwsSesClient。切换只需改DI容器配置技能代码零修改。生命周期控制SmtpClient可能需要连接池、健康检查、自动重连。如果技能内new SmtpClient()多个技能实例会创建多个连接池浪费资源。通过DI我们确保整个应用共享一个连接池实例。Nx提供了开箱即用的DI容器nx/node的createApp但我们的实践更进一步为每个技能定义自己的依赖图谱。例如send-email依赖SmtpClient和Logger而generate-pdf依赖PuppeteerClient和FileSystem。Nx的project.json中配置{ targets: { build: { executor: nx/node:webpack, options: { main: src/index.ts, tsConfig: tsconfig.lib.json, outputPath: dist/libs/skills/send-email } } }, dependencies: { smtp-client: [default], logger: [default] } }这样Nx在构建时自动检查send-email是否真的引用了smtp-client包如果没有CI报错。这杜绝了“写了依赖但没用”的隐形债务。3.3 运行时注册技能不是“存在”而是“可发现”技能写完了怎么让Agent框架知道它agent-skills采用约定优于配置的自动注册机制所有技能实现类必须放在libs/skills/*/src/lib/目录下且文件名匹配*.skill.ts如send-email.skill.ts。Nx的workspace.json中配置projects每个技能是一个独立projecttype: library。运行时启动时执行SkillRegistry.autoRegister()它会扫描所有libs/skills/*/src/lib/*.skill.ts文件动态import()每个文件查找导出的SkillImpl类通过instanceof Skill判断调用其constructor传入预配置的依赖SmtpClient、Logger等调用SkillRegistry.register(skillName, instance)关键点在于技能名skillName由文件路径推导而非硬编码。libs/skills/send-email/src/lib/send-email.skill.ts→skillName send-email。这带来两个好处避免命名冲突不可能有两个技能同名因为文件路径唯一。重构友好重命名send-email目录为email-notifier技能名自动变为email-notifier所有调用处如SkillRegistry.execute(email-notifier, ...)会因TS类型错误立即暴露强制更新。注意自动注册不等于“魔法”。我们保留手动注册入口SkillRegistry.register(legacy-api, new LegacyApiSkill(...))用于集成老系统。但新技能必须走自动注册这是规范。3.4 错误处理结构化错误是技能的“身份证”Agent技能的错误不能是Error(Network timeout)这种模糊信息。agent-skills强制要求所有错误必须映射为契约定义的SkillExecutionError子类且携带code、message、details。以send-email为例底层SMTP错误可能有ECONNREFUSED→ 映射为{ code: SMTP_CONNECTION_FAILED, message: 邮件服务器连接失败, details: { host: smtp.example.com, port: 587 } }ETIMEDOUT→ 映射为{ code: SMTP_TIMEOUT, message: 邮件发送超时, details: { timeoutMs: 30000 } }550 Invalid recipient→ 映射为{ code: INVALID_RECIPIENT, message: 收件人邮箱无效, details: { recipient: invaliddomain } }这样做的价值前端可精准提示收到INVALID_RECIPIENT就显示“请输入正确的邮箱地址”而非笼统的“发送失败”。监控可分类告警SMTP_TIMEOUT频率突增说明网络问题INVALID_RECIPIENT突增可能是前端表单校验失效。重试策略可定制SMTP_CONNECTION_FAILED可立即重试INVALID_RECIPIENT重试100次也没用应直接失败。我们甚至为每个错误code定义了HTTP状态码映射供API网关使用export const ERROR_CODE_TO_HTTP_STATUS: Recordstring, number { INVALID_RECIPIENT: 400, ATTACHMENT_TOO_LARGE: 400, SMTP_TIMEOUT: 504, SMTP_CONNECTION_FAILED: 503, RATE_LIMIT_EXCEEDED: 429, };4. 实操过程与核心环节实现从零搭建一个可发布的send-email技能现在让我们动手实现一个完整的send-email技能覆盖从初始化到发布的全流程。所有命令均基于Nx 18、Node 18、TypeScript 5.0。4.1 初始化Nx工作区与技能库# 创建新Nx工作区选择empty preset避免模板污染 npx create-nx-workspacelatest agent-skills --presetempty --clinx --nx-cloudfalse # 进入目录 cd agent-skills # 添加Node插件用于构建、运行 nx add nx/node # 创建skills库存放所有技能契约和实现 nx g nx/node:library skills --directorylibs --no-interactive # 创建send-email技能子库Nx会自动在libs/skills/send-email下生成 nx g nx/node:library send-email --directorylibs/skills --no-interactive此时目录结构为agent-skills/ ├── libs/ │ ├── skills/ # 契约库公共类型定义 │ │ └── src/ │ │ └── lib/ │ │ └── index.ts # 导出所有契约 │ └── skills/ │ └── send-email/ # 实现库 │ └── src/ │ └── lib/ │ └── send-email.skill.ts4.2 定义契约在libs/skills/src/lib/index.ts// libs/skills/src/lib/index.ts export * from ./send-email.contract; // libs/skills/src/lib/send-email.contract.ts export interface SendEmailSkillInput { to: string; subject: string; body: string; cc?: string[]; bcc?: string[]; attachments?: { filename: string; content: Buffer }[]; } export interface SendEmailSkillOutput { messageId: string; status: sent | queued; sentAt: Date; } export class SendEmailSkillError extends Error { constructor( public readonly code: INVALID_EMAIL | ATTACHMENT_SIZE_EXCEEDED | SMTP_ERROR, message: string, public readonly details?: Recordstring, any ) { super(message); this.name SendEmailSkillError; } } export type SendEmailSkill SkillSendEmailSkillInput, SendEmailSkillOutput;4.3 实现技能在libs/skills/send-email/src/lib/send-email.skill.ts// libs/skills/send-email/src/lib/send-email.skill.ts import { SmtpClient } from org/clients/smtp; // 假设已有SMTP客户端库 import { Logger } from org/utils/logger; import { MetricsClient } from org/utils/metrics; import { SendEmailSkill, SendEmailSkillInput, SendEmailSkillOutput, SendEmailSkillError } from org/skills; export class SendEmailSkillImpl implements SendEmailSkill { constructor( private readonly smtpClient: SmtpClient, private readonly logger: Logger, private readonly metrics: MetricsClient ) {} async execute(input: SendEmailSkillInput): PromiseSendEmailSkillOutput { // 1. 输入校验 const validated await this.validateInput(input); // 2. 构建邮件内容 const email { to: validated.to, cc: validated.cc, bcc: validated.bcc, subject: validated.subject, html: validated.body, attachments: validated.attachments?.map(a ({ filename: a.filename, content: a.content.toString(base64), encoding: base64 })) }; // 3. 发送并处理结果 try { const result await this.smtpClient.send(email); this.metrics.increment(send_email.success, { provider: this.smtpClient.provider }); return { messageId: result.messageId, status: sent, sentAt: new Date() }; } catch (error) { this.metrics.increment(send_email.failed, { code: this.mapErrorToCode(error) }); throw new SendEmailSkillError( this.mapErrorToCode(error), Email send failed: ${error.message}, { originalError: error } ); } } private async validateInput(input: SendEmailSkillInput): PromiseSendEmailSkillInput { if (!input.to || !/^[^\s][^\s]\.[^\s]$/.test(input.to)) { throw new SendEmailSkillError(INVALID_EMAIL, 收件人邮箱格式不正确); } if (input.attachments?.some(a a.content.length 10 * 1024 * 1024)) { throw new SendEmailSkillError(ATTACHMENT_SIZE_EXCEEDED, 附件大小超过10MB); } return input; } private mapErrorToCode(error: any): INVALID_EMAIL | ATTACHMENT_SIZE_EXCEEDED | SMTP_ERROR { if (error.code INVALID_EMAIL) return INVALID_EMAIL; if (error.code ATTACHMENT_SIZE_EXCEEDED) return ATTACHMENT_SIZE_EXCEEDED; return SMTP_ERROR; } }4.4 配置Nx构建与测试编辑libs/skills/send-email/project.json{ name: skills-send-email, projectType: library, sourceRoot: libs/skills/send-email/src, prefix: skills, targets: { build: { executor: nx/node:webpack, options: { main: libs/skills/send-email/src/index.ts, tsConfig: libs/skills/send-email/tsconfig.lib.json, outputPath: dist/libs/skills/send-email, assets: [libs/skills/send-email/src/**/*.d.ts] } }, test: { executor: nx/jest:jest, options: { jestConfig: libs/skills/send-email/jest.config.ts, passWithNoTests: true } } }, tags: [type:skill, scope:email] }编写测试libs/skills/send-email/src/lib/send-email.skill.spec.tsimport { SendEmailSkillImpl } from ./send-email.skill; import { MockSmtpClient } from org/clients/smtp/mocks; import { MockLogger } from org/utils/logger/mocks; import { MockMetricsClient } from org/utils/metrics/mocks; describe(SendEmailSkillImpl, () { let skill: SendEmailSkillImpl; let mockSmtp: MockSmtpClient; let mockLogger: MockLogger; let mockMetrics: MockMetricsClient; beforeEach(() { mockSmtp new MockSmtpClient(); mockLogger new MockLogger(); mockMetrics new MockMetricsClient(); skill new SendEmailSkillImpl(mockSmtp, mockLogger, mockMetrics); }); it(should send email successfully, async () { // Arrange const input { to: testexample.com, subject: Hello, body: pWorld/p }; mockSmtp.mockSend.mockResolvedValue({ messageId: msg-123 }); // Act const result await skill.execute(input); // Assert expect(result.messageId).toBe(msg-123); expect(mockSmtp.mockSend).toHaveBeenCalledTimes(1); expect(mockMetrics.increment).toHaveBeenCalledWith(send_email.success, expect.any(Object)); }); it(should throw INVALID_EMAIL error for invalid email, async () { // Arrange const input { to: invalid-email, subject: Hello, body: World }; // Act Assert await expect(skill.execute(input)).rejects.toThrow( expect.objectContaining({ code: INVALID_EMAIL }) ); }); });4.5 配置semantic-release与发布安装semantic-releasenpm install --save-dev semantic-release semantic-release/commit-analyzer semantic-release/release-notes-generator semantic-release/npm semantic-release/github创建.releaserc{ branches: [main], plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, [ semantic-release/npm, { npmPublish: true, pkgRoot: dist/libs/skills/send-email } ], semantic-release/github ] }在GitHub Actions中配置CI.github/workflows/release.ymlname: Release on: push: branches: [main] 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 - run: npx nx build skills-send-email - name: Semantic Release uses: cycjimmy/semantic-release-actionv3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}第一次发布提交feat(send-email): initial implementationpush到main分支CI自动触发构建dist/libs/skills/send-emailsemantic-release检测到feat发布v1.0.0自动生成CHANGELOG.md推送到npm registry包名为org/skill-send-email实测下来很稳从写代码到npm包可用全程无人工干预耗时约3分钟。后续每次fix(send-email): handle attachment encoding提交自动发布v1.0.1。5. 常见问题与排查技巧实录那些文档不会写的实战经验在落地agent-skills的过程中我们整理了高频问题清单。这些问题往往不在官方文档里却是新人卡住几小时的关键。5.1 TypeScript类型错误Cannot find module org/skills or its corresponding type declarations现象在send-email.skill.ts中import { SendEmailSkill } from org/skillsVS Code报红编译失败。根因Nx的tsconfig.base.json中baseUrl: .和paths未正确配置或org/skills包未在package.json中声明为types。解决方案检查tsconfig.base.json{ compilerOptions: { baseUrl: ., paths: { org/skills: [libs/skills/src/index.ts], org/skills/*: [libs/skills/src/lib/*] } } }确保libs/skills/package.json中有{ name: org/skills, types: ./src/index.ts, main: ./src/index.ts }重启TS ServerVS Code中CtrlShiftP→TypeScript: Restart TS server注意Nx 18默认启用moduleResolution: node16如果项目仍用moduleResolution: node需在tsconfig.json中显式覆盖否则路径映射失效。5.2 Nx构建失败Error: Cannot find module libs/skills/send-email/src/lib/send-email.skill现象运行nx build skills-send-email报错找不到模块。根因Webpack打包器默认不处理.ts文件且Nx的nx/node:webpackexecutor需要明确指定入口文件。解决方案确保libs/skills/send-email/project.json中main指向index.ts而非.skill.tsmain: libs/skills/send-email/src/index.ts创建libs/skills/send-email/src/index.ts导出技能类export { SendEmailSkillImpl } from ./lib/send-email.skill; export * from org/skills; // 导出契约在tsconfig.lib.json中include必须包含src/index.ts。5.3 semantic-release不触发Commit message不符合规范现象push后CI日志显示No version publishedCHANGELOG未更新。排查步骤检查commit message是否严格匹配type(scope): subject如feat(send-email): add cc support。运行本地命令验证npx semantic-release --dry-run --debug查看输出中[Semantic release]: There are 0 commits since the last release是否为真。如果不是检查branches配置是否匹配当前分支名如mainvsmaster。 3. 确认.releaserc中的semantic-release/npm插件pkgRoot指向正确的dist目录dist/libs/skills/send-email。实操心得我们用Husky commitlint强制校验commit message。在package.json中husky: { hooks: { commit-msg: commitlint -E HUSKY_GIT_PARAMS } }, commitlint: { extends: [commitlint/config-conventional] }这样git commit -m update send-email会被拒绝必须写git commit -m feat(send-email): update logic。5.4 技能注册失败Skill send-email not found in registry现象Agent调用SkillRegistry.execute(send-email, ...)时报错。根因自动注册未执行或SkillRegistry.autoRegister()调用时机错误。解决方案确保在应用启动的最早期如main.ts调用import { SkillRegistry } from org/skills/runtime; import { SendEmailSkillImpl } from org/skills/send-email; async function bootstrap() { // 必须在任何技能调用前执行 await SkillRegistry.autoRegister(); // 启动HTTP服务、WebSocket等 const app await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();检查autoRegister()扫描路径是否正确。默认扫描libs/skills/*/src/lib/*.skill.ts如果文件名不是.skill.ts如.ts需在SkillRegistry.autoRegister({ pattern: **/*.ts })中自定义。5.5 性能瓶颈单个技能执行耗时过长现象send-email技能平均耗时2s超出SLA500ms。排查与优化添加性能监控在execute方法前后打点const start Date.now(); try { const result await this.smtpClient.send(email); const duration Date.now() - start; this.metrics.histogram(send_email.duration, duration, { provider: this.smtpClient.provider }); return result; } catch (error) { const duration Date.now() - start; this.metrics.histogram(send_email.duration, duration, { provider: error }); throw error; }分析慢因如果duration集中在smtp