API 計費不對怎麼查:token 計數口徑、重複扣費與逐條核對方法
覺得 API 計費不對,常見的情況不是平臺算錯了,而是你估算用的口徑和平臺計費的口徑不一樣:輸入、輸出、快取、推理 token 單價不同,不同模型的 tokenizer 切分方式不同,客戶端還可能在你不知情的情況下重試。要核對賬單,最可靠的辦法是把每次請求響應裡的 usage 欄位記下來,按同一套單價重算一遍,再和平臺的明細對比。
為什麼我自己估的 token 和賬單對不上?
最常見的原因是把所有 token 當成一個價。實際上一次請求至少有四類 token,各自計價:
| 類別 | 是什麼 | 常見誤區 |
|---|---|---|
| 輸入 token | system prompt、歷史訊息、工具定義、你本次發的內容 | 只算了使用者這句話,忘了歷史對話和工具 schema 每輪都會重發 |
| 輸出 token | 模型生成的可見內容 | 用字數粗估,中英文、程式碼的 token 密度差很多 |
| 推理 token | 推理類模型在給出答案前的內部思考 | 響應裡看不到正文,但按輸出計費 |
| 快取 token | 命中提示快取的輸入部分,或寫入快取的部分 | 讀快取通常比普通輸入便宜,寫快取可能更貴,具體倍率以官方價格頁為準 |
輸出比輸入貴得多,這一點會把誤差放大。按 cdnping 在 2026-10-17 從 OpenRouter 公開模型目錄抓取的 16 個主流模型價格,輸出單價普遍是輸入單價的 3 到 5 倍,其中一個模型約為 39 倍。所以同樣“多算了 1 萬 token”,落在輸入還是輸出上,金額可能差一個數量級。這些價格會變,想跟蹤變化可以看 /models 的調價記錄和 /changes 對官方價格頁的逐行監控,做法在大模型 API 價格變了怎麼第一時間知道裡有詳細說明。
Agent 類工具為什麼特別費 token?
Claude Code、Codex 這類工具每一輪都會帶上完整的上下文:系統指令、工具定義、已讀過的檔案、之前每一步的工具呼叫結果。假設一次會話跑了 30 輪,每輪上下文平均 4 萬 token,僅輸入就是 120 萬 token,而你看到的對話可能只有幾屏。這部分如果沒有命中快取,就全部按普通輸入計費。
不同 tokenizer 會讓同一段文字算出不同的 token 數嗎?
會。每家模型用自己的分詞器,同一段中文在不同模型上的 token 數可能相差明顯,程式碼和 JSON 也一樣。用某一家的 tokenizer 去估另一家的賬單,本身就會有偏差。
更穩妥的做法:
- OpenAI 系模型可以用
tiktoken本地計數,但要選對編碼,並且記住訊息格式本身也會佔少量 token。 - Anthropic 提供
/v1/messages/count_tokens介面,按實際請求體返回輸入 token 數,比本地估算準。 - 其他模型以響應裡的
usage為準,不要拿通用分詞器去推算。
如果你用的是中轉站或聚合閘道器,還要確認它轉發到的是不是你以為的那個模型。模型換了,tokenizer 和單價都會跟著變,怎麼確認 API 給你的是哪個模型裡有可以動手做的一致性檢查。
流式輸出和重試會不會重複扣費?
賬單“憑空多出來”的部分,常見的有三種來源:
- 客戶端自動重試:很多 SDK 預設對超時、429、5xx 自動重試若干次。如果第一次請求其實已經在服務端生成了一部分甚至全部內容,只是響應沒回到你這裡,這次呼叫可能已經計費,重試又算一次。
- 流式中途斷開:流式請求在中途斷開時,已經生成的 token 通常仍會計費。客戶端隨後重新發起整輪請求,輸入 token 也會再算一遍。
- 上層工具的重放:Agent 工具在出錯後經常把整輪上下文重發,單次失敗的代價是“整段上下文 + 已生成的輸出”。
失敗和重試往往集中出現在服務不穩定的時段。以 cdnping 映象的 AGI Plan 公開請求資料為例(AGI Plan 是 cdnping 的商業合作方;視窗為北京時間 2026-10-15 19:05 至 2026-10-17 18:05,逐小時計數),2026-10-16 14:05 和 15:05 兩個小時分別有 722 次和 877 次失敗,都超過了同小時的成功次數(664 和 455),其餘大部分小時的失敗數在兩位數以內。對賬時如果某幾個小時的消耗明顯偏高,先去看那段時間服務端有沒有故障,再看客戶端日誌裡同一個任務是不是被髮了多次。可用率怎麼從這種計數里算出來,可以參考自己測 AI API 的可用率。
怎麼用 usage 欄位逐條核對?
第一步:確認每次請求都拿到了 usage
非流式請求的響應裡直接帶 usage。流式請求要注意:
- OpenAI 相容介面需要在請求里加
"stream_options": {"include_usage": true},最後一個資料塊才會帶上usage,否則流式請求可能拿不到任何用量資訊。 - Anthropic 介面的
message_start事件裡有輸入用量,message_delta事件裡有累計的輸出 token,要以最後一個message_delta為準,不要把每個事件裡的數字相加。
第二步:認清欄位
| 含義 | OpenAI 相容格式 | Anthropic 格式 |
|---|---|---|
| 輸入 token | usage.prompt_tokens(含快取命中部分) | usage.input_tokens(不含快取部分) |
| 快取讀取 | usage.prompt_tokens_details.cached_tokens | usage.cache_read_input_tokens |
| 快取寫入 | 無單獨欄位 | usage.cache_creation_input_tokens |
| 輸出 token | usage.completion_tokens(含推理) | usage.output_tokens |
| 推理 token | usage.completion_tokens_details.reasoning_tokens | 計入 output_tokens |
注意兩種格式在“輸入是否包含快取”上口徑相反。把 OpenAI 的 prompt_tokens 再加上 cached_tokens,或者把 Anthropic 的 input_tokens 當成全部輸入,都會算錯。
第三步:在自己這邊記賬
在呼叫層加一層日誌,每次請求記一行。下面是一個最小例子,把 OpenAI 相容介面的用量按單價重算:
import json, time
# 單價按你實際使用的渠道填寫,單位:每百萬 token
PRICE = {"input": 0.0, "cached": 0.0, "output": 0.0}
def log_usage(resp_json, request_id, path="usage.jsonl"):
u = resp_json.get("usage") or {}
cached = (u.get("prompt_tokens_details") or {}).get("cached_tokens", 0)
prompt = u.get("prompt_tokens", 0)
output = u.get("completion_tokens", 0)
cost = ((prompt - cached) * PRICE["input"]
+ cached * PRICE["cached"]
+ output * PRICE["output"]) / 1_000_000
row = {"ts": time.time(), "id": request_id, "model": resp_json.get("model"),
"prompt": prompt, "cached": cached, "output": output, "cost": cost}
with open(path, "a") as f:
f.write(json.dumps(row) + "\n")
request_id 用響應裡的 id 欄位,或你自己生成後放進請求頭的 ID。重試時要複用同一個業務 ID、另記一個嘗試次數,這樣才能在日誌裡看出哪些是重複請求。
第四步:和平臺明細對賬
拿到平臺匯出的明細後,按下面的順序比:
- 總請求數:平臺條數比你日誌多,說明有你沒記到的呼叫:SDK 自動重試、另一個程序在用同一個金鑰,或金鑰洩漏。
- 按小時聚合的 token 量:差額集中在少數幾個小時,回頭看這幾個小時有沒有故障或斷流。
- 單條比對:挑差額最大的幾條,用請求 ID 對上,比較輸入、快取、輸出三項。輸入對得上、輸出差很多,多半是推理 token;輸入差很多,多半是快取口徑或上下文膨脹。
- 單價:數量都對上了金額還對不上,檢查渠道單價是否在這段時間調整過。
中轉站和聚合閘道器的扣費要額外注意什麼?
用中轉站或聚合閘道器時,你看到的賬單經過了一層換算,需要額外確認:
- 計量單位:是直接按 token、按次,還是按自定義單位。
- 換算規則是否公開:輸入、輸出、快取是否分別換算,不同模型的係數是多少。
- 失敗請求怎麼算:5xx、超時、客戶端主動斷開分別是否扣費。
- 能否匯出逐條明細:只有總額、沒有逐條記錄的話,基本無法自己核對。
這幾項在AI API 中轉站怎麼選的 8 項檢查裡都有展開。
以 CU 為例:統一計量單位怎麼換算?
前面提到的 AGI Plan(是 cdnping 的商業合作方)用的是 CU 這種統一計量單位:1 CU 等於基準模型 1,000 個輸出 token;按量付費時 1 元 = 500 CU。按這個定義,在基準模型上生成 10 萬個輸出 token 消耗 100 CU,摺合按量價 0.2 元。輸入 token、快取以及其他能力組的折算係數,以 agiplan.dev/pricing 公佈的為準,這裡不替它推算。
統一單位的好處是一份額度可以在多個能力組之間切換,代價是你要多做一步換算才能和官方 token 價格對比。核對時還是同樣的方法:每條請求記下 usage,按公佈的係數折成 CU,再和後臺扣減對比。如果你在 Claude Code、Codex 裡改了介面地址,記得確認請求確實走到了你以為的那個地址,配置方法見Claude Code 自定義 API 與 Codex、OpenCode 介面地址配置。AGI Plan 各能力組的公開可用率可以在 /status/agiplan 檢視,對賬時遇到失敗集中的時段可以對照。
一份排查清單
賬單對不上時,按這個順序過一遍:
- 流式請求是否開啟了用量回傳(
include_usage或讀取message_delta) - 輸入 token 是否混淆了“含快取”和“不含快取”兩種口徑
- 推理 token 是否被算進了輸出
- SDK 的自動重試次數是多少,日誌裡能否區分重試
- 差額是否集中在某幾個小時,那段時間有無服務故障
- 平臺記錄的請求數是否多於自己的日誌,金鑰是否只在預期的地方使用
- 統計期間單價或換算係數是否調整過
- 中轉站或閘道器是否提供逐條明細,失敗請求是否計費
如果查下來發現主要成本來自上下文膨脹,而不是計費錯誤,那就該從用法上省了:把補全、格式化這類簡單任務分到便宜的模型上,思路見多模型路由怎麼做。