Grok token 计量自己核一遍
月底账单比预估高出一截,多数人的第一反应是「调用量涨了」。于是去查请求数、查并发、查有没有死循环重试,折腾一圈发现调用量几乎没变。真正的原因可能只是一次请求多带了几十万 token 的上下文——跨过阈值的那一步,把整条请求的所有 token 都抬到了另一档单价。
与其等账单出来倒查,不如在每次请求结束时自己记一笔。这一节写一个不联网的本地记账器,把「分档」这件抽象的事变成可复算的数字。
一、先认清两条容易混在一起的规则
按 2026-11-04 的官方定价页,文本模型的计价有两档,分界看的是提示 token 是否达到 20 万:
- 未达阈值:
grok-4.7为每百万 token 输入 2.00 美元、缓存输入 0.50 美元、输出 6.00 美元; - 达到阈值:输入 4.00 美元、缓存输入 1.00 美元、输出 12.00 美元。
官方模型页在表格下写得很明确:提示达到阈值的请求,整个请求的所有 token 按更高的费率计费。关键词是「整个请求」,不是「超出部分」。
这两条规则经常被混为一谈。很多人以为只有超过 20 万的那部分 token 涨价,于是把长文档硬塞进一次请求,觉得「反正只多几万」。实际上账单会按整包重算。本节的核心训练就是把这个差别算出来。
价格会变,模型也会增删。下面的单价常量是按核验日期抄录的教学基线,不是长期承诺;每次复核都要回到官方页重新确认。
二、本地记账器:把分档算成数字
这段脚本只做纯计算,不发起任何请求,也不读取真实响应。用量数据由调用方传进来,方便离线测试与批量回放历史记录。
"""本地 token 记账:按官方分档估算单次请求成本,全程不联网。
单价单位:USD / 每 1M token,核验日期 2026-11-04。
提示 token 达到阈值时,整个请求的所有 token 按高档费率计算。
"""
from dataclasses import dataclass, field
THRESHOLD = 200_000
RATE_NORMAL = {"input": 2.00, "cached_input": 0.50, "output": 6.00}
RATE_LONG = {"input": 4.00, "cached_input": 1.00, "output": 12.00}
TICKS_PER_USD = 10_000_000_000 # 官方口径:1 美元 = 10^10 ticks
@dataclass
class Ledger:
rows: list = field(default_factory=list)
def account(self, name: str, prompt_tokens: int, output_tokens: int) -> dict:
"""记一笔账;缓存命中的部分暂不计入,见正文口径说明。"""
if prompt_tokens < 0 or output_tokens < 0:
raise ValueError("token 数不能为负,用量字段缺失时应先报警")
# 关键判断:阈值看的是提示 token,且一旦触发,整包按高档算
long_tier = prompt_tokens >= THRESHOLD
rate = RATE_LONG if long_tier else RATE_NORMAL
usd = (prompt_tokens * rate["input"] + output_tokens * rate["output"]) / 1_000_000
row = {
"name": name,
"prompt": prompt_tokens,
"output": output_tokens,
"tier": "long" if long_tier else "normal",
"usd": round(usd, 6),
}
self.rows.append(row)
return row
def report(self) -> str:
"""输出可直接贴进对账表的 Markdown 表格。"""
lines = [
"| 请求 | 提示 token | 输出 token | 档位 | 估算 USD |",
"| --- | --- | --- | --- | --- |",
]
for row in self.rows:
lines.append(
"| {name} | {prompt:,} | {output:,} | {tier} | {usd:.3f} |".format(**row)
)
return chr(10).join(lines)
def total(self) -> float:
return round(sum(row["usd"] for row in self.rows), 6)
def billed_usd(cost_in_usd_ticks: int) -> float:
"""把官方响应里的 cost_in_usd_ticks 换算成美元。"""
if cost_in_usd_ticks < 0:
raise ValueError("cost_in_usd_ticks 不能为负")
return round(cost_in_usd_ticks / TICKS_PER_USD, 6)
def reconcile(name: str, estimate: float, cost_in_usd_ticks: int) -> dict:
"""本地估算与平台真值对账;偏差超过 5% 说明口径理解有误,要回头查。"""
actual = billed_usd(cost_in_usd_ticks)
gap = round(actual - estimate, 6)
pct = round(gap / actual * 100, 1) if actual else 0.0
return {
"name": name,
"est": estimate,
"actual": actual,
"gap": gap,
"pct": pct,
"flag": "查口径" if abs(pct) > 5 else "一致",
}
if __name__ == "__main__":
book = Ledger()
book.account("A 未跨阈值", prompt_tokens=180_000, output_tokens=2_000)
book.account("B 跨阈值", prompt_tokens=210_000, output_tokens=2_000)
print(book.report())
print("合计 USD:", book.total())
# 拿平台返回的 ticks 与本地估算对账(数值为演示用假数据)
checks = [
reconcile(book.rows[0]["name"], book.rows[0]["usd"], 3_720_000_000),
reconcile(book.rows[1]["name"], book.rows[1]["usd"], 9_600_000_000),
]
print()
print("| 请求 | 本地估算 | 平台真值 | 差额(偏差) | 结论 |")
print("| --- | --- | --- | --- | --- |")
for c in checks:
print(
f"| {c['name']} | {c['est']:.3f} | {c['actual']:.3f} "
f"| {c['gap']:+.3f}({c['pct']:+.1f}%) | {c['flag']} |"
)
把单价集中成两个常量,是为了让复核只改一处。若官方页调整了价格,改完常量后重跑历史记录,就能立刻看出「账单变化有多少来自调价、多少来自用量」。
三、计量字段与两档计价口径
| 项 | 含义 | 容易算错的地方 |
|---|---|---|
prompt_tokens |
本次请求带进去的提示 token 数 | 只看「新增」部分,忘了系统提示与检索片段 |
output_tokens |
本次生成的 token 数 | 流式输出只统计了最后一段增量 |
THRESHOLD |
分界值 20 万 | 误以为只对超出部分加价 |
cached_input |
命中缓存的输入单价 | 与普通输入混算,重复计费 |
cost_in_usd_ticks |
平台返回的本次请求实际费用(1 美元 = 10^10 ticks) | 当成会话累计值;流式没开 include_usage 时根本取不到 |
这几点要分清:
- 算例假设没有缓存命中。若部分输入命中了缓存,应把那部分从普通输入里拆出来,按
cached_input单价计,不能整包都按input算。缓存前缀怎么组织是 5.2 的内容,本节不把口径混进来。 - 用量字段缺失要报警,不要当 0。
account()对负数直接抛错,是因为「字段取不到」和「真的用了 0 个 token」是两件完全不同的事;静默按 0 记账会让对账永远对不上。 - 平台其实每一笔都给了真值。每个响应的
usage里有cost_in_usd_ticks,已扣掉所有折扣(含缓存减免)、也已涵盖服务端工具调用;流式、图像、视频同样带这个字段。换句话说,请求返回的那一刻,这次花了多少钱就已经在响应里,不必等月账单。 - 那为什么还要自己算。三个理由:事前(请求还没发出去,只有估算能拦住「要不要把 21 万 token 一次发完」)、校验(估算与真值偏差大,说明你的计费口径理解错了)、聚合(ticks 是逐请求的,会话级与批次级仍要你自己累加)。
- 流式有个开关。OpenAI SDK 与 REST 要显式传
stream_options: {"include_usage": true},否则只有最后那个choices为空的块带用量,中间增量块没有;xAI SDK 则每个块携带累计值。漏了这个开关,整段流式的用量与费用会凭空消失。
四、预期输出与异常分支
运行 __main__,输出应接近:
| 请求 | 提示 token | 输出 token | 档位 | 估算 USD |
| --- | --- | --- | --- | --- |
| A 未跨阈值 | 180,000 | 2,000 | normal | 0.372 |
| B 跨阈值 | 210,000 | 2,000 | long | 0.864 |
合计 USD: 1.236
| 请求 | 本地估算 | 平台真值 | 差额(偏差) | 结论 |
| --- | --- | --- | --- | --- |
| A 未跨阈值 | 0.372 | 0.372 | +0.000(+0.0%) | 一致 |
| B 跨阈值 | 0.864 | 0.960 | +0.096(+10.0%) | 查口径 |
B 的提示 token 只比 A 多 16.7%,费用却是 A 的约 2.32 倍。这个不成比例的跳变,就是「整个请求按高档计费」在数字上的样子。看到它,就该回头问一句:这 21 万 token 真的有必要一次性发过去吗?
第二张表是这节的另一半重点:B 的本地估算比平台真值低了 10%,说明估算口径漏了东西。按金额方向反推,能排掉一类猜测——不是缓存。缓存命中只会让实际更便宜,不会更贵;实际偏高通常指向三种情况:
- 响应里还有你没计入的输出,比如推理模型的
reasoning_tokens; - 这次请求带了服务端工具,工具调用单独计费;
- 单价常量过期,官方已经调价(核验日期就是为这一刻准备的)。
所以对账的价值不在「让两个数相等」,而在让偏差有解释。偏差能被归因,估算器才值得继续用;偏差归不了因,就该换成平台真值做账。
常见失败分支与处理:
| 现象 | 原因 | 处理 |
|---|---|---|
| 记账恒为 0 | 用量字段没取到,被当 0 处理 | 让解析层抛错,别给默认值 |
| 本地与账单差一截 | 漏记工具调用、图像或语音费用 | 分账本记录,别混进 token 估算 |
| 改了模型但费用没变 | 单价常量没跟着模型走 | 把模型名写进每条账目,按模型取费率 |
| 历史记录重算后对不上 | 官方调价但未更新常量 | 常量标注核验日期,调价时单独重跑 |
五、判据卡
| 判断项 | 该这么做 | 不该这么做 |
|---|---|---|
| 提示接近 20 万 | 先估算「整包按高档」的成本,再决定发不发 | 觉得只多几万 token 无所谓 |
| 用量字段缺失 | 报警并中止记账 | 静默按 0 写入账本 |
| 单价维护 | 集中成常量并标注核验日期 | 把价格散落在各段业务代码里 |
| 对账口径 | 逐请求拿 cost_in_usd_ticks 校准估算 |
只比月账单总额,偏差无法归因 |
| 复核节奏 | 官方页变更或每 3 个月重算历史 | 抄一次价格用一年 |
把这张卡贴在成本评审旁边。它不要求你预测账单,只要求你在「跨阈值」和「字段缺失」这两个节点上不要静默放过。
六、来源与核验
- 官方定价页:
https://docs.x.ai/developers/pricing(核验日期 2026-11-04),两档单价与计价单位来自该页。 - 官方模型页:
https://docs.x.ai/developers/models(核验日期 2026-11-04),阈值规则「整个请求所有 token 按高价计费」来自该页表格下的说明;同页还列出不同模型的上下文窗口与分档。 - 官方成本追踪页:
https://docs.x.ai/developers/cost-tracking(核验日期 2026-11-04)。cost_in_usd_ticks字段、1 美元 = 10^10 ticks、已含折扣与工具费用、流式需stream_options.include_usage、ticks 为逐请求不累计,均以该页为准(F54–F56)。 - 上文价格以美元计,按每百万 token 展示,仅作教学算例;实际计费以官方页与控制台当前显示为准。缓存输入、工具调用、图像与语音计价不在本节算例内。
七、动手试
- 把 B 拆成两次请求(例如 150,000 + 60,000 提示 token,输出各 2,000),用
Ledger重算。拆分后一定更省吗?把「多一次往返、输出 token 翻倍、上下文重复」也写进你的判断。 - 给
account()增加cached_tokens参数:从prompt_tokens中扣掉命中缓存的部分,按cached_input单价计。用一组数据验证缓存确实影响了总额。 - 在每条账目里补上
model字段,并让account()按模型选择费率表。想一想:如果模型名写错,记账器应该报错还是按默认档算?为什么? - 把
reconcile()接到真实响应上:抓 10 条请求的usage.cost_in_usd_ticks与本地估算逐一对比,统计偏差分布。若偏差集中在某一类请求上(比如带工具的、流式的),说明你的口径缺了哪一块?

TRAE-AI编程


更多建议: