从服务简介到接口契约:agent-skills 仓库中 URL 短链服务 API 设计评测用例全解
本文以 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/fixtures(scripts/run-evals.js),files[]中的相对路径经resolveFixturePath校验必须落在该目录内,防止路径逃逸;- 每条评测在一次性(throwaway)项目目录中执行,fixture 文件被物化为工作区基线并提交为一个
fixture baselinegit 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 // 破坏存量浏览器扩展
三个未决事项各自的兼容安全决策路径(注意:这是"决策时怎么落",不是替需求方提前落):
- 自定义 slug:若放行,以
CreateShortUrlInput上的可选字段追加,老调用方行为不变; - 默认过期:若启用,契约应声明"默认策略 + 逐链接覆盖"的形态,且过期语义(410 +
SLUG_EXPIRED)在启用前就应存在于错误码枚举中——枚举值预留是只增不改的典型用法; - 统计认证:若未来从公开收紧为需认证,属于破坏性变更(原本可用的调用将开始收到 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.js 与 evals/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(完成度标准)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00