失败不计费:一句承诺背后的工程量
「失败不计费」写在 footer 里一行字。要让它在九家厂商、两套协议、同步与流式、同步与异步、文字与像素之间都成立 —— 这是几千行代码加一份公开的退款政策。
每个想显得公平的聚合器都会在 pricing 页放一句「失败不计费」。这句话是免费的,实现起来不是。等你真坐下来在九家上游厂商、两套协议、同步和流式、四种输出模态之间执行这条规则,edge case 出现的速度比策略能吸收的速度还快。这篇文章是那一句话底下的工程地图 —— 什么算失败、什么不算、以及哪些地方必须我们自己把线画清楚,因为宇宙不会替你画。
无聊的 80%:4xx 和 5xx
大部分失败很简单。上游返回 401、429、500、502、503、504。我们把状态码透传、记录请求、credits 账本写零。这些请求的账务视角是一致的:流量出了边缘、回来了、没有模型产出可用 token、不计费。我们失败量的 80% 都长这个样,永远不需要人介入。
中间棘手部分:200 OK 但 body 里是错误
我们三家上游厂商会返回 HTTP 200 + 顶层字段是 `error` 的 JSON body。从 HTTP 看请求成功了,从用户看根本没成功 —— 没有 completion token,只有一句道歉。我们的网关会解析这种响应并把它重写成对应的 4xx 或 5xx 再回给客户端,同时在账务上记为失败。如果只信 status code,那就是在悄悄地为产出为 0 的请求收钱。
最难的部分:流式中断
流式响应是每个聚合器计费承诺开始漏水的地方。1000 token 响应里跑了 300 token,上游发了一个 `"type": "error"` delta 然后关 SSE。客户端已经渲染了 300 个 token。这算成功吗?我们选了能正眼看人的最严策略:流式中途 abort,不管客户端渲染了多少 token,都不计费。用户拿到了一个不完整、可信度未知的部分答案。我们不为这种情况收钱。代价确实更高。但这是我们能对客户原话复述、不脸红的唯一一条策略。
可以诚实地按部分计费的地方:视频
视频生成打破了对称模式。视频任务是异步的、几秒到几分钟、走一个状态机。一旦 GPU 上开始 render,厂商就按生成的每一秒向我们收费 —— 我们没办法靠取消任务回收已经扣的钱。所以 rate card 里写得很明确:视频任务进入 running 之后再 cancel,按已经生成的秒数计费。这是唯一例外,写在所有客户都能查到的地方。
图像、embedding、rerank:原子的
这三个模态属于简单桶。请求同步、响应要么是文件 / 向量 / 分数,要么是非 2xx,没有「部分完成」这种中间态。2xx 且 payload 存在就计费;其他任何形态就计零。没有灰色地带,没有特殊账目条目。处理它们的代码里大半是 shape 校验,不是计费逻辑。
tool_use、thinking 和「保真税」
Anthropic 形态的响应里有 `tool_use` block 和 `thinking` block。这两类在上游都算 output token —— 也都可能合法地出现在一个客户认为「没用」的响应里(一次客户端联不上的 tool call;一次没产出最终文字的思考 pass)。这些我们不退。模型做了工作、上游为这份工作向我们收了费、「客户没用上」不是网关的失败。我们在 request inspector 里把它们清楚地列出来 —— 客户能看到自己为什么被收钱。透明跟可退是两回事。
幂等、retry 和「被收两次」的担忧
看 SLA 的客户在意的不是我们怎么数 token,而是他们的 retry 脚本会不会不小心被双重计费。所以每个 chat 请求都接受 `Idempotency-Key` header。24 小时窗口内同一个 key 重复发,返回缓存响应、第二次记零。真正会出现的 retry —— Cloudflare 层丢了一段 stream、客户端超时刚好在我们的 200 落地前 1ms 触发 —— 是「为什么我被收了两次」工单最常见的原因。Idempotency key 把这个循环关掉,客户根本不用想它。
策略从内部看长什么样
- 上游任何非 2xx → 计 0 credit,没有例外。
- 200 OK 但 body 顶层是 `error` → 重写状态码、计 0 credit。
- 流式中 `error` delta → 计 0 credit,哪怕 token 已经渲染了。
- 视频任务进 running 之后 cancel → 按生成秒数计费(rate card 明示)。
- 图像 / embedding / rerank:2xx 且有 payload 才计费;其他都免费。
- Idempotency-Key 在 24h 窗口去重 —— 第二次命中无论结果如何都免费。
- tool_use 和 thinking token 计费 —— 上游向我们收,我们向客户收;request inspector 显示明细。
在这一切之上还有一层退款
即便这样,客户偶尔还是会觉得某笔扣费不公平 —— 一个 2xx 但他们认为不可用的响应、一个 Cloudflare retry 了一次才完成的 stream。这种我们不争。30 天未用 USD credits 退款 + 给模糊情况自由裁量的 credit-back 通道,覆盖了 per-request 计费逻辑覆盖不了的部分。这是唯一可持续的形态:代码执行能被规则执行的事,人来处理真的执行不了的事。
“每条 pricing 承诺都是和未来签的合约。订一条的成本是一句话;守住它的成本是整个 codebase。”
失败不计费是我们为 ByteSpike 写下的第一条策略,也是被我们重写最多次的那一条。流式改过它;多模态改过它;第一个写 retry 脚本的客户改过它;第一个返回 200 OK 但 body 是 error 的厂商改过它。pricing 页上的那句话没动过。它下面的代码一直在动。我们预期会一直动下去。