API 可用率测试怎么做:自己测 AI API 的指标、脚本与统计口径

博客 / 性能

API 可用率测试怎么做:自己测 AI API 的指标、脚本与统计口径

做 API 可用率测试,最简单可靠的办法是:每隔固定时间向接口发一个最小请求,记下状态码和耗时,按“成功 /(成功 + 服务端失败)”算比例。难点不在脚本,而在口径:哪些错误算服务的、哪些算你自己的,样本够不够,测的是入口还是真实推理。本文给出一个能直接跑的脚本,再用 cdnping 和 AGI Plan 两份公开数据说明,同一个服务换一种口径,结论可能差很多。

合成探测和真实请求统计,测的是同一件事吗?

不是。两种常见做法:

  • 合成探测:由监控方定时发请求,例如每 5 分钟访问一次 API 入口。优点是间隔均匀,夜里没人用也有数据;缺点是请求很轻,往往只验证到网关能响应,未必走到模型推理。
  • 真实请求统计:服务方把用户真实请求的成功、失败计数汇总出来。它反映的是用户实际碰到的情况,但请求量随作息起伏,凌晨样本很少,一小时只有两三个请求时,比例没有统计意义。

cdnping 的 API 状态页 属于前一种:从 Cloudflare 边缘每 5 分钟探测各官方 API 入口,具体做法写在 数据方法。AGI Plan 公开的逐小时数据属于后一种。AGI Plan 是 cdnping 的商业合作方,正好可以把两种口径放在一起看。

两种口径放在一起,差别有多大?

先看合成探测。cdnping 在 2026 年 10 月 17 日 18:05(北京时间)的快照,探测点为丹佛(DEN)。cdnping 当天才开始采集,字段名虽然叫 24 小时,实际只覆盖 16:40 到 18:05 约 85 分钟、242 条探测记录:

服务入口中位耗时 p50p95窗口内可用率
api.anthropic.com73 ms84 ms100%
api.openai.com96 ms194 ms100%
api.deepseek.com353 ms414 ms100%
api.moonshot.cn375 ms1125 ms约 94.4%
agiplan.dev883 ms883 ms100%

15 个入口里只有 Kimi 开放平台在这段时间出现过失败,其余都是 100%。

再看真实请求。AGI Plan 公开的逐小时计数,取 10 月 15 日 19 时到 10 月 17 日 18 时(北京时间)共 48 个小时,合计成功 13,963 次、平台失败 2,177 次,按公式算出来约 86.5%。失败高度集中:10 月 16 日 14 时和 15 时这两个小时分别有 722 次和 877 次失败,单小时成功率约 48% 和 34%;凌晨 4 点到 6 点每小时只有 2 个请求,成功率 100% 也说明不了什么。

同一快照里 AGI Plan 按能力组给出的 30 天可用率也不一样:快速组约 81.7%(44,729 次请求),前沿模型组约 94.4%,均衡组约 95.0%,国产模型组约 98.0%。

把两边对上:合成探测窗口内 agiplan.dev 入口 100% 可用,而同一时段(10 月 17 日 16 时到 18 时)的真实请求每小时都有 1 到 7 次失败。这不矛盾,入口能响应只说明网关在线;上游模型返回 5xx 时,入口探测看不到。反过来,只看真实请求数据,又会在请求稀少的时段失去判断依据。所以自己测的时候,两种都要有,至少要清楚手上的数字属于哪一种。

哪些错误应该算进不可用?

可用率的分母怎么定,决定了结果。下面是一个可以直接沿用的分类表:

结果例子是否计入可用率说明
2xx200计为成功流式接口要确认读到了结束事件
5xx500、502、503、504计为失败服务方或网关的问题
超时、连接失败DNS 失败、TLS 握手失败、读超时单独记录,再判断可能是服务,也可能是你的网络
429限流单独统计和你的额度、并发有关,不宜直接算故障
其他 4xx400、401、403、404不计入参数、密钥或权限问题,是调用方的错

AGI Plan 公开数据采用的就是“只有 5xx 算平台失败”的口径。自己测的时候也建议把 4xx 排除在分母之外,但一定要把它们记下来:一个突然大量出现的 401 可能是密钥被轮换,403 可能和地区或账号状态有关,这些要按服务条款处理,不是可用率问题。

超时最难归因。比较实用的办法是同时探测一个对照目标,例如 cdnping 自己的入口。对照目标也超时,多半是本机网络;对照正常、只有 AI 接口超时,再算到服务头上。网络层的排查顺序可以参考 Claude Code 连接超时和报错怎么排查。

动手:一个最小可运行的探测脚本

下面的 Python 脚本只用标准库,兼容 OpenAI 格式的接口(官方 API、云厂商托管或聚合网关都可以)。每次运行发两个请求:一个 GET /models 检查入口,一个 max_tokens=1 的对话请求检查推理链路,结果追加到 CSV。

#!/usr/bin/env python3
# probe.py: 每次运行探测一次,结果追加到 probe.csv
import csv, json, os, time, urllib.request, urllib.error

