把 API 改动描述成 Agent 可以检查的契约

使用 AI 辅助编写与复核。

本文目录

API 改动任务说明应展示既有调用方收到什么、新调用方可以请求什么、错误如何处理,以及哪些兼容性说法仍需检查。“增加一个摘要字段”并不够:字段不存在、字段值为 null,以及必填字符串,是三份不同的契约。

本指南设计的是位于 https://api.example.invalid 的完全虚构只读卡片目录。这个主机刻意不用于真实服务;没有发送请求,也没有实现服务。契约、消息和数据均为原创教学示例。

从调用方与不变的响应开始

既有调用方发送以下请求:

GET /v1/cards?limit=2 HTTP/1.1
Host: api.example.invalid
Accept: application/json

示意响应如下:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "items": [
    {"id": "c1", "title": "Review draft"},
    {"id": "c2", "title": "Plan export"}
  ],
  "next_cursor": null
}

拟议功能让新调用方用 include_summary=true 主动请求 summary 属性。省略选项或明确传入 false 时,保持既有结构。这是一项选定的迁移策略,不是“可选字段永远安全”的通用规则。

契约要点 之前 之后
方法与路径 GET /v1/cards 不变
limit 可选整数,默认 20,范围 1–50 不变
不带摘要选项 每项有 id 和 title 结构相同;summary 不存在
include_summary=false 不是既有选项 结构与省略时相同
include_summary=true 不是既有选项 每个返回项还包含 summary,其值为字符串或 null
结果顺序与所选 ID 既有选择行为 对相同数据和 limit 保持不变
访问模型 虚构的公开目录 不变;不修改授权

这个公开数据案例并不是要求从其他服务移除认证。调整任务说明时,应使用对应服务真实的访问与归属规则。

展示新请求及响应

GET /v1/cards?limit=2&include_summary=true HTTP/1.1
Host: api.example.invalid
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "items": [
    {"id": "c1", "title": "Review draft", "summary": "Review the proposed wording."},
    {"id": "c2", "title": "Plan export", "summary": null}
  ],
  "next_cursor": null
}

这里的 null 表示卡片没有可用摘要,不表示“客户端没有请求摘要”。另一种情况由 summary 不存在来表达。新客户端主动启用摘要后,必须同时处理字符串和明确的 null。

空结果应返回 items: [] 和 next_cursor: null。这时,“每个返回项”的规则适用于零项,不要求插入占位卡片。

在边界定义错误

本例中的 include_summary 只接受小写查询字面值 true 与 false。limit 是 1 到 50 的规范十进制整数:2 有效,而 02、2.0 与空值无效。重复任一已识别参数都是错误,不是要求采用第一个或最后一个值。

请求情况 选定响应
有效 limit,摘要选项省略或为 false 200,既有结构
有效 limit,摘要选项为 true 200,每项包含可为 null 的 summary
limit 为 0、51、02、two 或空值 400,INVALID_QUERY
摘要选项为 yes、1、False 或空值 400,INVALID_QUERY
重复已识别参数 400,INVALID_QUERY

示意错误响应体如下:

{
  "code": "INVALID_QUERY",
  "message": "include_summary must be true or false."
}

消息应解释具体无效输入;code 是稳定的机器可读字段。这些查询规则不会把无关服务错误重新定义为 400。既有路由、未知参数和服务器失败行为不属于这项小改动,必须按真实服务契约保留。

用 OpenAPI 描述结构,再检查条件行为

下面的教学文档使用固定版本 OpenAPI 3.1.1,不是声称它是最新版本。OpenAPI 3.1.1 规范定义了这里使用的文档、参数与 Schema Object 结构。在该版本中,类型数组可以表示字符串或 null 字段。

