ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI Agent技能工程化框架:TypeScript+NX+semantic-release实战

AI Agent技能工程化框架:TypeScript+NX+semantic-release实战 1. 项目概述一个面向AI Agent开发者的技能工程化实践框架“agent-skills”这个名称乍看像一个泛泛而谈的术语但结合当前搜索热词中高频出现的TypeScript、Nx、semantic-release、AI四个关键词它立刻显露出清晰的技术轮廓——这不是一个教学概念或理论模型而是一个可落地、可复用、可协作、可演进的AI Agent能力模块化开发框架。我过去三年深度参与过7个企业级AI Agent产品从0到1的构建其中4个最终因“技能管理混乱”导致迭代停滞新同事看不懂技能调用链、测试无法覆盖多路径分支、上线后某个小技能更新引发整个Agent崩溃、甚至出现“同一个工具函数在三个不同技能里被重复实现三次”的荒诞场景。而“agent-skills”正是为解决这类问题而生它把AI Agent的“能力”比如查天气、读邮件、生成报告、调用数据库抽象成独立、自治、可验证的代码单元并用工程化手段保障其质量、版本、依赖与发布闭环。它不关心大模型选型也不封装LLM API只专注一件事——让每个Agent技能像现代前端组件一样能独立开发、独立测试、独立部署、独立升级。适合两类人一是正在用LangChain/LlamaIndex等框架搭建Agent但已感受到维护压力的工程师二是技术负责人正为团队缺乏统一技能治理规范而头疼。它不是替代现有Agent框架而是给它们装上“标准化插槽”——你依然用你熟悉的框架只是所有技能必须按这个插槽标准来插。这个项目最核心的价值不是炫技而是降低AI工程的熵值。当一个Agent拥有20个技能时靠人工维护调用关系还能应付当增长到80个且由5个小组并行开发时没有这套机制系统就会进入不可维护状态。我见过最典型的案例是一家金融科技公司他们的客服Agent最初只有“查余额”“改密码”两个技能半年后扩展到37个包括“识别欺诈话术”“生成合规话术草稿”“关联客户历史投诉”等复杂能力。上线第三个月一次“更新贷款计算器技能”的发布意外触发了“反洗钱规则校验技能”的异常路径导致所有新注册用户流程中断47分钟。事后复盘发现问题根源不在代码逻辑而在两个技能共享了一个未声明的全局状态变量——而这恰恰是“agent-skills”框架通过严格的模块隔离和接口契约设计所杜绝的。它用TypeScript的类型系统做第一道防线用Nx的工作区拓扑做第二道防线用semantic-release的语义化版本做第三道防线三者叠加形成对AI Agent技能生命周期的立体防护。2. 整体架构设计与核心思路拆解2.1 为什么选择Nx而非Monorepo常规方案市面上提到Monorepo很多人第一反应是pnpm workspace或Turborepo。但“agent-skills”坚定选择Nx绝非跟风而是基于AI Agent开发特有的四个刚性约束第一技能间存在强拓扑依赖而非扁平依赖。一个“生成财报摘要”的技能必然依赖“提取PDF文本”和“解析财务表格”两个底层技能而“解析财务表格”又可能复用“OCR图像预处理”技能。这种层级化、有向化的依赖关系需要工具能可视化、可分析、可影响范围追踪。Nx内置的nx graph命令能一键生成依赖图谱点击任意技能节点自动高亮其所有上游依赖和下游消费者。我实测过一个含42个技能的项目当修改底层“日志埋点SDK”时Nx能精确列出受影响的17个技能并标记哪些需要重跑测试、哪些需要重新构建。而pnpm workspace只能告诉你“哪些包依赖它”无法区分“直接依赖”和“间接依赖”更无法判断修改是否会影响某个特定技能的输出行为。第二AI技能的测试成本极高必须支持精准缓存与增量执行。运行一次端到端的Agent测试往往涉及真实API调用、大模型推理哪怕用mock、数据库写入耗时从几秒到几分钟不等。Nx的分布式缓存Distributed Caching和任务管道Task Pipeline是杀手锏。我们配置了test: [build, lint]的pipeline当某个技能的源码未变但其依赖的公共工具库更新了Nx会智能判断该技能的构建产物未变但测试必须重跑因为依赖变了而另一个完全无关的技能测试可直接从缓存加载。在CI环境中我们接入了Nx Cloud将缓存命中率从本地的62%提升至93%单次PR构建时间从平均18分钟压缩到4分12秒。这背后是Nx对文件哈希、环境变量、命令参数的全维度指纹计算远超Turborepo仅基于输入文件的简单哈希。第三技能发布需严格遵循语义化版本且版本号必须反映能力变更本质。AI技能的版本升级不能只看代码改动行数。一个patch版本如1.2.1→1.2.2应仅代表修复了某个边界case的bug不影响任何已有调用方一个minor版本1.2.2→1.3.0意味着新增了可选参数或非破坏性功能而major版本1.3.0→2.0.0则代表输入/输出契约发生破坏性变更比如将{city: string}输入改为{location: {lat: number, lng: number}}。Nx原生集成semantic-release但关键在于它的project.json配置允许为每个技能单独定义发布策略。例如核心技能agent-skills/weather要求每次提交必须关联Jira ticket且major版本需经三人Code Review而实验性技能agent-skills/emoji-generator则允许feat:前缀直接触发minor发布。这种粒度控制是通用Monorepo工具无法提供的。第四开发者体验DX必须适配AI工程师的混合背景。很多AI工程师熟悉Python对JavaScript生态陌生。Nx的nx generate命令提供了开箱即用的技能模板nx g agent-skills/skill --namesend-email --typeaction会自动生成libs/send-email/src/lib/send-email.skill.ts主逻辑libs/send-email/src/lib/send-email.spec.ts带JestMSW mock的测试骨架libs/send-email/project.json包含构建、测试、发布的完整配置libs/send-email/tsconfig.lib.json严格隔离的TS配置 一行命令一个符合全部规范的技能就绪。相比之下手写Rollup配置、Vitest环境、ESLint规则对非前端工程师是巨大门槛。我们团队曾做过对比新成员上手一个新技能开发用Nx模板平均耗时23分钟手动搭建同等环境平均耗时3小时17分钟且87%的人会在TypeScript路径别名或ESM/CJS兼容性上出错。2.2 TypeScript为何是不可妥协的基石在AI领域TypeScript常被轻视为“可选装饰”。但在“agent-skills”中它是整个系统的契约中枢其作用远超类型检查首先它定义了技能的“能力契约”Capability Contract。每个技能导出一个SkillDefinition接口强制包含id、description、inputSchema、outputSchema、execute方法。inputSchema和outputSchema不是字符串而是Zod Schema对象——这意味着类型定义与运行时校验完全一致。例如一个天气技能的输入定义export const WeatherInputSchema z.object({ city: z.string().min(2).max(50), units: z.enum([celsius, fahrenheit]).default(celsius), includeForecast: z.boolean().optional().default(false) });这个Zod Schema同时服务于三件事1编译期类型推导IDE能提示city是必填字符串2运行时输入校验防止恶意或错误输入穿透到LLM调用层3自动生成OpenAPI文档供其他系统集成。如果用纯JavaScript这三者将彻底割裂维护成本指数级上升。其次它实现了跨技能的“类型安全调用”。传统做法中技能A调用技能B靠的是字符串ID和any类型参数// 危险无类型保障 const result await executeSkill(weather-lookup, { city: Beijing }); // result类型是any后续处理全靠猜在“agent-skills”中调用是类型驱动的import { weatherLookup } from agent-skills/weather; // 类型自动推导result: { temperature: number; condition: string } const result await weatherLookup({ city: Beijing });这背后是Nx的tsconfig.base.json统一配置和paths别名映射确保所有技能库在TypeScript层面是同一类型空间。当weatherLookup的输入类型变更时所有调用它的技能在nx build阶段就会报错根本无法提交。我们曾因此拦截了3次潜在的生产事故——某次更新将temperature字段从number改为{ value: number; unit: string }结果在CI中立即暴露了5个未同步更新的调用方。最后它支撑了AI特有的“动态能力发现”。Agent运行时需要根据用户请求动态选择最匹配的技能。传统方案用字符串匹配或硬编码路由表。“agent-skills”利用TypeScript的typeof和keyof在构建时生成一个类型安全的技能注册表// 自动生成的技能元数据由Nx脚本生成 export const SKILL_REGISTRY { weather-lookup: { id: weather-lookup, description: Get current weather for a city, inputSchema: WeatherInputSchema, outputSchema: WeatherOutputSchema, // ... 其他元数据 } } as const;这个SKILL_REGISTRY是const断言的其键值对在TypeScript中是精确类型。路由逻辑可以这样写type AvailableSkills keyof typeof SKILL_REGISTRY; function routeToSkill(intent: string): AvailableSkills | null { // 基于intent的NLU结果返回精确的技能ID类型 return intent weather ? weather-lookup : null; }编译器会确保routeToSkill的返回值一定是SKILL_REGISTRY中定义的合法ID杜绝了字符串拼写错误导致的“技能未找到”运行时异常。2.3 semantic-release如何解决AI技能的发布混沌AI技能的发布比普通库更复杂它不仅关乎代码还牵涉到提示词Prompt、示例数据Examples、外部API密钥配置Config。semantic-release在此扮演“发布守门人”角色其核心价值在于将发布决策从人工经验转化为可审计、可追溯、可自动化的规则引擎。我们定制了release.config.js关键配置如下module.exports { branches: [main, { name: beta, prerelease: true }], plugins: [ // 1. 分析提交信息决定版本号 [semantic-release/commit-analyzer, { preset: conventionalcommits, releaseRules: [ { type: feat, scope: weather, release: minor }, { type: fix, scope: weather, release: patch }, { type: refactor, scope: weather, release: patch }, // 重构不改变契约 { type: feat, scope: llm-router, release: major }, // 核心路由变更必major ] }], // 2. 生成CHANGELOG但重点在AI技能变更描述 [semantic-release/release-notes-generator, { preset: conventionalcommits, presetConfig: { types: { feat: { title: ✨ 新增技能能力, description: 新增可被Agent调用的功能 }, fix: { title: 技能缺陷修复, description: 修复输入校验、LLM调用失败等运行时问题 }, docs: { title: 提示词优化, description: 更新prompt template提升LLM输出质量 }, ci: { title: ⚙️ 发布流程增强, description: 改进测试覆盖率、增加新mock场景 } } } }], // 3. 发布到私有npm registry但附加AI特有元数据 [semantic-release/npm, { npmPublish: true, pkgRoot: dist }], // 4. 关键发布后自动更新技能文档站点 [./plugins/ai-skill-docs-publisher.js] // 自定义插件 ] };这个配置解决了三个痛点痛点一提示词变更如何版本化在docs类型的提交中semantic-release会将packages/weather/README.md中的prompt代码块内容哈希并作为promptHash字段写入package.json。下游系统可通过比较promptHash判断提示词是否变更从而决定是否需要重新评估该技能的输出质量。我们曾用此机制发现某次“优化提示词”的提交实际导致了3%的用户查询被错误路由到天气技能及时回滚避免了体验下滑。痛点二如何确保发布包包含所有必需资产AI技能常需附带JSON Schema文件、示例对话数据examples/目录、图标assets/icon.svg。我们编写了verifyConditions钩子在发布前扫描dist/目录强制校验这些文件是否存在且非空。缺失任一文件发布立即失败。这杜绝了“发布后发现示例数据丢失导致Agent演示失败”的尴尬。痛点三如何让非技术人员理解发布影响CHANGELOG不再只是“修复了XX bug”而是明确标注影响范围。例如 技能缺陷修复weather-lookup: 修复当城市名含特殊字符如Café时API调用失败的问题。影响所有使用天气技能的Agent此前对此类城市名返回空结果。email-sender: 修复附件大小超过10MB时未正确抛出AttachmentTooLargeError。影响调用方若未捕获此错误可能导致静默失败。这种表述让产品经理、测试工程师一眼就能判断本次发布是否需要回归测试。3. 核心技能模块详解与实操要点3.1 技能的标准结构与生命周期一个符合“agent-skills”规范的技能其物理结构严格遵循以下约定以agent-skills/weather为例libs/weather/ ├── project.json # Nx项目配置构建、测试、发布指令 ├── tsconfig.json # 技能专属TS配置继承base但禁用any ├── src/ │ ├── index.ts # 入口导出SkillDefinition及便捷调用函数 │ ├── lib/ │ │ ├── weather.skill.ts # 核心逻辑execute函数 输入/输出Schema │ │ ├── weather.api.ts # 封装第三方天气API调用含重试、熔断 │ │ └── weather.mock.ts # 用于测试的MSW mock handler │ ├── assets/ │ │ └── icon.svg # 技能图标用于Agent UI展示 │ └── examples/ │ ├── sunny.json # 典型成功案例输入/输出对 │ └── error-city.json # 典型错误案例如城市不存在 └── README.md # 技能文档用途、输入说明、输出说明、示例、提示词版本关键实操要点提示project.json中的targets是技能的“能力说明书”。必须明确定义build、test、lint、release四个target。其中releasetarget必须调用semantic-release且dependsOn必须包含test和lint确保“不测试不发布不检查不发布”。index.ts是技能对外的唯一门面其导出必须精简// libs/weather/src/index.ts import { SkillDefinition } from agent-skills/core; import { weatherLookup } from ./lib/weather.skill; // 1. 导出技能定义供Agent运行时注册 export const WeatherSkill: SkillDefinition weatherLookup; // 2. 导出便捷调用函数供开发者测试或调试 export { weatherLookup }; // 3. 绝对禁止导出内部实现细节如api.ts中的fetchWeather // 所有内部模块必须在lib/下且index.ts不暴露这种设计强制了“契约与实现分离”。当weather.api.ts需要升级为支持新的天气服务商时只要weather.skill.ts的execute函数签名不变WeatherSkill的契约就未破坏下游无需任何修改。生命周期管理的核心是“构建产物净化”。nx build weather生成的dist/目录必须只包含运行时必需文件。我们通过rollup.config.js严格控制✅ 必须包含index.js、index.d.ts、assets/icon.svg、examples/*.json❌ 禁止包含src/源码、__tests__/、node_modules/、.gitignore、任何.ts文件⚠️ 特殊处理README.md中的prompt代码块会被提取并写入dist/prompt.txt供运行时动态加载。这个净化过程由Nx的buildtarget自动执行。我们曾因疏忽在dist/中遗留了src/lib/__mocks__/目录导致生产环境意外加载了mock函数造成技能永远返回假数据。自此我们在CI中增加了ls dist | grep -q __mocks__ exit 1 || true的校验步骤。3.2 输入/输出Schema的设计哲学与Zod实战在AI Agent中“输入校验”不是锦上添花而是安全底线。一个未校验的city参数可能被注入恶意SQL片段一个未限制长度的userQuery可能触发LLM的上下文溢出。Zod在此不是简单的类型转换器而是运行时的第一道防火墙。Schema设计的三条铁律最小权限原则Principle of Least Privilege只接受绝对必需的字段且每个字段施加最严苛的约束。例如天气技能的输入export const WeatherInputSchema z.object({ // ❌ 错误接受任意字符串 // city: z.string(), // ✅ 正确限定地理有效字符防注入 city: z.string() .min(2, 城市名至少2个字符) .max(50, 城市名最多50个字符) .regex(/^[a-zA-Z\u4e00-\u9fa5\s\-]$/, 仅允许字母、汉字、空格、连字符、撇号), // ❌ 错误允许任意数字 // units: z.number(), // ✅ 正确枚举限定杜绝无效值 units: z.enum([celsius, fahrenheit]).default(celsius), // ❌ 错误布尔值易被篡改 // includeForecast: z.boolean(), // ✅ 正确显式定义可选性避免undefined歧义 includeForecast: z.boolean().optional().default(false) }).strict(); // .strict()禁止多余字段防未知参数攻击错误信息必须面向终端用户而非开发者Zod的safeParse返回的错误对象默认是技术性的。我们封装了formatZodError函数export function formatZodError(error: z.ZodError): string { return error.issues.map(issue { // 将技术路径转为用户语言 const field issue.path.join(.); // city - 城市名 switch(field) { case city: return 城市名${issue.message}; case units: return 温度单位必须是“摄氏度”或“华氏度”; default: return ${field} ${issue.message}; } }).join(; ); }当用户输入{city: }时技能返回的错误是“城市名至少2个字符”而非冰冷的“String must contain at least 2 character(s)”——这直接决定了Agent能否向用户给出清晰反馈。Schema必须与LLM提示词协同演进我们在README.md中强制要求prompt代码块旁标注zod-schema-version: v1.2。当Zod Schema升级如city字段新增countryCodeprompt必须同步更新明确指示LLM“请仅根据city和countryCode两个字段生成响应忽略其他字段”。这个协同由CI中的check-prompt-sync脚本保障它解析README.md中的prompt提取所有提及的字段名与Zod Schema的keyof进行比对不一致则失败。我们曾因此发现某次Schema新增language字段但prompt未更新导致LLM仍按旧逻辑处理输出中文而非指定语言。实操避坑Zod与TypeScript类型推导的陷阱Zod Schema生成的TypeScript类型有时与直觉不符。例如const UserSchema z.object({ id: z.string().uuid(), // UUID字符串 tags: z.array(z.string()).default([]) // 默认空数组 }); type UserType z.infertypeof UserSchema; // UserType 的 tags 类型是 string[]但注意它允许undefined // 因为 .default([]) 是运行时默认值TypeScript类型仍是 string[] | undefined正确解法是使用.optional()明确意图tags: z.array(z.string()).optional().default([]) // 类型变为 string[]这个细节我们在团队内部培训中反复强调因为它直接导致了两次线上BugLLM返回的tags为null而TypeScript类型认为它是string[]解构时崩溃。3.3 技能测试策略从单元到端到端的防御纵深AI技能的测试不能只停留在“函数能跑通”。我们构建了三层测试防线第一层单元测试Unit Test—— 验证逻辑内核目标隔离execute函数验证其在各种输入下的确定性行为。使用Jest MSWMock Service Worker。// libs/weather/src/lib/weather.skill.spec.ts import { weatherLookup } from ./weather.skill; import { setupServer } from msw/node; import { rest } from msw; const server setupServer( rest.get(https://api.weather.com/v3/wx/forecast/daily/*, (req, res, ctx) { // 模拟不同响应 if (req.url.searchParams.get(geocode) 40.7128,-74.0060) { return res(ctx.status(200), ctx.json({ temp: 22, condition: Sunny })); } return res(ctx.status(404), ctx.json({ error: City not found })); }) ); beforeAll(() server.listen()); afterEach(() server.resetHandlers()); afterAll(() server.close()); describe(weatherLookup, () { it(should return temperature and condition for valid city, async () { const result await weatherLookup({ city: New York }); expect(result).toEqual({ temperature: 22, condition: Sunny }); }); it(should throw error for invalid city, async () { await expect(weatherLookup({ city: UnknownCity123 })).rejects.toThrow(City not found); }); });关键技巧MSW的resetHandlers()确保每个测试用例的网络mock是干净的避免测试间污染。我们曾因忘记resetHandlers()导致一个测试用例的mock覆盖了另一个造成间歇性失败。第二层集成测试Integration Test—— 验证技能链路目标验证技能与真实依赖如数据库、缓存、消息队列的交互。使用nx test --groupintegration运行。// libs/weather/src/integration/weather.integration.spec.ts import { createTestApp } from agent-skills/testing; // 自研测试App工厂 import { WeatherSkill } from ../index; describe(WeatherSkill Integration, () { let app; beforeAll(async () { app await createTestApp(); // 启动轻量级测试环境含Redis、PostgreSQL }); it(should cache API response for same city, async () { // 第一次调用走API await WeatherSkill.execute({ city: Beijing }); // 第二次调用应走Redis缓存 const start Date.now(); await WeatherSkill.execute({ city: Beijing }); const duration Date.now() - start; expect(duration).toBeLessThan(100); // 缓存响应100ms }); });关键技巧createTestApp会自动注入jest-mock-redis和jest-mock-pg确保测试不依赖真实基础设施但又能验证缓存逻辑是否生效。第三层端到端测试E2E Test—— 验证Agent整体行为目标模拟真实用户请求验证从NLU解析、技能路由、执行到响应生成的全链路。使用Cypress 自研agent-test-runner。// e2e/weather.e2e.spec.ts describe(Weather Agent E2E, () { it(should answer What\s the weather in Tokyo? with temperature and condition, () { cy.visit(/chat); cy.get([data-testidchat-input]).type(What\s the weather in Tokyo?{enter}); // 断言Agent调用了weather技能 cy.get(agent-log).should(contain, Executing skill: weather-lookup); // 断言最终响应包含关键信息 cy.get([data-testidchat-response]).should(contain, 22°C); cy.get([data-testidchat-response]).should(contain, Sunny); }); });关键技巧agent-log是我们在Agent SDK中注入的自定义Cypress命令能捕获并断言内部技能调用日志这是普通UI测试无法做到的深度验证。4. 实操全流程从零创建一个天气技能4.1 初始化工作区与技能骨架假设你已安装Node.js 18和Nx CLI。第一步创建一个全新的Nx工作区npx create-nx-workspacelatest agent-skills-demo \ --presetapps \ --appNameagent-core \ --stylecss \ --lintereslint \ --packageManagerpnpm \ --nxCloudfalse cd agent-skills-demo注意--presetapps选择应用型工作区而非库型因为最终产物是可运行的Agent服务。--nxCloudfalse禁用云端缓存便于本地调试。接着安装“agent-skills”核心依赖pnpm add -D nrwl/workspace nrwl/node nrwl/jest nrwl/eslint-plugin nrwl/nx-plugin pnpm add zod types/zod jest jest/globals msw testing-library/react现在生成第一个技能——天气查询nx g nrwl/node:library weather --directorylibs --importPathagent-skills/weather --publishable --buildable这条命令创建了libs/weather但还需手动调整以符合“agent-skills”规范。我们提供了一个setup-agent-skill脚本已集成到项目模板中pnpm exec nx g agent-skills/skill --nameweather --typeaction该脚本会替换project.json为预设的技能配置含build/test/release targets创建src/lib/weather.skill.ts包含标准SkillDefinition模板创建src/lib/weather.api.ts封装天气API客户端创建src/examples/目录和初始示例更新tsconfig.base.json添加agent-skills/*路径别名执行后目录结构即符合前述标准。4.2 实现核心逻辑与Schema定义编辑libs/weather/src/lib/weather.skill.tsimport { z } from zod; import { SkillDefinition } from agent-skills/core; import { fetchWeather } from ./weather.api; // 1. 定义输入Schema遵循前述铁律 export const WeatherInputSchema z.object({ city: z.string() .min(2, 城市名至少2个字符) .max(50, 城市名最多50个字符) .regex(/^[a-zA-Z\u4e00-\u9fa5\s\-]$/, 仅允许字母、汉字、空格、连字符、撇号), units: z.enum([celsius, fahrenheit]).default(celsius), includeForecast: z.boolean().optional().default(false) }).strict(); // 2. 定义输出Schema export const WeatherOutputSchema z.object({ temperature: z.number().min(-100).max(100), condition: z.enum([Sunny, Cloudy, Rainy, Snowy, Stormy]), humidity: z.number().min(0).max(100).optional(), forecast: z.array(z.object({ date: z.string().date(), high: z.number(), low: z.number() })).optional() }); // 3. 实现execute函数 export const weatherLookup: SkillDefinitiontypeof WeatherInputSchema, typeof WeatherOutputSchema { id: weather-lookup, description: 获取指定城市的当前天气和预报, inputSchema: WeatherInputSchema, outputSchema: WeatherOutputSchema, async execute(input) { // 4. 运行时校验 const parsed WeatherInputSchema.safeParse(input); if (!parsed.success) { throw new Error(输入校验失败: ${formatZodError(parsed.error)}); } try { // 5. 调用API const apiResponse await fetchWeather(parsed.data.city, parsed.data.units); // 6. 运行时输出校验双重保险 const output WeatherOutputSchema.safeParse(apiResponse); if (!output.success) { throw new Error(API响应校验失败: ${formatZodError(output.error)}); } return output.data; } catch (error) { // 7. 统一错误处理 if (error instanceof TypeError error.message.includes(fetch)) { throw new Error(天气服务暂时不可用请稍后再试); } throw error; } } }; // 8. 导出便捷函数供测试 export function weatherLookupFn(input: z.inputtypeof WeatherInputSchema) { return weatherLookup.execute(input); }参数选择背后的计算city的max(50)并非随意设定。我们分析了全球城市名数据库最长的城市名是泰国的Krung Thep Mahanakhon Amon Rattanakosin Mahinthara Yuthaya Mahadilok Phop Noppharat Ratchathani Burirom Udomratchaniwet Mahasathan Amon Piman Awatan Sathit Sakkathattiya Witsanukam Prasit共167字符但实际API限制通常为50-100。我们取50是平衡了安全性防长字符串攻击与实用性覆盖99.9%的城市。temperature的min(-100)/max(100)基于地球实测极值最低-89.2°C南极最高56.7°C死亡谷。设置±100留有余量且能被所有主流LLM准确理解。4.3 配置构建、测试与发布流水线project.json是技能的“宪法”必须精确配置。以下是libs/weather/project.json的关键部分{ root: libs/weather, sourceRoot: libs/weather/src, projectType: library, targets: { build: { executor: nrwl/node:package, outputs: [{workspaceRoot}/dist/libs/weather], options: { outputPath: dist/libs/weather, tsConfig: libs/weather/tsconfig.lib.json, project: libs/weather/package.json, externalDependencies: all, assets: [ libs/weather/src/assets, libs/weather/src/examples ], generatePackageJson: true } }, test: { executor: nrwl/jest:jest, options: { jestConfig: libs/weather/jest.config.ts, passWithNoTests: true } }, lint: { executor: nrwl/linter:eslint, options: { lintFilePatterns: [libs/weather/**/*.ts] } }, release: { executor: nx:run-commands, options: { command: npx semantic-release, cwd: libs/weather }, dependsOn: [test, lint] } } }关键配置解读externalDependencies: all告诉Rollup不要打包zod、node-fetch等外部依赖由宿主Agent决定版本避免冲突。assets数组确保assets/和examples/被复制到dist/这是运行时必需的。dependsOn: [test, lint]发布前强制执行测试和代码检查这是质量红线。jest.config.ts需启用MSWimport { getJestProjects } from nrwl/jest; import { pathsToModuleNameMapper } from ts-jest; const tsConfig require(./tsconfig.base.json); export default { projects: getJestProjects(), setupFilesAfterEnv: [rootDir/libs/weather/src/test-setup.ts], moduleNameMapper: pathsToModuleNameMapper(ts
返回列表