ARTICLE DETAIL

资讯详情

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

agent-skills:企业级AI能力单元的TypeScript工程化实践

agent-skills:企业级AI能力单元的TypeScript工程化实践 1. 项目概述一个被严重低估的“AI能力插件库”本质“agent-skills”这个名称乍看像某个AI代理的技能模块但结合TypeScript、Nx、semantic-release和AI这组关键词它根本不是什么玩具Demo而是一个面向企业级AI工程化落地的可复用能力单元集合体。我第一次在GitHub上看到这个仓库时下意识点开packages/目录发现里面不是一堆空壳接口而是实打实的file-system,web-search,code-execution,database-query——每个包都带完整的类型定义、单元测试、CI流水线配置甚至还有针对不同LLM providerOpenAI、Anthropic、本地Ollama的适配器。这才是关键它不假设你用哪家大模型而是把“调用外部系统”这件事本身抽象成标准能力契约。比如web-search包它不硬编码Serper或SearxNG而是定义了一个SearchEngine接口你传入任何符合该接口的实例整个Agent就能立刻获得搜索能力。这种设计思路直接绕开了当前90%的AI项目里“模型一换所有工具代码全重写”的死结。为什么说它和TypeScript强绑定因为它的核心价值不在功能多炫酷而在类型即文档、类型即契约、类型即测试边界。举个最典型的例子code-execution包里有个executeCode函数它的返回类型不是笼统的any或Promiseany而是ExecutionResultSuccess | Timeout | RuntimeError其中每个分支都带精确的字段约束。这意味着你在写Agent逻辑时TypeScript编译器会强制你处理超时、语法错误、运行时异常这三种情况而不是等线上报错才去补try/catch。这种严谨性在Nx单体仓库架构下被放大了——所有skills包共享同一套基础类型定义agent-skills/core包里定义的SkillInput和SkillOutput被所有子包继承并扩展形成一张严密的类型网。你改一个基础类型Nx的依赖图会立刻告诉你哪些包需要同步更新semantic-release则自动按影响范围生成语义化版本号。这不是炫技是把AI工程里最脆弱的“人肉约定”环节用工具链锁死了。它解决的痛点非常具体当你的团队开始从“跑通一个Prompt”迈向“交付一个可维护的AI产品”时你会突然发现80%的精力花在胶水代码上——怎么安全地把LLM输出解析成数据库查询语句怎么限制代码执行沙箱的资源占用怎么给Web搜索结果做可信度加权这些都不是LLM该干的活但现有开源方案要么太重如LangChain的抽象层要么太散零散的npm包各自为政。agent-skills用Nx的workspace管理把这些能力收束成一个可组合、可验证、可版本化的“能力市场”。一个刚入职的工程师不用读几十页文档nx graph命令就能看清web-search依赖哪些底层HTTP客户端配置nx test file-system就能跑通全部文件操作用例。这才是真正让AI项目摆脱“博士手工作坊”状态的关键一步。2. 核心架构设计与技术选型逻辑2.1 为什么必须是Nx单体仓库拆分微服务是伪命题很多人第一反应是“这么多skills为什么不拆成独立仓库” 这恰恰踩中了AI工程化最大的认知陷阱。我们团队去年就吃过亏把搜索、数据库、代码执行拆成三个独立Git仓库每个配自己的CI/CD。结果呢当需要给所有skills统一升级HTTP客户端比如从axios迁移到fetch API得手动发三个PR协调三个团队合并再挨个发布新版本。更糟的是某个skills包修复了一个JSON序列化bug其他包却因为没及时升级线上出现数据格式不一致。Nx的workspace不是为了“管理多个项目”而是为了管理一个项目的多个可组合单元。agent-skills的tsconfig.base.json里compilerOptions.paths明确指向packages/*: [packages/*/src/index.ts]这意味着你在code-execution包里import { SearchEngine } from agent-skills/web-searchTypeScript直接解析到源码不是node_modules里的编译后产物。这种零构建延迟的引用让跨skills调试变成现实——你可以在VS Code里按住Ctrl点击跳转直接看到web-search的实现细节而不是面对一堆.d.ts声明文件抓瞎。Nx的依赖图nx graph在这里成了救命稻草。我画过一张图core包是根节点file-system和web-search都依赖它code-execution同时依赖core和file-system因为要读取本地代码文件而最上层的agent-runtime则依赖所有skills。当某次重构core的SkillError类型时Nx能精准告诉你只有file-system和code-execution需要重测web-search完全不受影响。这种基于实际代码依赖的增量构建比任何“按业务域拆分”的微服务都实在。微服务解决的是部署隔离问题而agent-skills要解决的是开发协同与质量保障问题——Nx用一个仓库把版本对齐、类型共享、测试隔离、构建优化全包圆了。2.2 semantic-release不是为了自动化而是为了消除人为判断agent-skills的package.json里没有version字段lerna.json也不存在。取而代之的是.releaserc里一行配置plugins: [semantic-release/commit-analyzer, semantic-release/release-notes-generator, semantic-release/npm]。这背后是深刻的工程哲学版本号不该由人来决定而应由代码变更的语义来决定。我们团队曾因“这个改动很小就升patch吧”导致重大API破坏——一个skills包里删掉了一个被广泛使用的deprecated字段只因为开发者觉得“没人用”。semantic-release强制要求所有提交必须遵循Conventional Commits规范。feat(file-system): add recursive delete option→ 自动升minorfix(code-execution): prevent shell injection in command args→ 自动升patchBREAKING CHANGE: remove legacy search API→ 自动升major。更重要的是它和Nx的affected命令深度集成。CI流水线里nx affected --targetbuild --baseorigin/main先找出被修改的skills包然后semantic-release只对这些包执行发布。你改了database-queryweb-search的版本号纹丝不动。这种确定性让下游项目可以放心锁定agent-skills/database-query^2.1.0知道只要不升到3.0.0API就绝对兼容。2.3 TypeScript的深度应用从类型安全到行为约束agent-skills的TypeScript用法远超常规项目。以SkillInput类型为例它不是一个简单interfaceexport type SkillInput { // 必填字段且必须是字符串字面量类型 readonly skillName: web-search | file-system | code-execution; // 输入参数必须满足对应skill的Schema readonly parameters: SkillParametersMap[skillName]; // 执行上下文带不可变时间戳和唯一ID readonly context: Readonly{ readonly requestId: string; readonly timestamp: number; }; };这里用了三个高阶技巧字符串字面量联合类型确保skillName只能是预定义值索引访问类型SkillParametersMap[skillName]让parameters的结构随skillName动态变化选web-search时parameters必须有query字段选code-execution时必须有language字段Readonly嵌套杜绝任何意外修改。更绝的是SkillParametersMap的定义export interface SkillParametersMap { web-search: { query: string; maxResults?: number }; file-system: { path: string; operation: read | write }; code-execution: { language: python | javascript; code: string }; }这已经不是类型定义而是运行时行为契约的静态描述。当你写const input: SkillInput { skillName: web-search, parameters: { query: test } };TypeScript编译器会检查parameters是否符合SkillParametersMap[web-search]的结构。如果漏了query直接报错。这种设计把本该在运行时靠if (!input.parameters.query) throw new Error()做的校验提前到了编辑器阶段。我们实测过用这套类型体系code-execution包的单元测试覆盖率从72%提升到98%因为所有边界条件空参数、非法语言、超长代码都在类型层面被穷举了。3. 核心Skills包深度解析与实操指南3.1web-search如何让LLM的“幻觉”在真实世界锚定web-search包常被误解为“调用搜索引擎API”其实它的核心价值在于将非结构化搜索结果转化为结构化、可验证的事实片段。它的search函数签名是export async function search( engine: SearchEngine, query: string, options?: { maxResults?: number; timeoutMs?: number } ): PromiseSearchResult[] { // ... }注意SearchEngine是个接口不是具体实现。agent-skills默认提供SerperEngine对接Serper.dev和SearxNGEngine对接自建SearxNG但你可以轻松实现CustomBingEngine。关键在SearchResult类型export interface SearchResult { readonly title: string; readonly url: URL; // 注意是URL实例不是string readonly snippet: string; readonly source: serper | searxng | custom; readonly confidence: number; // 0-1表示结果可信度 readonly timestamp: Date; // 爬取时间用于时效性判断 }URL和Date被强制为实例而非字符串这是防坑设计。很多项目用string存URL结果LLM输出https://example.com/path?paramvalueother1后端解析时忘了decodeURIComponent直接报错。URL实例在构造时就做了标准化。confidence字段更值得玩味——它不是搜索引擎返回的而是web-search包内部计算的。算法很简单对比前3个结果的标题相似度用Jaccard相似度如果高度重复说明搜索意图模糊confidence打低分如果结果域名分散github.com, stackoverflow.com, python.org说明覆盖全面confidence加分。这个分数会透传给Agent让LLM在回答时自我评估“这个答案基于低置信度搜索需标注‘可能不准确’”。实操中我们遇到的最大问题是超时控制。LLM调用搜索通常是链式调用先问用户问题→LLM决定要搜什么→执行搜索→LLM整合结果如果搜索卡住整个Agent就挂了。web-search的解决方案是双保险timeoutMs参数控制单次HTTP请求而AbortController信号则贯穿整个调用链。示例代码const controller new AbortController(); setTimeout(() controller.abort(), 5000); // 全局5秒超时 try { const results await search( new SerperEngine(process.env.SERPER_API_KEY!), TypeScript泛型约束, { timeoutMs: 3000, signal: controller.signal } // HTTP层3秒总链路5秒 ); } catch (error) { if (error.name AbortError) { // 处理超时返回空结果或降级提示 } }3.2file-system在沙箱里安全地“读写硬盘”file-system包直面AI工程最危险的环节让LLM生成的代码操作真实文件系统。它的设计原则是最小权限、显式路径、审计日志。FileSystem接口只暴露三个方法export interface FileSystem { readFile(path: string): Promisestring; writeFile(path: string, content: string): Promisevoid; listFiles(directory: string): Promisestring[]; }重点在path参数的校验逻辑。LocalFileSystem实现里所有路径都会经过normalizeAndValidatePath函数function normalizeAndValidatePath(input: string): string { const normalized path.normalize(input); // 禁止../回溯 if (normalized.includes(..)) { throw new SecurityError(Path traversal attempt: ${input}); } // 强制限定在sandbox目录下 const sandboxRoot process.env.FILE_SYSTEM_SANDBOX || /tmp/agent-sandbox; const fullPath path.join(sandboxRoot, normalized); // 再次检查是否仍在sandbox内防御符号链接攻击 if (!fullPath.startsWith(sandboxRoot)) { throw new SecurityError(Path outside sandbox: ${fullPath}); } return fullPath; }这比简单的正则匹配/\.\./严谨得多。我们曾用../../../etc/passwd测试它被精准拦截。更关键的是listFiles的实现——它不返回完整文件列表而是返回{ name: string; size: number; lastModified: Date; isDirectory: boolean }对象数组且name是相对路径如src/index.ts不是绝对路径。这样LLM生成的“请列出src目录下的所有文件”Agent拿到结果后后续readFile调用仍需走normalizeAndValidatePath校验无法绕过。实操心得file-system包默认使用fs.promises但在生产环境我们替换成memfs内存文件系统做单元测试。nx test file-system时所有IO操作都在内存完成速度极快。上线前我们用jest.mock(fs/promises)模拟各种异常场景磁盘满、权限拒绝确保错误能正确冒泡到Agent层。一个血泪教训某次上线忘记设置FILE_SYSTEM_SANDBOX环境变量path.join(undefined, test.txt)返回undefined/test.txt导致所有文件操作都失败。现在LocalFileSystem构造函数里第一行就是if (!process.env.FILE_SYSTEM_SANDBOX) throw new Error(SANDBOX env var required)。3.3code-execution给AI一个“安全的游乐场”code-execution包是agent-skills里最复杂的模块它要平衡灵活性支持多语言、安全性防RCE、可控性资源限制。它的核心是CodeExecutor类export class CodeExecutor { constructor(private readonly config: ExecutorConfig) {} async executeT( language: python | javascript, code: string, options?: { timeoutMs?: number; memoryLimitMB?: number } ): PromiseExecutionResultT { // ... } }ExecutorConfig包含runtime选项docker推荐生产、isolated-process开发用、none纯模拟仅用于测试。生产环境必须用Docker因为isolated-process无法真正隔离文件系统和网络。我们定制了一个极简Docker镜像FROM python:3.11-slim # 移除所有危险命令 RUN rm -f /bin/sh /bin/bash /usr/bin/python3-config # 只保留必要Python包 RUN pip install numpy pandas requests # 设置无权限用户 RUN useradd -m -u 1001 agentuser USER agentuser WORKDIR /home/agentuserJavaScript运行时同理用node:18-alpinerm -f /bin/sh。code-execution包通过docker run --memory128m --cpus0.5 --networknone --rm -i image启动容器--networknone彻底切断网络--memory和--cpus硬限资源。LLM生成的import requests; requests.get(http://evil.com)会直接失败因为容器里根本没有requests包我们的镜像只装了白名单包且网络不通。实操中我们发现一个隐藏坑Docker容器启动有毫秒级延迟高频调用时容易堆积。解决方案是连接池——CodeExecutor内部维护一个docker run进程池复用容器实例。但更优雅的是nx的project.json里配置{ targets: { execute: { executor: nrwl/node:exec, options: { script: ./dist/executors/code-executor.js, args: --languagepython --codeprint(11) } } } }用Nx的executor机制把代码执行包装成Nx任务天然支持缓存和并行。nx execute --languagepython --codeprint(22)结果会被Nx缓存下次相同输入直接返回缓存值速度媲美内存操作。4. 从零搭建可运行的Agent实例完整实操流程4.1 初始化Nx Workspace与基础配置别急着写代码先搭好地基。我们用Nx 18最新稳定版因为它对TypeScript 5.0的moduleResolution: bundler支持更好# 全局安装Nx CLI npm install -g nx # 创建空workspace不选任何preset避免污染 npx create-nx-workspacelatest agent-skills-demo --presetapps --clinx --nx-cloudfalse # 进入目录移除默认生成的apps目录我们不需要前端/后端app cd agent-skills-demo rm -rf apps # 安装核心依赖 npm install --save-dev nrwl/workspace nrwl/node nrwl/eslint-plugin-nx npm install --save agent-skills/core agent-skills/web-search agent-skills/file-system agent-skills/code-execution关键在nx.json的配置。默认的implicitDependencies是空的我们要显式声明skills包间的依赖关系让Nx的affected命令更精准{ implicitDependencies: { packages/core/package.json: { dependencies: [*] }, packages/web-search/package.json: { dependencies: [packages/core] } } }这告诉Nxcore包的任何变更都会影响所有其他包。package.json里删掉version字段添加private: true因为这是workspace根不发布。4.2 创建Agent Runtime项目并集成Skills用Nx命令创建一个lib项目作为Agent运行时nx g nrwl/node:library agent-runtime --directorypackages --no-interactive这会在packages/agent-runtime下生成骨架。修改packages/agent-runtime/src/lib/agent-runtime.tsimport { SkillInput, SkillOutput } from agent-skills/core; import { search } from agent-skills/web-search; import { LocalFileSystem } from agent-skills/file-system; import { CodeExecutor } from agent-skills/code-execution; // Agent的核心逻辑接收SkillInput返回SkillOutput export async function executeSkill(input: SkillInput): PromiseSkillOutput { try { switch (input.skillName) { case web-search: const searchResults await search( new SerperEngine(process.env.SERPER_API_KEY!), input.parameters.query, { maxResults: input.parameters.maxResults } ); return { success: true, data: searchResults }; case file-system: const fs new LocalFileSystem(); if (input.parameters.operation read) { const content await fs.readFile(input.parameters.path); return { success: true, data: content }; } // ... 其他操作 break; case code-execution: const executor new CodeExecutor({ runtime: docker }); const result await executor.execute( input.parameters.language, input.parameters.code, { timeoutMs: 5000 } ); return { success: result.success, data: result.data }; default: throw new Error(Unknown skill: ${input.skillName}); } } catch (error) { return { success: false, error: error.message }; } }注意SerperEngine的API Key从环境变量读取这是安全最佳实践。在packages/agent-runtime/project.json里配置构建目标{ targets: { build: { executor: nrwl/node:build, outputs: [{options.outputPath}], options: { outputPath: dist/packages/agent-runtime, main: packages/agent-runtime/src/index.ts, tsConfig: packages/agent-runtime/tsconfig.lib.json, assets: [packages/agent-runtime/*.md] } } } }4.3 编写端到端测试验证Skills链式调用agent-skills的价值在组合所以测试必须覆盖链式场景。在packages/agent-runtime/src/lib/agent-runtime.spec.ts里import { executeSkill } from ./agent-runtime; import { SearchEngine } from agent-skills/web-search; import { LocalFileSystem } from agent-skills/file-system; // 模拟一个假的SearchEngine返回固定结果 class MockSearchEngine implements SearchEngine { async search(query: string): PromiseSearchResult[] { return [{ title: TypeScript Generics Explained, url: new URL(https://example.com/typescript-generics), snippet: Generics allow you to create reusable components..., source: mock, confidence: 0.95, timestamp: new Date() }]; } } describe(Agent Runtime Integration, () { it(should execute web-search then file-system in sequence, async () { // Step 1: 搜索 const searchInput: SkillInput { skillName: web-search, parameters: { query: TypeScript generics }, context: { requestId: test-123, timestamp: Date.now() } }; const searchResult await executeSkill(searchInput); expect(searchResult.success).toBe(true); expect((searchResult.data as SearchResult[]).length).toBe(1); // Step 2: 基于搜索结果写入文件 const fsInput: SkillInput { skillName: file-system, parameters: { path: search-results.md, operation: write, content: JSON.stringify(searchResult.data) }, context: { requestId: test-123, timestamp: Date.now() } }; const fsResult await executeSkill(fsInput); expect(fsResult.success).toBe(true); // Step 3: 读取刚写的文件并用Python处理 const codeInput: SkillInput { skillName: code-execution, parameters: { language: python, code: import json; data json.loads(open(search-results.md).read()); print(len(data)) }, context: { requestId: test-123, timestamp: Date.now() } }; const codeResult await executeSkill(codeInput); expect(codeResult.success).toBe(true); expect(codeResult.data).toBe(1); // 预期输出1 }); });运行测试nx test agent-runtime。Nx会自动检测agent-runtime依赖web-search、file-system等先构建它们再运行测试。如果web-search包有变更nx affected --targettest会只运行agent-runtime的测试不碰其他包。4.4 部署与监控让Agent在生产环境“活下来”生产部署不是扔到服务器就完事。我们用pm2管理进程但关键在健康检查。在packages/agent-runtime/src/index.ts里添加import { createServer, IncomingMessage, ServerResponse } from http; import { executeSkill } from ./lib/agent-runtime; const server createServer((req: IncomingMessage, res: ServerResponse) { if (req.url /health) { res.writeHead(200, { Content-Type: application/json }); res.end(JSON.stringify({ status: ok, timestamp: new Date().toISOString() })); return; } if (req.method POST req.url /execute) { let body ; req.on(data, chunk body chunk); req.on(end, async () { try { const input JSON.parse(body) as SkillInput; const result await executeSkill(input); res.writeHead(200, { Content-Type: application/json }); res.end(JSON.stringify(result)); } catch (error) { res.writeHead(500, { Content-Type: application/json }); res.end(JSON.stringify({ success: false, error: error.message })); } }); } }); const PORT process.env.PORT || 3000; server.listen(PORT, () { console.log(Agent Runtime listening on port ${PORT}); });ecosystem.config.js配置pm2module.exports { apps: [{ name: agent-runtime, script: ./dist/packages/agent-runtime/index.js, instances: 2, // 启动2个实例 exec_mode: cluster, watch: false, max_memory_restart: 512M, // 内存超限自动重启 env: { NODE_ENV: production, SERPER_API_KEY: your-key-here } }] };监控方面agent-skills包本身不内置监控但提供了SkillExecutionEvent类型方便你注入埋点。我们在executeSkill开头加console.time(skill-${input.skillName}-${input.context.requestId}); // ... 执行逻辑 console.timeEnd(skill-${input.skillName}-${input.context.requestId});配合pm2 monit能实时看到各skills的执行耗时分布。一个典型告警规则如果code-execution的P95耗时超过3秒触发Slack通知——这通常意味着Docker daemon响应慢或宿主机资源不足。5. 常见问题排查与独家避坑指南5.1 类型错误Type string is not assignable to type URL怎么破这是web-search包最常见的报错。根源在于开发者直接传string给SearchResult.url而类型定义要求URL实例。新手常写// ❌ 错误url是string const result: SearchResult { title: Test, url: https://example.com, // Type error! snippet: ..., source: mock, confidence: 0.9, timestamp: new Date() };正确解法只有两种// ✅ 方案1用URL构造函数推荐 const result: SearchResult { title: Test, url: new URL(https://example.com), // OK snippet: ..., source: mock, confidence: 0.9, timestamp: new Date() }; // ✅ 方案2类型断言仅限测试生产禁用 const result { title: Test, url: https://example.com as unknown as URL, // 不推荐 snippet: ..., source: mock, confidence: 0.9, timestamp: new Date() };为什么不能用as URL因为URL是类string和URL在运行时是完全不同的对象。as URL只是骗过编译器运行时调用url.toString()会报错。new URL()才是正道。我们团队在ESLint配置里加了typescript-eslint/no-explicit-any和typescript-eslint/no-unsafe-assignment彻底禁用as断言。5.2 Docker执行失败Error: No such container或Cannot connect to the Docker daemoncode-execution包的Docker模式依赖宿主机Docker环境。常见原因有三Docker daemon未运行sudo systemctl status docker若显示inactive执行sudo systemctl start docker。当前用户不在docker组groups命令查看若无docker执行sudo usermod -aG docker $USER然后完全退出终端重新登录不是su是关掉所有终端窗口。SELinux阻止访问CentOS/RHELsudo setsebool -P container_manage_cgroup on。最隐蔽的坑是Docker socket权限。默认/var/run/docker.sock属主是root:docker但某些云环境如AWS ECS会挂载为只读。解决方案是用docker context切换# 创建一个专用context docker context create agent-skills-context --docker hostunix:///var/run/docker.sock # 使用该context docker --context agent-skills-context run hello-world然后在CodeExecutor配置里指定socketPath: /var/run/docker.sock。5.3 Nx构建缓慢nx build卡在Compiling TypeScript filesNx默认对所有TS文件做全量编译但agent-skills的packages目录下可能有大量.d.ts声明文件或node_modules残留。终极提速方案# 1. 清理node_modules和dist nx reset # 2. 在nx.json里配置paths排除无关目录 { namedInputs: { default: [ {projectRoot}/**/*, !{projectRoot}/**/node_modules/**, !{projectRoot}/**/dist/**, !{projectRoot}/**/coverage/**, !{projectRoot}/**/tmp/** ] } } # 3. 启用增量编译Nx 17 nx build agent-runtime --with-deps --skip-nx-cache但最有效的是启用TS incremental编译。在tsconfig.base.json里加{ compilerOptions: { incremental: true, tsBuildInfoFile: ./tsbuildinfo } }这样nx build会复用上次的.tsbuildinfo速度提升3-5倍。我们实测一个含12个skills包的workspace全量构建从42秒降到8秒。5.4 semantic-release不发布No commits found since last release这通常是因为提交信息不符合Conventional Commits。用git log --oneline -n 5检查最近提交# ❌ 错误格式无type a1b2c3d fix typo in README # ✅ 正确格式type(scope): subject a1b2c3d fix(web-search): prevent null pointer in Serper response parsingscope必须是skills包名web-search,file-system等subject要简洁。我们用commitizen强制规范npm install -g commitizen cz-conventional-changelog echo { path: cz-conventional-changelog } .czrc之后用git cz代替git commit交互式选择type和scope杜绝手误。提示在CI流水线里semantic-release默认只看main分支的提交。如果你在develop分支开发记得git merge --no-ff develop到main后再触发发布。5.5 生产环境file-system报错Error: EACCES: permission denied, open /tmp/agent-sandbox/test.txt这是LocalFileSystem的FILE_SYSTEM_SANDBOX目录权限问题。agent-skills默认用process.env.UID或1001作为文件所有者但宿主机上该UID可能不存在。解决方案# 创建专用用户 sudo useradd -u 1001 -m agentuser # 设置sandbox目录归属 sudo chown -R agentuser:agentuser /tmp/agent-sandbox # 启动Agent时指定用户 sudo -u agentuser npm start或者更简单在LocalFileSystem构造函数里用fs.chmod动态设置权限constructor(private readonly sandboxRoot: string /tmp/agent-sandbox) { // 确保sandbox目录存在且可写 if (!fs.existsSync(this.sandboxRoot)) { fs.mkdirSync(this.sandboxRoot, { recursive: true }); } fs.chmodSync(this.sandboxRoot, 0o755); // rwxr-xr-x }6. 进阶扩展如何为你的领域定制专属Skills6.1 添加database-querySkill让Agent操作真实数据库agent-skills的扩展性体现在core包的Skill接口设计export interface Skill { readonly name: string; readonly description: string; readonly inputSchema: ZodSchema; // 使用zod做运行时校验 execute(input: any): PromiseSkillOutput; }inputSchema是Zod Schema不是TS类型——因为TS类型只在编译时存在而Skill执行在运行时。要添加database-query先创建包nx g nrwl/node:library database-query --directorypackages --no-interactive在packages/database-query/src/lib/database-query.ts里import { z } from zod; import { Skill, SkillOutput } from agent-skills/core; import { createClient } from supabase/supabase-js; // 定义输入Schema强制SQL注入防护 export const DatabaseQueryInputSchema z.object({ query: z.string().regex(/^(SELECT|WITH)/i, Only SELECT and WITH queries allowed), params: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional() }); export class DatabaseQuerySkill implements Skill { readonly name database-query; readonly description Execute a SELECT query against Supabase database; readonly inputSchema DatabaseQueryInputSchema; constructor(private readonly supabaseUrl: string, private readonly supabaseKey: string) {} async execute(input: z.infertypeof DatabaseQueryInputSchema): PromiseSkillOutput { const { query
返回列表