每一个 AI API 都按 token 计费。你信任网关返回的 usage 对象 —— prompt_tokens、 completion_tokens —— 认为那就是真实的数量。但这个数字偏偏是 转售商能完全掌控的唯一一项,也是最容易让它悄悄向你超收的地方。它有个 客气的名字,叫做 token 灌水(token inflation):网关上报的 token 数比你 实际发出或收到的还多,于是你要付出诚实成本的 5 倍、10 倍,有时甚至 25 倍 —— 不管用的是不是真模型。
这正是 Brievio 当初为了对抗它而打造的失效模式,所以值得把它讲清楚:灌水 是怎么运作的、一段你能对任何网关(包括我们)跑一遍的 20 行测试,以及该 怎么读懂结果。
token 灌水到底是什么
诚实的 token 数,是供应商自己会为你的消息 —— 你的系统提示、你的 用户内容、模型的回复 —— 算出来的数量。值得信任的网关会原封不动地把它 透传过去。会灌水的网关则往里头塞东西。同一个请求,账单却天差地别:
# 诚实的 token 数 —— 数量与你发出的文本相符,再加上一点点
# 对话模板的额外开销(role 标记、格式化 token):
{"prompt_tokens": 24, "completion_tokens": 2, "total_tokens": 26}
# 灌水 —— 你只发了约 20 个 token 的文本,却被收了 1,840 个:
{"prompt_tokens": 1840, "completion_tokens": 2, "total_tokens": 1842}
# ^ 一段你从没写过、约 1,800 个 token 的系统提示被注入进了这次请求,
# 再回头算到你头上。哪怕只是一个字的提问。每一次调用都这样。最常见的套路是隐藏的注入式系统提示:转售商在每一次调用 的最前面,塞进它自己几百到几千个 token 的文本 ——「安全」前言、路由 包装、假人设。你从没写过、也看不见,却在每一个请求上都得替它买单。 按 Sonnet 的输入费率算,一段 1,800 个 token 的幽灵前缀,光是一个字的 提问就约值 $0.0055 的纯利润。再乘上每月一百万次调用。
这段 20 行测试
你不必相信任何人的说辞 —— 也包括我们的。发出一段你知道大小的提示,再把 网关向你收费的数量,跟你的文本在自己机器上实际分词出来的数量做对比:
# token_inflation_test.py
# 你的网关有没有如实上报 token 数?发出一段已知大小的提示,
# 再把网关上报的 prompt_tokens,跟你用本地分词器对你“实际
# 发出的那几条消息”算出的 token 数做对比。
import tiktoken
from openai import OpenAI
client = OpenAI(api_key="sk-brievio-...", base_url="https://api.brievio.com/v1")
messages = [
{"role": "system", "content": "You are a terse assistant."},
{"role": "user", "content": "Reply with the single word: ok."},
]
# 1. 网关实际向你收费的量:
resp = client.chat.completions.create(
model="claude-sonnet-4-6", messages=messages, max_tokens=5,
)
reported = resp.usage.prompt_tokens
# 2. 你的消息在本地实际分词后的量:
enc = tiktoken.get_encoding("cl100k_base") # 只是近似值 —— 详见下方说明
local = sum(len(enc.encode(m["content"])) for m in messages)
print(f"gateway reported prompt_tokens: {reported}")
print(f"local token count of your text: {local}")
print(f"ratio: {reported / local:.1f}x")
# 正常 → 比值约 1.1~1.6 倍(role 标记 + 对话模板的额外开销)
# 灌水 → 比值 2 倍 / 5 倍 / 25 倍(有一段隐藏提示正在塞满你的输入)怎么读:有一小段固定的额外开销是正常的 —— 对话格式 会为 role 标记和消息边界加上几个 token,所以在很短的提示上,比值落在 1.1~1.6 倍 左右没问题,而且提示越长就越会趋近 1.0 倍。但 2 倍、5 倍、25 倍 的比值不是四舍五入的误差 —— 那是掺料。
有一个诚实的提醒:tiktoken 的 cl100k_base 是 OpenAI 的分词器,而 Claude 或 Gemini 的 分词方式略有不同(通常在 10~20% 以内)。所以请把本地算出的数字 当成近似值,而不是逐 token 的精确审计。它永远解释不了 2 倍的 差距,更别提 25 倍 —— 想要精确数字,请改用供应商自家的分词器或 count-tokens 接口。这个测试是用来抓灌水的,不是用来在单个 token 上锱铢 必较的。
输出和缓存也要查
输入是常见的下手目标,但同样的掺料也可能藏在输出 token 和缓存里。再做 两个快速检查:
# 输出和缓存同样可能被灌水。再做两个 30 秒就能完成的检查:
#
# (a) 输出:要求只回一个 token,并设上限。
resp = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Reply with only: ok"}],
max_tokens=2,
)
print(resp.usage.completion_tokens) # 诚实:约 1~2。灌水:50、200、…
#
# (b) 缓存:在约 5 分钟内把同一段长前缀发两次。第二次调用
# 应该把大部分输入按更低的缓存读取费率计费。如果
# “cached”字段永远是 0,说明你连缓存命中都在付全价。
print(resp.usage.prompt_tokens_details.cached_tokens)如果你只要一个 token 却被收了五十个,或是同样内容的重复调用里,你的 cached_tokens 却一直卡在零,那这块计费表就是错的。
灌水都从哪来
- 注入式系统提示。加进每一个请求里的包装前言 —— 单一 最常见的来源。又大、又看不见、又照样计费。
- 重新包装的「模板代理」模型。你的提示在抵达模型 之前,会被塞进一个庞大的固定模板。那些模板 token 是真实的 token —— 对 模型、对你的账单都是 —— 但它们不是你的。
- 伪造的 usage 数字。最粗暴的版本:
usage对象干脆就跟现实对不上。上面那段测试会当场抓到 它。 - 幽灵式输出灌水。上报的
completion_tokens超过实际返回的字数。
这些都用不着假模型。一个网关可以提供真正的 Claude,却照样把计费表 灌水 —— 模型的真伪和账单的诚实,是两个各自独立的承诺,两者你都 该查。
诚实的基准线
Brievio 原封不动地把供应商自己的 token 数透传过去,不在你的请求里注入任何 东西,并在每一次调用上记录真实的输入与输出 token,以及精确的成本,这些 都能在你的 用量仪表板里看到。对 Brievio 跑一遍上面 那段测试,你应该会看到 reported ≈ local + 一点点额外开销 —— 这本来就该是每个地方都读得到的样子。我们的 定价把每个模型都对照它的官方参考费率列出, 让折扣可被审计,而 用量文档则明确写清楚我们会返回哪些 字段。
如果一个网关比官方定价低 80%,第一个该问的问题不是「模型是不是真的」 —— 而是「计费表怎么说」。把这 20 行跑一遍。它花不到一美分,却是你 对任何供应商所能做的、最便宜的尽职调查。