BASE  = os.environ["API_BASE"].rstrip("/")   # 例如 https://api.example.com/v1
KEY   = os.environ["API_KEY"]
MODEL = os.environ["API_MODEL"]              # 你实际在用的模型 id
OUT   = os.environ.get("PROBE_OUT", "probe.csv")

def call(kind, method, path, body=None):
    req = urllib.request.Request(BASE + path, method=method,
        data=json.dumps(body).encode() if body else None,
        headers={"Authorization": f"Bearer {KEY}",
                 "Content-Type": "application/json"})
    t0 = time.time(); status, err = 0, ""
    try:
        with urllib.request.urlopen(req, timeout=30) as r:
            r.read(); status = r.status
    except urllib.error.HTTPError as e:
        status = e.code
    except Exception as e:                    # 超时、DNS、TLS 等
        err = type(e).__name__
    ms = int((time.time() - t0) * 1000)
    return [int(t0), kind, status, ms, err]

rows = [
    call("entry", "GET", "/models"),
    call("infer", "POST", "/chat/completions",
         {"model": MODEL, "max_tokens": 1,
          "messages": [{"role": "user", "content": "ping"}]}),
]
with open(OUT, "a", newline="") as f:
    csv.writer(f).writerows(rows)

用 cron 每 5 分钟跑一次:

# crontab -e
*/5 * * * * API_BASE=https://api.example.com/v1 API_KEY=sk-xxx API_MODEL=your-model PROBE_OUT=$HOME/probe.csv /usr/bin/python3 $HOME/probe.py

推理请求会消耗少量 token。每 5 分钟一次、每次输出 1 个 token,一天 288 次,成本可以忽略,但仍建议用单独的密钥,方便在账单里区分。不同接口地址的写法可以对照 Claude Code 自定义 API 与 Codex、OpenCode 接口地址配置。

汇总时按上面的口径计算,4xx 不进分母,429 和超时单独列出:

awk -F, '$2=="infer"{
  if($3>=200 && $3<300) ok++;
  else if($3>=500) fail++;
  else if($3==429) rl++;
  else if($3==0) to++;
  else c4++
} END{
  if (ok+fail==0) { print "没有可统计的样本"; exit }
  printf "成功 %d  5xx %d  可用率 %.3f%%\n", ok, fail, 100*ok/(ok+fail);
  printf "429 %d  超时/连接失败 %d  其他4xx %d\n", rl, to, c4
}' ~/probe.csv

如果超时最后确认是服务方的问题,把它并入 fail 再算一遍,两个数字都留着。

要测多久,样本才够?

可以用算术估一下。每 5 分钟一次,一天 288 个样本,一次失败就让当天可用率掉 0.35 个百分点。想区分 99.9% 和 99.5%,样本至少要上千个:1,000 个样本里,前者约对应 1 次失败,后者约 5 次,再少就分不开。按每 5 分钟一次算,要三到四天起步;按月看需要约 8,640 个样本,这时一次失败约占 0.01%。

几条经验:

  1. 只跑一天,只能看出有没有大问题,别用来比较两个服务谁更稳。
  2. 按小时出图,比只看一个总百分比有用。上面 AGI Plan 的 48 小时数据,总数 86.5% 掩盖了一件事:失败主要集中在两个小时里。
  3. 请求很少的小时不要单独下结论,可以设一个门槛,例如当小时样本少于 10 个就标灰。
  4. 探测点要和你的用户处在相近的网络环境。从境外机房测出来的结果,不代表国内办公网的情况,延迟差异的成因可以看 AI API 延迟拆解。

怎么拿公开数据和自己的数据对照?

自测最大的问题是不知道失败是自己的网络还是服务方。可以拿几份公开数据交叉看:

curl -s 'https://agiplan.dev/api/status/requests?month=2026-10' -o agiplan-2026-10.csv
curl -s 'https://agiplan.dev/api/status/requests?month=2026-10&format=json'

cdnping 的 /status/agiplan 页面会镜像展示这份数据。拿到后和自己的 probe.csv 按小时对齐:两边同时出现失败,基本可以归到服务方;只有你这边失败,先查本地网络和出口。

如果你测的是中转站或聚合网关,可用率只是其中一项,计费方式、数据政策和模型一致性同样要查,完整清单见 AI API 中转站怎么选:可用率、计费、数据政策 8 项检查。经过 OpenRouter 这类路由时,同一模型在不同供应商上的可用率也会不同,参考 同一模型换个 OpenRouter 供应商,可用率能差多少。

上线前的检查表

  • 探测分两类:入口(/models)和推理(最小对话请求)
  • 2xx 计成功,5xx 计失败,429、超时、其他 4xx 分开记录
  • 同时探测一个对照目标,用来区分本地网络问题
  • 探测点和真实用户处在相近的网络环境
  • 至少积累 3 到 4 天数据再做比较,报告时写明窗口和样本数
  • 按小时汇总,样本过少的小时不单独下结论
  • 使用独立密钥,遵守服务条款,不用探测去试探限流或地区限制

引用任何可用率数字时,都把来源、窗口和口径写在旁边。本文的 cdnping 数据只有约 85 分钟,AGI Plan 的逐小时数据是 48 小时,它们说明的是方法上的差别,不是长期结论。

相关文章

继续阅读