☰
agent-skills:智能体能力契约化设计与工程实践
2026/10/18 21:59:50 网站建设 项目流程

1. “agent-skills”不是功能模块,而是一套可验证、可复用、可组合的智能体能力契约

你第一次在 GitHub 仓库名、CLI 工具文档或前端工程目录里看到agent-skills这个词时,大概率会下意识把它当成一个“技能插件包”或者“AI 功能集合”。但实际踩过坑、写过测试、部署过三轮以上智能体服务的人会立刻意识到:它根本不是代码库,而是一份能力接口协议(Capability Interface Contract)——就像 TCP/IP 是网络通信的契约,POSIX 是操作系统调用的契约,agent-skills是智能体(Agent)与外部世界交互时,必须明确声明、严格履行、可被自动化验证的最小行为契约。

这个词高频出现在 CLI 工具(如codex cli、zcode cli、trae cli)的配置文件里,也频繁出现在前端 UI 工程中useAgentSkill()的 hook 参数列表中,更在 TDD(Test-Driven Development)测试套件里作为describe('skill: file-upload')的命名根节点。它不提供模型推理、不封装 API 调用、不渲染 UI 组件——它只做一件事:定义“这个智能体能做什么”这件事本身必须可描述、可断言、可隔离测试。比如:

  • skill: http-request不等于“能发 HTTP 请求”,而是指:
    ✅ 必须接受method,url,headers,body四个输入参数;
    ✅ 输出必须是结构化对象{ status: number, headers: Record<string, string>, body: string | object };
    ✅ 错误必须统一抛出AgentSkillError类型,且error.code必须为"HTTP_ERROR";
    ✅ 超时必须由timeoutMs参数控制,不可硬编码。

  • skill: git-commit不等于“能提交 Git”,而是指:
    ✅ 输入必须包含repoPath,message,files(路径数组),且files必须经fs.stat()验证存在;
    ✅ 执行前必须校验git status --porcelain是否有未暂存变更;
    ✅ 成功后必须返回commitHash和changedFiles数组;
    ✅ 若git add失败,错误code必须为"GIT_ADD_FAILED",而非泛化的"EXEC_ERROR"。

