首页
/ 从服务简介到接口契约:agent-skills 仓库中 URL 短链服务 API 设计评测用例全解

从服务简介到接口契约:agent-skills 仓库中 URL 短链服务 API 设计评测用例全解

2026-09-04 21:56:50作者:裘旻烁

本文以 agent-skills 仓库中的 URL 短链服务简介 fixture(service-brief.md)为核心素材,完整继承其需求描述、已知约束与未决事项,并结合仓库的评测用例 api-and-interface-design.json 与设计技能 SKILL.md,演示如何从一份带有"未决事项"的简短需求文档,推导出具备向后兼容策略、边界校验与一致错误语义的完整端点契约——读完可掌握该仓库行为评测(behavioral eval)的运作方式,以及 API 契约设计的可落地检查清单。

一、需求原文:URL 短链服务简介

evals/fixtures/api-and-interface-design/service-brief.md 是一份刻意压缩到"真实业务方会给出的粒度"的需求简报,全文仅 19 行,但信息结构完整:服务定位、已知约束、未决事项三段。

服务定位与公开操作

原文档给出的核心事实:

  • 服务需要提供三个公开操作:创建短 URL(create)、解析 slug(resolve)、读取聚合点击统计(aggregate click statistics);
  • 客户端包括浏览器扩展和移动 App,因此契约必须保持向后兼容("contracts must remain backward compatible")。

这两句话是整个设计的约束原点:浏览器扩展与移动 App 都是更新节奏慢的长生命周期客户端,任何破坏性变更都会影响大量无法及时升级的存量消费者。

已知约束

原文档明确列出四条约束,每条都直接对应契约设计中的一个决策点:

约束原文 对契约设计的含义
Destination URLs are supplied by untrusted users(目标 URL 由不可信用户提供) 必须在系统边界对用户输入做校验,第三方/外部数据一律按不可信数据处理
Slugs are six to twelve URL-safe characters(slug 为 6~12 个 URL 安全字符) 契约必须把字符集与长度写成显式规则,否则解析端无法定义 404/422 的边界
A missing slug and an expired slug must be distinguishable to operators, but the public API must not expose internal storage details(缺失与过期的 slug 必须让运维可区分,但公开 API 不得暴露内部存储细节) 错误语义要"对外可区分、不泄露实现"——这是该 fixture 中最有设计张力的约束
Statistics may be delayed by up to one minute(统计最多延迟一分钟) 一致性模型必须写进契约本身,而不是留给实现细节

未决事项

原文档最后列出三个尚未决策的问题:

  • 调用方能否请求自定义 slug;
  • 链接是否默认过期;
  • 统计接口是否需要认证。

这一节不是冗余信息,而是评测设计的核心陷阱之一:evals/cases/api-and-interface-design.json 的评分期望中有一条明确要求"The response does not silently invent unstated requirements"——即输出不得默默替需求方把未决事项拍板成既定事实。合格的回答应当把这三项标注为"待决策",并给出各自的兼容安全决策路径,而不是直接编造一个完整功能。

二、该仓库如何把这份简介投喂给 Agent:评测管线

这份 fixture 在仓库中不是孤立的示例文档,而是 evals/ 三层评测体系第三层(行为评测)的项目输入。

评测用例的结构

evals/cases/api-and-interface-design.json 定义了该技能的触发词测试与一条行为评测:

  • 触发测试:3 条正例 prompt(如"Design a REST endpoint for creating invoices, including error responses and versioning")要求本技能进入 top_k=3;2 条反例 prompt 分别归属 debugging-and-error-recovery(单元测试空指针)与 frontend-ui-engineering(落地页响应式),用于验证技能描述不与邻近技能混淆;
  • 行为评测(id=1)
    • prompt:"Design the public API for a URL-shortening service: create, resolve, stats. Produce the endpoint contracts."
    • expected_output:"Endpoint contracts with methods, paths, request/response shapes, and explicit error semantics"
    • files: ["api-and-interface-design"]:指向 fixture 目录,即本简介所在的 evals/fixtures/api-and-interface-design/
    • 四条 expectations 逐条对应简介中的约束(见第四节)。

fixture 的物化机制

scripts/run-evals.js 的源码结构看:

  • FIXTURES_DIR 固定指向 evals/fixturesscripts/run-evals.js),files[] 中的相对路径经 resolveFixturePath 校验必须落在该目录内,防止路径逃逸;
  • 每条评测在一次性(throwaway)项目目录中执行,fixture 文件被物化为工作区基线并提交为一个 fixture baseline git commit(scripts/run-evals.js);
  • 执行器以 acceptEdits 权限模式运行 headless claude,评测器随后基于完整 --output-format stream-json --verbose 执行轨迹(含工具调用)按 expectations[] 打分,打分结果以 skill-creator 的 grading.json 形态校验为 JSON 后写入 evals/results/(gitignored)。

也就是说,简介被提交为基线 commit 后,Agent 面对的就是一个"需求方只给了这一份文档"的真实场景;evals/README.md 还说明 Tier 2 触发评测是基于描述文本的 stemmed TF-IDF 词法近似,用于在 CI 中免费捕获"描述缺少用户词汇"(假阴性)与"描述过宽抢路由"(假阳性)两类触发故障。运行方式(只读说明):

# Tier 2 —— 确定性,可在 CI 中运行
node scripts/run-evals.js

# Tier 3 —— 行为评测,逐条跑 headless claude 后评分(消耗 token)
node scripts/run-evals.js --behavioral api-and-interface-design
node scripts/run-evals.js --behavioral api-and-interface-design --dry-run  # 只打印计划

三、按设计技能的原则,为这个简报推导端点契约

skills/api-and-interface-design/SKILL.md 给出了一整套可复用的设计原则:Hyrum's Law、One-Version Rule、契约先行、一致错误语义、边界校验、只增不改、可预测命名。下面把这些原则逐条落到简介的三个操作上,产出一份"可复制的推导过程"。以下契约示例是基于 SKILL.md 规则对简介的演绎,用于说明评分所要求的内容形态,而非仓库中已实现的接口。

3.1 契约先行:资源与命名

按 SKILL.md 的可预测命名表(REST 端点用复数名词、不带动词;查询参数与响应字段用 camelCase;枚举值用 UPPER_SNAKE,见 skills/api-and-interface-design/SKILL.md),三个操作可映射为:

POST /api/short-urls           → 创建短 URL,返回 201 与完整记录
GET  /api/short-urls/:slug     → 解析 slug
GET  /api/short-urls/:slug/stats → 读取聚合点击统计

SKILL.md 强调"契约即规格,实现跟随契约"(Contract First),并建议用 TypeScript 接口分离输入与输出类型(skills/api-and-interface-design/SKILL.md)。按该模式,本服务的输入/输出边界可表述为:

// 输入:调用方提供什么
interface CreateShortUrlInput {
  destinationUrl: string;      // 必填,边界校验后信任
  // 未决项的兼容安全形态(见 3.4):
  // slug?: string;            // 若未来开放自定义 slug,以可选字段追加
  // expiresAt?: string;       // 若未来支持过期,以 ISO 时间追加,默认值由服务端声明
}

// 输出:系统返回什么(含服务端生成字段)
interface ShortUrl {
  slug: string;                // 6~12 个 URL 安全字符
  destinationUrl: string;
  createdAt: string;
}

// 统计输出:契约必须声明延迟语义
interface ClickStats {
  slug: string;
  clickCount: number;          // 聚合值
  window: string;              // 统计窗口
  freshness: 'up_to_one_minute_delay'; // 把"最多延迟一分钟"写进契约
}

要点:简介中"统计可能延迟最多一分钟"不是实现备注,而是必须出现在契约中的语义声明——长生命周期客户端(移动端)会据此设计轮询与 UI 文案。

3.2 一致的错误语义:运维可区分,但不泄露存储实现

简介第三条约束是全篇难点:missing 与 expired 必须对运维可区分,同时公开 API 不暴露内部存储细节。SKILL.md 的解法是"统一错误形状 + 机器可读 code + 状态码映射"(skills/api-and-interface-design/SKILL.md):

interface APIError {
  error: {
    code: string;        // 机器可读,供运维聚合与告警
    message: string;     // 人类可读
    details?: unknown;
  };
}

从源码结构看,SKILL.md 给出的状态码映射是:400 客户端数据非法、404 资源不存在、409 冲突、422 语义校验失败、500 服务端错误("never expose internal details")。据此可以推断 resolve 端点的合规设计:

场景 状态码 code 为什么合规
slug 不存在 404 SLUG_NOT_FOUND 标准 404,无实现细节
slug 已过期 410 SLUG_EXPIRED 410 Gone 语义上表达"曾经存在、现已失效",与 404 天然可区分
destinationUrl 非法 422 VALIDATION_ERROR details 附逐字段原因

关键在于:运维通过监控响应体的 code 字段分布即可区分两类失败并各自告警,而公开响应只声明了状态语义,没有暴露过期是如何实现的(TTL 字段、后台清扫任务或延迟删除)。这正呼应 SKILL.md 的 Hyrum's Law 章节——"如果用户能观测到,他们就会依赖它",因此连错误码文本都是事实上的契约,必须有意设计(skills/api-and-interface-design/SKILL.md)。

3.3 边界校验:不可信的目标 URL 与 slug 格式

