Grok token 计量自己核一遍

2026-11-04 17:42 更新

月底账单比预估高出一截,多数人的第一反应是「调用量涨了」。于是去查请求数、查并发、查有没有死循环重试,折腾一圈发现调用量几乎没变。真正的原因可能只是一次请求多带了几十万 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 展示,仅作教学算例;实际计费以官方页与控制台当前显示为准。缓存输入、工具调用、图像与语音计价不在本节算例内。

七、动手试

  1. 把 B 拆成两次请求(例如 150,000 + 60,000 提示 token,输出各 2,000),用 Ledger 重算。拆分后一定更省吗?把「多一次往返、输出 token 翻倍、上下文重复」也写进你的判断。
  2. 给 account() 增加 cached_tokens 参数:从 prompt_tokens 中扣掉命中缓存的部分,按 cached_input 单价计。用一组数据验证缓存确实影响了总额。
  3. 在每条账目里补上 model 字段,并让 account() 按模型选择费率表。想一想:如果模型名写错,记账器应该报错还是按默认档算?为什么?
  4. 把 reconcile() 接到真实响应上:抓 10 条请求的 usage.cost_in_usd_ticks 与本地估算逐一对比,统计偏差分布。若偏差集中在某一类请求上(比如带工具的、流式的),说明你的口径缺了哪一块?
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号