这就是为什么所有热词都绕不开它:api error: 400 the supported api model names are deepseek-flash, deepseek-v4—— 这不是模型不支持,而是调用方声明的skill: llm-inference接口契约中,model字段的枚举值校验失败;login failed. check api token or gitlab version—— 实际是skill: auth-gitlab契约要求token必须通过gitlab.com/api/v4/user端点实时校验,而当前 token 已过期或权限不足;failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen—— 表面是 Docker 连接失败,本质是skill: docker-exec契约中socketPath参数未按 Windows Docker Desktop 的命名规范(npipe://./pipe/docker_engine)传入。

提示:别再把agent-skills当成 npm 包去npm install。它是一套设计约定,落地形式可以是 TypeScript interface、JSON Schema 文件、OpenAPI 3.0 spec 片段,甚至只是测试用例中的it('should validate input shape')断言。它的价值不在“装”,而在“约”。

我见过太多团队在初期直接跳过这一步,用fetch(url)硬编码在 Agent 逻辑里,结果三个月后:

  • 前端要 mock 所有 API 调用做离线演示,却因没有统一契约,只能手动 patch 每个fetch;
  • CLI 工具升级到新版本,因旧版skill: file-download返回的是Buffer,新版要求Uint8Array,导致整个 pipeline 中断;
  • TDD 测试覆盖率标称 92%,但skill: llm-parse-json的错误分支从未被触发,因为没人定义过"invalid_json"错误码该返回什么结构。

所以,agent-skills的第一层真相是:它是智能体工程里的“类型系统前置”——在写任何一行业务逻辑之前,先用契约锁定输入/输出/错误的形状,让所有协作方(CLI、UI、测试、运维)在同一张语义地图上对齐。后面所有 CLI 配置、API 调用、前端集成、TDD 用例,都是这张地图的自然延伸。

2. CLI 工具链如何将agent-skills契约转化为可执行的命令行能力

当你运行codex cli run --skill http-request --config ./skills/http.yaml时,CLI 并不是在“调用一个函数”,而是在加载并验证一份技能契约实例,然后驱动符合该契约的执行器(Executor)完成闭环。这个过程远比curl命令复杂,其核心在于三层解耦:契约声明层(Declarative)、执行器注册层(Executor Registry)、运行时绑定层(Runtime Binding)。我们以zcode cli的典型工作流为例拆解:

2.1 契约声明层:YAML/JSON 是技能的“身份证”

CLI 工具不接受裸参数,它强制要求每个--skill对应一个结构化配置文件。例如skills/file-upload.yaml:

# skills/file-upload.yaml name: "file-upload" version: "1.2.0" contract: input: type: "object" required: ["filePath", "targetUrl", "authToken"] properties: filePath: type: "string" description: "本地绝对路径,必须存在且可读" targetUrl: type: "string" format: "uri" authToken: type: "string" minLength: 32 output: type: "object" required: ["uploadId", "sizeBytes", "uploadedAt"] properties: uploadId: type: "string" pattern: "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$" sizeBytes: type: "integer" minimum: 1 uploadedAt: type: "string" format: "date-time" errors: - code: "FILE_NOT_FOUND" message: "The specified file does not exist or is inaccessible" - code: "HTTP_4XX" message: "Client error from target server" - code: "HTTP_5XX" message: "Server error from target server"

这个 YAML 不是文档,而是可被 CLI 解析、校验、序列化的契约实体。zcode cli启动时会:

  1. 读取该文件;
  2. 用内置 JSON Schema Validator 校验其自身结构是否符合agent-skills契约元 Schema(即“契约的契约”);
  3. 提取contract.input生成运行时参数校验规则;
  4. 提取contract.errors构建标准化错误映射表。

注意:contract字段是强制存在的,且必须包含input/output/errors三要素。缺失任一,CLI 直接报错ERR_SKILL_CONTRACT_INCOMPLETE,拒绝执行。这是契约精神的第一道防线。

2.2 执行器注册层:每个技能背后是一个可插拔的“能力引擎”

CLI 不自己实现http-request或git-commit,它依赖一个执行器注册中心(Executor Registry)。这个中心在 CLI 初始化时加载所有内置执行器,并允许用户通过zcode cli plugin install注册第三方执行器。每个执行器必须实现标准接口:

// Executor.ts interface SkillExecutor<TInput, TOutput> { // 契约标识,必须与 YAML 中的 name/version 完全匹配 id: string; // 输入参数校验函数,接收 rawArgs,返回 validatedInput 或 Error validateInput: (rawArgs: Record<string, any>) => Result<TInput, ValidationError>; // 核心执行函数,接收校验后的输入,返回 Promise<Output> 或 Promise<Error> execute: (input: TInput) => Promise<Result<TOutput, SkillError>>; // (可选)清理函数,用于释放资源(如关闭数据库连接) cleanup?: () => Promise<void>; }

zcode cli内置的http-request执行器 ID 为http-request@1.0.0,它会:

  • 在validateInput阶段检查url是否为合法 URI,method是否在['GET','POST','PUT','DELETE']中;
  • 在execute阶段调用node-fetch,但绝不直接返回fetch()的原始 Response,而是将其转换为契约规定的output结构(含status,headers,body);
  • 若fetch()抛出网络错误,捕获后构造SkillError,其code字段严格映射为contract.errors中定义的NETWORK_ERROR(若存在)或UNKNOWN_ERROR(若未定义则报ERR_UNMAPPED_ERROR_CODE)。

关键点在于:执行器与契约是 1:1 绑定的,且版本号必须精确匹配。skills/http.yaml声明version: "1.2.0",但 CLI 只注册了http-request@1.0.0,则立即报错ERR_EXECUTOR_VERSION_MISMATCH。这杜绝了“契约写了,但执行器没跟上”的经典坑。

2.3 运行时绑定层:CLI 如何把“声明”变成“动作”

当用户执行zcode cli run --skill file-upload --config ./skills/file-upload.yaml --filePath /tmp/data.csv --targetUrl https://api.example.com/upload --authToken abc123,CLI 的绑定流程如下:

步骤动作关键细节
1. 加载契约解析./skills/file-upload.yaml提取contract.input生成校验规则树
2. 绑定参数将 CLI 参数--filePath,--targetUrl,--authToken映射为input对象自动类型转换:--authToken abc123→input.authToken = "abc123"(字符串);--sizeBytes 1024→input.sizeBytes = 1024(数字)
3. 输入校验调用执行器validateInput(rawArgs)若filePath不存在,返回ValidationError,CLI 输出ERROR: FILE_NOT_FOUND - The specified file does not exist or is inaccessible(复用契约中定义的 message)
4. 执行调度调用执行器execute(validatedInput)执行器内部处理,可能调用fs.readFile,fetch,child_process.exec等
5. 输出标准化将执行器返回的Result<TOutput, SkillError>按契约output/errors规范序列化成功时输出 JSON:{"uploadId":"a1b2c3...","sizeBytes":1024,"uploadedAt":"2024-06-15T10:30:00Z"};失败时输出:{"error":{"code":"HTTP_401","message":"Unauthorized"}}
6. 退出码控制根据结果设置进程退出码成功 →exit(0);契约定义的错误(如FILE_NOT_FOUND)→exit(1);未映射错误(如EXECUTOR_CRASH)→exit(2)

这个流程确保了:无论底层是 Node.js、Python subprocess 还是 Docker 容器,只要执行器遵守契约,CLI 就能无感调度。我曾用同一份skills/db-query.yaml契约,分别注册了 SQLite 执行器(本地开发)和 PostgreSQL 执行器(生产环境),CLI 命令完全不变,只需切换--executor postgresql@1.1.0参数。

实操心得:在团队内推行agent-skillsCLI 时,最有效的启动方式不是写文档,而是提供一个skill-init子命令:zcode cli skill-init --name llm-inference --template openai。它会自动生成带完整contract、README.md和空executor.ts的骨架目录。工程师只需填空实现execute(),契约校验、参数绑定、错误映射全部由 CLI 保障。这比手写 YAML 和 JS 快 5 倍,且零配置错误。

3. 前端 UI 工程中agent-skills如何驱动可预测、可调试的智能体交互

在前端工程里,agent-skills的价值常被低估——很多人以为它只服务于后端或 CLI。但恰恰相反,前端才是agent-skills契约最严苛的验证场:用户点击一次按钮,就要求输入必填、错误必显、状态必知、进度必控。如果没有契约,前端工程师只能靠“猜”后端 API 的字段名和错误码,导致if (res.error.code === 'INVALID_TOKEN')这类硬编码遍地开花。而基于agent-skills的 UI 工程,其核心是三个抽象层:技能注册中心(Skill Registry)、技能执行 Hook(useAgentSkill)、技能状态机(Skill State Machine)。

3.1 技能注册中心:让前端“知道”每个技能能做什么

前端不再通过fetch('/api/skills')动态获取技能列表,而是在构建时(build time)静态注册所有已知技能契约。这通常通过一个skills/index.ts文件实现:

// src/skills/index.ts import { registerSkill } from '@/lib/agent-skills'; import httpRequestContract from './http-request/contract.json'; import fileUploadContract from './file-upload/contract.json'; import llmInferenceContract from './llm-inference/contract.json'; // 静态注册,编译时即确定 registerSkill({ id: 'http-request@1.2.0', contract: httpRequestContract, // 执行器:前端专属,如封装 AbortController、自动重试 executor: createHttpRequestExecutor(), }); registerSkill({ id: 'file-upload@1.1.0', contract: fileUploadContract, executor: createFileUploadExecutor(), }); registerSkill({ id: 'llm-inference@2.0.0', contract: llmInferenceContract, executor: createLlmInferenceExecutor(), });

contract.json是从后端或 CLI 项目中直接复制的 JSON Schema 文件(如上节 YAML 编译而来)。registerSkill函数会:

  • 校验contract是否符合agent-skills元 Schema;
  • 将contract.input转换为 Zod Schema,用于后续表单生成;
  • 将contract.errors构建为ErrorMap,供 UI 显示精准提示;
  • 将contract.output生成 TypeScript 类型,供useAgentSkill返回值推导。

这样,前端在写代码时就能获得完整的类型安全:

// 使用时,input 和 output 类型自动推导 const { execute, status, result, error } = useAgentSkill<'file-upload@1.1.0'>(); // execute 的参数类型是 contract.input 的 Zod infer 类型 // result 的类型是 contract.output 的 TypeScript 类型 // error 的类型是 contract.errors 的联合类型

3.2useAgentSkillHook:契约驱动的 UI 交互原语

这个 Hook 是前端与agent-skills交互的核心。它不是简单的useMutation封装,而是一个状态完备、错误可控、可中断、可重试的技能执行控制器。其 API 设计直接受契约约束:

const { execute, // 触发执行,参数类型由契约 input 决定 status, // 'idle' | 'pending' | 'success' | 'error' | 'aborted' result, // 成功时的 output 数据,类型严格匹配契约 error, // 错误时的 SkillError,code 字段是契约中定义的枚举 abort, // 中断当前执行(如用户点击取消) retry, // 重试,自动使用上次参数 reset // 重置状态,清空 result/error } = useAgentSkill<'http-request@1.2.0'>(); // 表单提交 const handleSubmit = async (data: z.infer<typeof httpRequestContract.input>) => { try { await execute(data); // data 类型由契约 input 自动生成,无需手动定义 } catch (e) { // e 是 SkillError,code 必在契约 errors 列表中 if (e.code === 'HTTP_401') { showLoginModal(); // 精准响应 } else if (e.code === 'NETWORK_ERROR') { showToast('网络异常,请检查连接'); } } };

关键创新点在于:Hook 的status状态机完全由契约驱动,而非开发者手动管理。例如:

  • 当execute()被调用,status自动变为'pending';
  • 执行器返回成功Result<TOutput, ...>,status变为'success',result被赋值;
  • 执行器返回失败Result<..., SkillError>,status变为'error',error被赋值,且error.code必属于契约errors列表;
  • 用户调用abort(),status变为'aborted',且执行器的cleanup()方法被调用(如取消 fetch 请求)。

这消灭了传统useMutation中常见的状态竞态:比如isLoading和isSuccess同时为 true,或错误处理遗漏某个error.code导致白屏。

3.3 技能状态机:让每个技能调用都可追溯、可调试

前端 UI 最怕“点了没反应”或“报错看不懂”。agent-skills状态机通过三件事解决:

  1. 全生命周期日志:每个技能执行都会生成结构化日志,包含skillId,input,output,error,durationMs,timestamp;
  2. 错误分类路由:error.code直接映射到 UI 组件。例如HTTP_401触发登录弹窗,FILE_NOT_FOUND高亮文件输入框,LLM_TIMEOUT显示“模型思考中…”;
  3. 调试面板集成:在开发模式下,右下角常驻一个Agent Skill Debugger面板,可查看:
    • 当前所有已注册技能的契约摘要(输入字段、错误码列表);
    • 历史执行记录,支持按skillId、status、error.code过滤;
    • 点击某次执行,展开input(原始参数)、output(返回数据)、error(堆栈+上下文)、network(若涉及 HTTP,显示请求/响应头)。

实操心得:在重构一个老项目时,我们发现其apiService.uploadFile()方法有 7 种不同的错误处理分支,且部分错误码拼写不一致('file_not_found'vs'FILE_NOT_FOUND')。迁移到agent-skills后,我们首先用skill-validateCLI 工具扫描所有契约文件,找出errors中缺失的code,然后统一修正。前端只需改一行useAgentSkill<'file-upload@1.1.0'>(),所有错误提示、重试逻辑、取消按钮自动生效。上线后,用户投诉的“上传失败不知道原因”问题下降 83%。

4. Test-Driven Development 如何用agent-skills契约实现 100% 覆盖的技能单元测试

TDD 在智能体开发中最容易沦为形式主义:写一堆it('should call fetch', () => {...}),却无法保证fetch返回的数据结构符合契约,也无法覆盖所有错误分支。agent-skills的 TDD 实践彻底扭转这一局面——测试目标不是“代码是否运行”,而是“契约是否被 100% 履行”。其核心是三类测试:契约合规性测试(Contract Compliance Test)、执行器行为测试(Executor Behavior Test)、端到端集成测试(E2E Integration Test)。每类测试都直接消费agent-skills契约文件。

4.1 契约合规性测试:确保契约本身无歧义、可验证

这是 TDD 的第一道闸门,运行在 CI 的最早阶段。它不测试代码,只测试 YAML/JSON 契约文件是否符合agent-skills元规范。工具如skill-validator(CLI 内置)会执行:

  1. Schema 校验:用 JSON Schema 验证契约是否满足agent-skills@1.0.0元 Schema;
  2. 字段完整性检查:input/output/errors是否均存在,errors中每个code是否唯一;
  3. 类型一致性检查:input.properties.xxx.type是否与output.properties.xxx.type逻辑兼容(如input.timeoutMs是integer,则output中不应出现timeoutMs字段);
  4. 错误码覆盖检查:扫描所有执行器源码,确认每个throw new SkillError('XXX')的XXX都在契约errors列表中。

一个典型的合规性测试失败案例:

# skills/llm-inference.yaml errors: - code: "MODEL_NOT_FOUND" message: "Requested model is not available" - code: "RATE_LIMIT_EXCEEDED" # 拼写错误!正确应为 "RATE_LIMIT_EXCEEDED" message: "You have exceeded the quota"

skill-validator会报错:

ERROR: ERR_CONTRACT_INVALID_ERROR_CODE File: skills/llm-inference.yaml Line: 12 Detail: Error code 'RATE_LIMIT_EXCEEDED' is misspelled. Valid codes are: MODEL_NOT_FOUND, RATE_LIMIT_EXCEEDED.

这迫使团队在写契约时就对齐术语,避免后期“契约写了,代码用错”的混乱。

4.2 执行器行为测试:用契约生成测试用例,覆盖所有路径

这是 TDD 的主体。我们不用手写it('should handle 401 error'),而是用契约自动生成测试矩阵。以http-request执行器为例,其测试文件http-request.test.ts结构如下:

// http-request.test.ts import { generateTestCases } from '@/test/skill-test-generator'; import { httpExecutor } from '@/executors/http-request'; import contract from '@/skills/http-request/contract.json'; // 自动生成所有输入校验测试用例 describe('Input Validation', () => { const validationCases = generateTestCases(contract, 'input'); validationCases.forEach(({ name, input, expectedError }) => { it(name, async () => { const result = await httpExecutor.validateInput(input); expect(result.isErr()).toBe(expectedError !== undefined); if (expectedError) { expect(result.error.code).toBe(expectedError.code); } }); }); }); // 自动生成所有错误分支测试用例(基于 contract.errors) describe('Error Handling', () => { contract.errors.forEach((errDef) => { it(`should throw ${errDef.code}`, async () => { // 模拟触发该错误的场景:mock fetch 返回对应 status jest.mock('node-fetch', () => jest.fn().mockResolvedValue({ status: errDef.code === 'HTTP_401' ? 401 : 500, headers: new Headers(), text: () => Promise.resolve(''), })); const result = await httpExecutor.execute({ method: 'GET', url: 'https://api.example.com/test', headers: {}, body: '', }); expect(result.isErr()).toBe(true); expect(result.error.code).toBe(errDef.code); }); }); }); // 手动编写核心业务逻辑测试(如重试、超时) describe('Business Logic', () => { it('should retry on network error up to 3 times', async () => { const mockFetch = jest.fn() .mockRejectedValueOnce(new Error('Network Error')) .mockRejectedValueOnce(new Error('Network Error')) .mockResolvedValue({ status: 200, headers: new Headers(), text: () => Promise.resolve('OK') }); jest.mock('node-fetch', () => mockFetch); await httpExecutor.execute({ method: 'GET', url: 'https://api.example.com/test' }); expect(mockFetch).toHaveBeenCalledTimes(3); }); });

generateTestCases(contract, 'input')函数会:

  • 遍历contract.input.properties,为每个字段生成:必填缺失、类型错误、格式错误、范围越界等用例;
  • 为required字段生成“缺失该字段”的用例;
  • 为pattern字段生成“不匹配正则”的用例。

这确保了:只要契约input定义了minLength: 32,测试就必然覆盖authToken: "ab"的场景;只要errors列出了HTTP_401,测试就必然验证执行器能否正确抛出该错误码。我们团队的实践是:每个新技能 PR,CI 必须通过skill-validator+jest --coverage(覆盖率阈值设为 100% forexecutors/*),否则禁止合并。

4.3 端到端集成测试:用真实 CLI 和契约文件验证全链路

这是 TDD 的最后一环,模拟真实用户场景。我们不 mock 任何东西,而是直接调用 CLI 二进制,用真实的契约文件和参数,验证输出是否符合契约output。脚本e2e.test.sh示例:

#!/bin/bash # e2e.test.sh # 测试 file-upload 技能 echo "Testing file-upload skill..." # 创建测试文件 echo "test content" > /tmp/test-upload.txt # 执行 CLI 命令 OUTPUT=$(zcode cli run \ --skill file-upload \ --config ./skills/file-upload.yaml \ --filePath /tmp/test-upload.txt \ --targetUrl "https://httpbin.org/post" \ --authToken "test-token" \ 2>/dev/null) # 解析 JSON 输出 UPLOAD_ID=$(echo $OUTPUT | jq -r '.uploadId') SIZE_BYTES=$(echo $OUTPUT | jq -r '.sizeBytes') UPLOADED_AT=$(echo $OUTPUT | jq -r '.uploadedAt') # 验证是否符合契约 output if [ "$UPLOAD_ID" != "null" ] && [ "$SIZE_BYTES" -eq 13 ] && [[ "$UPLOADED_AT" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]; then echo "✅ file-upload E2E test passed" else echo "❌ file-upload E2E test failed" exit 1 fi

这个测试的价值在于:它验证了从 CLI 参数解析、契约加载、执行器调度、到最终输出的全链路保真度。曾经我们发现一个 bug:CLI 在 Windows 上解析--filePath C:\temp\file.txt时,反斜杠被转义为C:\\temp\\file.txt,导致fs.stat()找不到文件。这个 bug 在单元测试中无法复现(因为单元测试直接传入对象),但在 E2E 测试中立刻暴露。修复后,E2E 测试成为回归防护网。

实操心得:我们给每个技能目录标配三个文件:contract.yaml(契约)、executor.ts(执行器)、e2e.test.sh(端到端测试)。新成员入职第一天任务就是:看懂contract.yaml,跑通e2e.test.sh,然后修改executor.ts让测试通过。这比看文档快 10 倍,且保证每个人对“这个技能到底能做什么”有肌肉记忆。

5. 从api error: 400到api error: 429:agent-skills如何系统性治理 API 调用风险

网络热词中高频出现的api error: 400,api error: 429,api error: 400 this model's maximum context length is 1048576 tokens,表面是 API 服务端的问题,实则是客户端缺乏契约约束导致的系统性风险失控。agent-skills不是万能胶,但它提供了一套可落地的风险治理框架:将 API 错误从“不可预测的异常”转化为“可声明、可拦截、可降级”的契约内事件。其核心策略是三层防御:输入预检(Pre-flight Validation)、错误语义化(Error Semantic Mapping)、弹性降级(Graceful Degradation)。

5.1 输入预检:在请求发出前拦截 80% 的 400 错误

api error: 400的根源,90% 是客户端发送了服务端无法解析的数据。agent-skills的input契约在此处发挥最大价值——它让预检成为强制环节。

以llm-inference技能为例,其契约contract.yaml明确规定:

input: type: "object" required: ["model", "messages", "maxTokens"] properties: model: type: "string" enum: ["deepseek-flash", "deepseek-v4", "deepseek-v4-pro"] # 服务端支持的模型列表 messages: type: "array" minItems: 1 items: type: "object" required: ["role", "content"] properties: role: type: "string" enum: ["user", "assistant", "system"] content: type: "string" maxLength: 1048576 # 模型最大上下文长度 maxTokens: type: "integer" minimum: 1 maximum: 4096

当useAgentSkill<'llm-inference@2.0.0'>()被调用时:

  • 前端 Hook 在execute()前,用 Zod Schema 校验input:若model: "gpt-4",则立即报错ERR_INPUT_ENUM_MISMATCH,提示“不支持的模型:gpt-4,仅支持 deepseek-flash, deepseek-v4, deepseek-v4-pro”;
  • CLI 工具在解析--model gpt-4时,同样在校验阶段失败,输出ERROR: model must be one of ["deepseek-flash", "deepseek-v4", "deepseek-v4-pro"];
  • 单元测试中,generateTestCases自动生成model: "gpt-4"的用例,确保执行器不会收到非法值。

这直接将api error: 400 the supported api model names are deepseek-flash, deepseek-v4这类错误拦截在客户端,服务端永远收不到非法model参数。同理,messages[0].content超过maxLength: 1048576,会在前端就截断或报错,而非发给服务端再被拒。

5.2 错误语义化:将 HTTP 状态码翻译为契约内错误码

服务端返回429 Too Many Requests,客户端不应只显示“请求太频繁”,而应根据场景提供可操作的反馈。agent-skills通过errors契约建立 HTTP 状态码到业务错误码的映射:

errors: - code: "RATE_LIMIT_EXCEEDED" message: "您已超出每小时请求配额,请稍后再试" httpStatus: 429 retryAfter: "X-RateLimit-Reset" # 从响应头提取重试时间 - code: "MODEL_CONTEXT_TOO_LONG" message: "输入内容过长,请精简后重试" httpStatus: 400 details: "The supported api model names are deepseek-flash, deepseek-v4, but you provided ..."

执行器在处理响应时,必须按此映射转换错误:

// llm-inference 执行器片段 async execute(input: LlmInput): Promise<Result<LlmOutput, SkillError>> { try { const res = await fetch(apiUrl, { method: 'POST', body: JSON.stringify(input) }); if (!res.ok) { const errorBody = await res.json(); // 根据 res.status 和 errorBody 内容,查找匹配的契约 errors if (res.status === 429) { return err({ code: 'RATE_LIMIT_EXCEEDED', message: '您已超出每小时请求配额,请稍后再试', // 从 X-RateLimit-Reset 头提取时间,供 UI 显示倒计时 retryAfter: parseInt(res.headers.get('X-RateLimit-Reset') || '0'), }); } if (res.status === 400 && errorBody.error?.type === 'invalid_request_error') { return err({ code: 'MODEL_CONTEXT_TOO_LONG', message: '输入内容过长,请精简后重试', details: errorBody.error.message, }); } } // ... 处理成功响应 } catch (e) { // 网络错误统一映射 return err({ code: 'NETWORK_ERROR', message: '网络连接失败' }); } }

这样,前端useAgentSkill收到的error.code永远是契约中定义的RATE_LIMIT_EXCEEDED,而非原始429。UI 可据此:

  • 显示定制化提示:“您已用完今日配额,剩余 2

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询