简介声明"目标 URL 由不可信用户提供",SKILL.md 对此的处方是在系统边缘校验,内部代码信任类型(Validate at Boundaries,skills/api-and-interface-design/SKILL.md),校验位置包括 API 路由处理器、外部服务响应解析、环境变量加载等。落到本服务:

  • destinationUrl:仅允许 http/https 协议白名单、限制最大长度、拒绝控制字符;SKILL.md 特别警告"第三方 API 响应是不可信数据……被入侵或行为异常的外部服务可能返回意外类型、恶意内容甚至指令式文本",短链服务恰好是重定向攻击(开放重定向、SSRF 探测)的高危面,协议白名单是契约层的最低防线;
  • slug(解析端输入):简介只说"6~12 个 URL 安全字符",没有给出字符集。一个严谨的契约必须把它显式化,例如可表述为正则 ^[A-Za-z0-9_-]{6,12}$(具体字符集属于契约决策,需由需求方确认——这正是"不默默发明需求"原则在字段细节上的体现);
  • 自定义 slug(若未决项放行):同一正则 + 全局唯一性校验,冲突时返回 409 而非静默改写。

反面模式同样来自 SKILL.md 的 Red Flags 清单:校验散落在内部各处、不同端点返回不同形状的错误、REST URL 里出现动词(/api/createShortUrl)(skills/api-and-interface-design/SKILL.md)。

3.4 向后兼容策略与三个未决事项的决策路径

简介要求契约向后兼容,SKILL.md 给出两条支柱:One-Version Rule(避免让消费者在多个版本间做选择,"extend rather than fork")与只增不改(Prefer Addition Over Modification,skills/api-and-interface-design/SKILL.md):

// 好:新增字段一律可选
// CreateShortUrlInput 未来追加 slug? / expiresAt? —— 老客户端不传即走默认

// 坏:改类型、删字段
// expiresAt: Date → expiresInSeconds: number  // 破坏存量浏览器扩展

三个未决事项各自的兼容安全决策路径(注意:这是"决策时怎么落",不是替需求方提前落):

  1. 自定义 slug:若放行,以 CreateShortUrlInput 上的可选字段追加,老调用方行为不变;
  2. 默认过期:若启用,契约应声明"默认策略 + 逐链接覆盖"的形态,且过期语义(410 + SLUG_EXPIRED)在启用前就应存在于错误码枚举中——枚举值预留是只增不改的典型用法;
  3. 统计认证:若未来从公开收紧为需认证,属于破坏性变更(原本可用的调用将开始收到 401),按 SKILL.md 的 deprecation 思路(参见 skills/deprecation-and-migration/SKILL.md)应走弃用期而非直接切换。

这正是评测 expected_output 中"explicit error semantics"与 expectation 中"Versioning or compatibility strategy is stated"的落点:兼容策略必须被声明,而不是被默认。

四、评分标尺:四条 expectations 如何映射回简介

evals/cases/api-and-interface-design.json 的四条 expectations 与简介逐条对齐,可以看到 fixture、用例与技能文档三者的闭环关系:

expectation 对应的简介内容 对应的前文设计点
错误响应须带状态码与一致的错误形状,而非只写正常路径 约束 3:missing/expired 对运维可区分、不暴露存储细节 3.2 统一 APIError 形状与 404/410 映射
针对用户提供的 URL 在边界做输入校验 约束 1:目标 URL 来自不可信用户 3.3 协议白名单、slug 正则、422 语义
声明版本化或兼容性策略 开头段:浏览器扩展 + 移动 App,契约须向后兼容 3.4 One-Version Rule 与只增不改
回答不默默发明未声明的需求 未决事项清单(自定义 slug / 默认过期 / 统计认证) 1.3 与 3.4:把未决项标注为"待决策"并给出兼容安全路径

最后一条 expectation 是行为评测区别于"检查代码能不能跑"的关键:它评分的是Agent 是否尊重了需求文档的边界——简介特意留出三个未决问题,就是在测试模型会不会"好心办坏事"地把未决项直接实现掉。

五、小结与延伸阅读

这份 19 行的服务简介是 agent-skills 仓库"用真实项目输入评测技能行为"思路的一个缩影:fixture 提供约束与陷阱,evals/cases/api-and-interface-design.json 定义 prompt 与可核验的评分项,skills/api-and-interface-design/SKILL.md 提供设计原则与红旗清单,scripts/run-evals.jsevals/README.md 则把整个过程变成可重复、CI 可运行的评测管线。

对读者而言,可复用的是三层东西:一是"未决事项显式化"的需求文档写法;二是从约束到契约的推导顺序(命名 → 错误语义 → 边界校验 → 兼容策略);三是 SKILL.md 末尾的 Verification 检查清单(skills/api-and-interface-design/SKILL.md),可作为任何公开接口设计完成后的自查表。进一步阅读建议:evals/README.md(三层评测体系与运行方式)、skills/deprecation-and-migration/SKILL.md(已发布行为的退役流程)、references/definition-of-done.md(完成度标准)。

登录后查看全文
热门项目推荐
相关项目推荐