{
  "openapi": "3.1.1",
  "info": {
    "title": "Fictional Card Catalog",
    "version": "1.1.0-example"
  },
  "servers": [{"url": "https://api.example.invalid"}],
  "paths": {
    "/v1/cards": {
      "get": {
        "operationId": "listCards",
        "summary": "List fictional cards, optionally including summaries",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Canonical decimal 1-50 only; repeated values are invalid.",
            "schema": {"type": "integer", "minimum": 1, "maximum": 50, "default": 20}
          },
          {
            "name": "include_summary",
            "in": "query",
            "required": false,
            "description": "Lowercase true/false only; repeated values are invalid. True requires summary on every returned item; otherwise summary is absent.",
            "schema": {"type": "boolean", "default": false}
          }
        ],
        "responses": {
          "200": {
            "description": "Cards; item shape also follows the include_summary behavior contract.",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/CardList"}
              }
            }
          },
          "400": {
            "description": "Invalid recognized query parameter",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/QueryError"}
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Card": {
        "type": "object",
        "required": ["id", "title"],
        "additionalProperties": false,
        "properties": {
          "id": {"type": "string"},
          "title": {"type": "string"},
          "summary": {"type": ["string", "null"]}
        }
      },
      "CardList": {
        "type": "object",
        "required": ["items", "next_cursor"],
        "additionalProperties": false,
        "properties": {
          "items": {"type": "array", "items": {"$ref": "#/components/schemas/Card"}},
          "next_cursor": {"type": ["string", "null"]}
        }
      },
      "QueryError": {
        "type": "object",
        "required": ["code", "message"],
        "additionalProperties": false,
        "properties": {
          "code": {"type": "string", "const": "INVALID_QUERY"},
          "message": {"type": "string"}
        }
      }
    }
  }
}

这个 schema 允许 summary 存在或不存在,不会依据查询参数强制要求其存在。同样,整数参数 schema 本身也不能确定服务器如何处理数值的文字形式或重复查询值。这些要求需要在 HTTP 边界做行为检查。

配套文档已有的检查仅是作为 JSON 解析;没有运行完整 OpenAPI 校验器、生成客户端或服务。应把它们视为另外的实现检查,而不是从 JSON 语法有效推断出来。

按调用方行为检查兼容性

调用方 在这份拟议契约下的效果 迁移前检查
省略选项的既有客户端 收到旧结构 对固定数据比较改动前后的响应字段
拒绝未知字段的既有严格客户端 默认仍收到旧结构 核实其请求不会意外开始启用新选项
请求摘要的新客户端 每项收到字符串或 null 检查两种取值以及空列表
未知调用方 兼容性未知 识别其请求与解码假设

一种可能的顺序,是先部署服务端支持,再让客户端发送新参数。应先检查既有服务端如何处理未知参数,因为旧服务端可能拒绝新请求。同样,回退部署可能留下仍在调用不受支持选项的新客户端。主动启用式设计除了 schema,也需要迁移方案。

一份完整的实现请求

实现虚构 GET /v1/cards 的主动启用摘要契约。
使用附带的 OpenAPI 3.1.1 文档和两组请求/响应示例。
example.invalid 主机仅供说明,不要联系它。

保留既有方法、路径、数据选择、排序、limit 默认值 20、
limit 范围 1-50、响应封套与访问行为。

新行为:
- include_summary 省略或为 false:每个条目中 summary 不存在。
- include_summary=true:每个条目都有 summary,值为字符串或 null。
- Null 表示不可用,不要替换成空字符串。
- 空结果仍为 items: [],并且 next_cursor: null。
- include_summary 只接受小写 true/false。
- 接受规范十进制 limit 1-50;拒绝重复的已识别参数。
- 无效的已识别输入返回 400,带 code INVALID_QUERY 和有用消息。
  保留无关错误行为。

在 HTTP 边界检查选项省略、false、true,null 摘要、空列表、
limit 边界、无效文字形式以及重复参数。
根据固定的改动前样例,比较 ID、标题、顺序与封套。
单独检查存在性规则,因为响应 schema 无法强制执行这些规则。

交付实现、实际检查结果、调用方影响说明,以及服务端/客户端部署顺序提案。
不要修改认证、写入接口、线上部署或未知调用方的契约。
不要把单纯 JSON 解析描述成 OpenAPI 或兼容性验证。

给评审者可观察的改动前后行为,才能判断 Agent 是否交付了所要求的 API 改动,而不把看似很小的字段增加当成普遍兼容保证。