夜雨聆风学习资料网

ARTICLE · 982987

AI 时代,API 文档的读者有一半是机器

AI 时代,API 文档的读者有一半是机器

你可能已经遇到过这样的场景。

你让 AI 编程助手干一件看起来很简单的事:"调一下我们公司的订单接口,把上周状态异常的订单拉出来,写个脚本。"

AI 信心满满地打开了你们内部的 API 文档,三秒钟后,给你生成了这样的代码:

// AI 生成的代码——看起来有模有样async function getAbnormalOrders() {  const res = await fetch("https://api.internal.com/order/list", {    method: "POST", // ❌ 实际是 GET,文档没写清,AI 猜的    headers: { Authorization: "Bearer " + TOKEN }, // ❌ 实际用 X-Api-Key 头    body: JSON.stringify({      status: "abnormal", // ❌ 实际枚举值是 "EXCEPTION"      page: 1, // ❌ 实际分页字段叫 pageNo,且从 0 开始      startTime: "2026-08-14", // ❌ 实际要 Unix 毫秒时间戳    }),  });  const data = await res.json();  return data.list.map((o) => o.orderNo); // ❌ 实际字段是 order_no}

八个假设,错了六个。你骂了一句"这 AI 真笨",然后自己翻文档、问后端,花了半小时把参数一个个对齐。

但先别急着骂 AI。你回头打开那份文档看一眼——大概率是写在内部 Wiki (比如飞书文档、语雀、Confluence)里的一页非正式文档:一张简单的参数表格,配上几句口头描述

查询订单列表地址:/order/list参数:  status    订单状态  page      页码  时间范围   起止时间返回:订单列表数组

问题来了:这份文档里的每一个空格,都是靠"人"来填满的。

人看到"订单状态",会去问后端:都有哪些值?人看到"页码",会试一下从 0 还是从 1 开始。人调通一次之后,这些约定就进了他的脑子,文档写不写无所谓了。

而 AI 不会问,也不敢试(试错要花你的钱和时间)。它面对这份文档,唯一的选择就是猜。

今天这篇文章,我想聊的观点是:

API 文档的读者变了。以前它是后端写给前端的"人际备忘录",模糊一点没关系,反正可以问;现在越来越多的调用方是 Agent、MCP 工具、AI 编程助手——它们直接读文档调接口,文档里没写出来的约定,对机器来说就等于不存在。在 AI 时代,"机器可读"不再是 API 设计的加分项,而是及格线。

1. 人和机器读文档,是两种完全不同的读法

先把这个差异说透,它是后面所有结论的地基。

人类工程师读文档,读的是线索。文档给个大概,剩下的靠三样东西补齐:

文档没写清的 -> 问后端("status 都有哪些值来着?")文档写错的   -> 试一下(发个请求看报错,再调整)约定俗成的   -> 靠经验("时间戳嘛,估计是毫秒")

机器读文档,读的是全部事实。它的世界里没有"问一下"这个选项,没有"约定俗成"这个储备。文档里有的,它会用;文档里没有的,它只能用训练数据里"最常见的写法"去补——而最常见的写法,恰恰最可能不是你们系统的写法。

所以那份模糊的 Wiki 表格,在人类世界里的代价是"偶尔问一嘴",在机器世界里的代价是"每次调用都是掷骰子"。

文档的模糊地带,人靠沟通补齐,机器靠幻觉补齐。 你觉得 AI 笨,其实是你的文档在逼它瞎编。

2. 第一层补法:契约结构化,让"猜"变成"查"

第一个要解决的是:把接口的形状从"自然语言描述"变成"结构化契约"。

业界现成的答案就是 OpenAPI(Swagger)。但注意,这里要的不是"用 Swagger UI 生成一个好看的文档页面",而是让 OpenAPI 定义成为接口的事实来源(source of truth)。

同样是订单接口,结构化的写法长这样:

# openapi.yaml(节选)——机器眼里的"全部事实"paths:  /order/list:    get: # 方法明确:GET,不用猜      summary: 查询订单列表      parameters:        - name: status          in: query          schema:            type: string            # 关键:枚举值穷举在这里,AI 永远不会猜出 "abnormal"            enum: [UNPAID, PAID, SHIPPED, COMPLETED, EXCEPTION, REFUNDING]          description: 订单状态筛选,不传表示全部        - name: pageNo          in: query          schema:            type: integer            minimum: 0            default: 0 # 关键:起始页写死,不用试        - name: startTime          in: query          schema:            type: integer            format: int64          description: 起始时间,Unix 毫秒时间戳 # 单位写明        - name: endTime          in: query          schema:            type: integer            format: int64          description: 结束时间,Unix 毫秒时间戳      responses:        "200":          description: 成功          content:            application/json:              schema:                type: object                properties:                  list:                    type: array                    items:                      type: object                      properties:                        order_no:                          type: string # 字段名精确到蛇形命名                        amount_cents:                          type: integer

这份定义往那一放,开头那段"八错其六"的代码就没有生存空间了:方法是 GET、参数是 pageNo 从 0 开始、状态枚举里没有 abnormal、时间要毫秒、字段是 order_no——每一个 AI 会猜错的地方,都被一个结构化的字段钉死了。

而且结构化契约的价值不止"给 AI 读"。它能直接变成工具:

  • 从 OpenAPI 自动生成 MCP Server,Agent 把接口当工具调用,连代码都不用生成

  • 自动生成客户端 SDK、前端请求函数的类型定义

  • 自动生成 mock 服务和契约测试

以前 OpenAPI 在很多团队里是"有空再补"的文档工程,因为对人来说,Wiki 表格凑合够用。是机器读者把它从"锦上添花"变成了"基础设施"——没有它,你的 API 在 AI 眼里就是一堆需要猜的谜语。

3. 第二层补法:错误信息,是写给调用方的"下一步指令"

再看一个更隐蔽的地方:出错的时候。

你们的接口出错时返回什么?大概率是这个:

// 典型的"给人看都费劲"的错误响应{  "code": 500,  "message": "系统异常"}

人类调用方收到这个,会去翻日志、找后端、问"我刚才传的参数哪里不对"。

机器收到这个,能干什么?什么都干不了。"系统异常"四个字没有携带任何可执行的信息,Agent 唯一的策略就是原样重试——然后把同样的错误再犯一遍。

对比一下机器可读的错误设计:

// HTTP 400 —— 状态码先分对错类别{  "error": {    "code": "INVALID_ORDER_STATUS", // 机器可分支处理的稳定标识    "message": "status 参数取值不合法", // 给人看的描述    "details": {      "received": "abnormal", // 你传了什么      "allowed": ["UNPAID", "PAID", "SHIPPED", "COMPLETED", "EXCEPTION", "REFUNDING"]      // ^^^ 关键:合法值直接给出来,调用方可以自我修正    },    "retryable": false // 明确告诉调用方:别重试,改了参数再来  }}

这份错误响应的本质变化是:它不再是一句"出事了",而是一份"怎么修"的说明书。

有了它,Agent 的纠错循环就能转起来:

async function callOrderApi(params) {  const res = await fetch(buildUrl(params));  const body = await res.json();  if (res.ok) return body;  const err = body.error;  // 机器可执行的纠错逻辑:  if (err.code === "INVALID_ORDER_STATUS") {    // 从错误详情里取出合法值,映射后自动重试    params.status = mapToAllowed(params.status, err.details.allowed);    return callOrderApi(params); // 修正后重发,一次成功  }  if (err.retryable) {    await sleep(1000);    return callOrderApi(params); // 服务端瞬时故障,允许重试  }  throw new Error(`${err.code}: ${err.message}`); // 真错误,抛给人}

对比一下两种世界里 Agent 的表现:

错误响应是 "系统异常":  调用 -> 失败 -> 原样重试 -> 失败 -> 放弃 -> 找人来修错误响应是结构化的:  调用 -> 失败 -> 读到 allowed 枚举 -> 自我修正 -> 成功

设计错误响应时,记住一个标准:假设调用方是一个看不到你日志、也联系不上你的工程师,你的错误信息够他把问题修好吗? 够,机器就能修;不够,人和机器都得卡在这。

4. 第三层补法:字段的语义,要长到名字和示例里

第三层最细碎,也最容易被忽略:单个字段的含义。

看几个真实项目里遍地都是的字段:

{  "amount": 1999,       // 1999 什么?元?角?分?  "created_at": 1755763200, // 秒还是毫秒?  "price": "19.99",     // 怎么又是字符串?  "expire": 86400       // 时长还是截止时间?单位是?}

人类工程师看到 amount: 1999,结合页面上显示 19.99 元,能推理出"哦,单位是分"。这个推理,人做一次就记住了。

但机器每次面对的都是"第一次"。而且一旦它猜错——把分当元——它生成的代码会安静地错误运行,把钱数放大一百倍,直到某天的对账单爆炸。

修法不需要什么高科技,就是把语义焊死在字段上:

{  "amount_cents": 1999, // 单位进名字,想误读都难  "created_at": "2026-08-21T08:00:00Z", // ISO 8601 字符串,没有歧义  "price_yuan": 19.99, // 或统一用 amount_cents 风格,二选一,全系统一致  "expire_seconds": 86400 // 时长还是时点、什么单位,名字里说清}

再加一道保险:给每个接口配真实的请求/响应示例。 在 OpenAPI 里就是 example 字段:

# openapi.yaml(节选)order_no:  type: string  example: "SO20260821000123" # 示例一眼暴露了编号格式amount_cents:  type: integer  example: 1999 # 配合名字,双重确认"单位是分"created_at:  type: string  format: date-time  example: "2026-08-21T08:00:00Z"

对人来说,示例是"帮助理解";对机器来说,示例就是规格的一部分——AI 生成调用代码时,会优先模仿示例的形状。你给它的每一个例子,都是在替它消除一次猜测。

5. 一个能立刻用的自查清单

聊了这么多,落成一份清单。下次评审 API 设计(或者补文档)时,逐条对照:

□ 有没有 OpenAPI / JSON Schema 定义,并且和真实接口保持一致?  ("有但过时"比"没有"更糟——机器会照着错的契约自信地犯错)□ 所有枚举字段,合法值有没有穷举出来?□ 错误响应有没有结构化的 code?合法值、是否可重试,机器能拿到吗?□ 金额、时间、比例这些字段,单位和格式是写在名字里,还是靠口头约定?□ 每个接口有没有至少一个真实的请求 + 响应示例?□ 鉴权方式(用什么头、什么格式、怎么获取)写了吗?□ 分页方案(字段名、起始值、每页上限)写了吗?

如果嫌清单长,它其实可以压缩成一条终极测试:

把你们 API 的文档,交给一个完全不了解你们业务的工程师,规定他不许问任何人。他能把接口调通吗?

人调不通的,机器一定调不通——因为机器就是一个"永远问不了人"的调用方。反过来,一份能让陌生人零沟通调通的文档,交到 AI 手里,表现也不会差。

还有一层账要算明白:把 API 做得机器可读,受益的从来不只是 AI。前端拿到自动生成的类型定义,联调扯皮少一半;测试拿到契约,直接做契约测试;新人入职,不用在群里翻三个月前的聊天记录找"那个接口到底怎么调"。机器可读是文档的最高标准——达到了它,给人读的版本自然合格。

写在最后

开头那个场景里,你骂 AI 笨,其实骂错了对象。

AI 的表现,只是把你的 API 文档的质量,放大了一倍给你看。以前文档写得糙,代价由下一个接手的同事默默承担了——他问几嘴、试几次、踩几个坑,就把缺口补上了,你还以为文档没问题。现在调用方换成了不会问、不敢试的机器,文档欠的账,一次性全部暴露了。

所以,API 设计在 AI 时代多了一条新的及格线:

你的 API 好不好用,不再只取决于人类工程师的评价,还取决于机器能不能零沟通地调通它。文档不再是写给人看的说明书,而是写给机器读的契约——而能被机器读懂的系统,才接得上 Agent 时代的生态。

RECOMMEND

推荐阅读

相关学习资料