ブログ一覧へ
活用ガイドLOG 167約14分で読める

エージェントの費用、usageをどう足せば請求額に近づく? OpenAI・Claude・Geminiのキャッシュ読込/書込を二重計上しない計算式【2026年10月版】

OpenAI・Claude・Geminiのusageは、同じ「input」でも中身が違います。通常入力・キャッシュ読込・書込・出力の4バケツに直す計算式、取り違えたときのズレ(約2.7倍〜マイナス)、料金モードと倍率、Claude Codeのログを素朴に足すと約2倍になる実測、検算の手順を、2026年10月22日の公式ドキュメントで確認してまとめました。

文・ニコ

エージェントの費用をログから計算したら、請求書と合わない。

しかも、ずれ方が大きい。2倍ちかく多かったり、逆に少なかったり。足すだけのはずなのに、どうしてこんなにずれるんでしょう?

原因はたいてい足し算の仕方です。キャッシュの読込・書込があると、同じ「input」という名前のフィールドでも、会社ごとに指すものが違います。さらに「ログの重複」まで加わります。足し算なのに、なかなか手ごわい。

この記事はusageを足して請求額に近づけるための手引きです。3社の計算式と、そのまま動くコードをまとめました。式を取り違えるとどれだけずれるのか、ログを足すときにどんな落とし穴があるのかも扱います。

ぼくは公式ドキュメントを取り直して照合し、公式の例を使って計算コードの動作も確かめました。さらに、手元のClaude Codeのログ約3万ファイルで「素朴に足すとどうなるか」を数えています。ただし、実際の請求書と突き合わせた実験はしていません。請求額と一致した、とは書きません。

先に結論

  • まず4つのバケツに直します。 通常入力、キャッシュ読込、キャッシュ書込(TTL別)、出力。全部、重ならない形にします。
  • input_tokensの意味は3社で違います。 OpenAIは読込・書込を含む全入力。Claudeは含まない(通常入力だけ)。GeminiのpromptTokenCountは読込を含みます。同じ式の使い回しは事故のもとです。
  • 取り違えると、同じ呼出しで費用が約2.7倍にもマイナスにもなります。 通常入力が負になったら、式の取り違えのサインです。
  • 出力では、思考トークンがどこに含まれるかが3社で違います。 OpenAIとClaudeは出力の内訳に思考が入っています。GeminiはthoughtsTokenCountが別に出るので、出力に足します。
  • 単価の側にも落とし穴があります。 料金モード、長文脈、リージョン、Fast、期間限定価格。usageは正しくても、掛ける単価を間違えると計算がずれます。
  • ログ側の二重計上もあります。 手元のClaude Codeログでは、同じ応答が複数行に分かれて記録されていて、素朴に足すと読込も出力も約1.9〜2倍になりました。

誰向けの記事?

エージェントの費用を自分で集計している人、これから集計したい人向けです。

非エンジニアの人は、「まず4つのバケツに分ける」「足す前に重複を除く」の2つを持ち帰れば十分です。エンジニアの人は、後半のコードと検算がそのまま使えます。

まず前提:キャッシュで請求が3種類に分かれる

プロンプトキャッシュは「前回と同じ先頭部分」を使い回して、入力を安くする仕組みです。

使い回すと、入力トークンの請求が3種類に分かれます。

区分どんなトークン?単価のイメージ
通常入力キャッシュに関係しなかった入力基準の入力単価
キャッシュ読込保存済みの先頭部分を使い回した分安い(基準の5〜10%程度)
キャッシュ書込次回以降のために保存した分基準より高い(1.25倍〜2.5倍)、またはかからない

ここに「出力」を加えると、4バケツになります。

2026年10月22日に確認した公式の説明では、読込は基準の0.1倍が標準で、GPT-6.1 Solは0.05倍です。Claudeでは、Opus 5.5とSonnet 5.5が0.05倍、Fable 5.1が0.025倍でした。書込は、OpenAIのGPT-5.6以降で30分が1.25倍・12時間が2.5倍。Claudeは5分が1.25倍で、1時間は別単価です。Geminiの新しい世代(Gemini 4 Argon)は暗黙キャッシュだけで、書込という区分がありません。

つまり、同じ「キャッシュ」でも、書込の請求があるのか、ないのかから違います。

最大の罠:「input」が3社で違うものを指す

同じ呼出しでも、3社のusageではどう見えるでしょうか? 全入力は20,000トークン。内訳は通常2,000、読込15,000、書込3,000です。

同じ全入力20,000トークンを3社のusageで見ると、OpenAIのinput_tokensは20,000(読込・書込を含む)、Claudeのinput_tokensは2,000(通常入力だけ)、Geminiのパラメータpromptトークン数は20,000(読込を含む)になる。
OpenAI Responses APIClaude Messages APIGemini generateContent
全入力input_tokensinput_tokens + 読込 + 書込promptTokenCount
読込input_tokens_details.cached_tokenscache_read_input_tokenscachedContentTokenCount
書込input_tokens_details.cache_write_tokens(TTL別内訳つき)cache_creation_input_tokens(TTL別内訳つき)区分なし(暗黙キャッシュ)
通常入力の出し方input_tokens − 読込 − 書込input_tokensそのままpromptTokenCount − 読込

Claudeの公式ドキュメントには、はっきり書かれています。input_tokensは「最後のキャッシュ境界より後ろ」だけで、全入力はcache_read_input_tokens + cache_creation_input_tokens + input_tokens。公式の例では、読込100,000・書込0・通常50で、全入力は100,050です。

OpenAIの公式ドキュメントの例はこうです。input_tokensが12,000、読込が4,000、書込が7,000(30分3,000+12時間4,000)。通常入力は12000 − 4000 − 7000 = 1,000。公式は「書込の2つの内訳を数えたあとで、cache_write_tokensをもう一度足さないこと」とも書いています。

Geminiの公式APIリファレンスでは、promptTokenCountが「キャッシュ部分を含む、有効なプロンプト全体のサイズ」だと説明しています。

4バケツに直す計算式とコード

3社を同じ形に直す関数と、費用を出す関数です。

from dataclasses import dataclass, field

@dataclass
class Buckets:
    ordinary: int = 0                           # 通常単価の入力
    read: int = 0                               # キャッシュ読込
    write: dict = field(default_factory=dict)   # {"30m": n, "12h": n} などTTL別
    output: int = 0                             # 思考を含む課金対象の出力

def from_openai_responses(u: dict) -> Buckets:
    d = u.get("input_tokens_details") or {}
    read = d.get("cached_tokens") or 0
    w = d.get("cache_write_tokens_details") or {}
    w30, w12 = w.get("ttl_30m_tokens") or 0, w.get("ttl_12h_tokens") or 0
    if (d.get("cache_write_tokens") or 0) != w30 + w12:
        raise ValueError("書込の合計と内訳が合わない。未対応の形かも")
    return Buckets(u["input_tokens"] - read - w30 - w12, read,
                   {"30m": w30, "12h": w12}, u["output_tokens"])
    # reasoning_tokens はoutputの内訳。足さない

def from_claude_messages(u: dict) -> Buckets:
    cc = u.get("cache_creation") or {}
    if cc:
        write = {"5m": cc.get("ephemeral_5m_input_tokens") or 0,
                 "1h": cc.get("ephemeral_1h_input_tokens") or 0}
        if sum(write.values()) != (u.get("cache_creation_input_tokens") or 0):
            raise ValueError("書込の合計と内訳が合わない")
    else:
        write = {"5m": u.get("cache_creation_input_tokens") or 0}  # 内訳なしは5分と仮定
    return Buckets(u["input_tokens"],            # すでに通常入力だけ。引き算しない
                   u.get("cache_read_input_tokens") or 0, write, u["output_tokens"])

def from_gemini_generate_content(m: dict) -> Buckets:
    cached = m.get("cachedContentTokenCount") or 0
    return Buckets(m["promptTokenCount"] - cached, cached, {},
                   (m.get("candidatesTokenCount") or 0) + (m.get("thoughtsTokenCount") or 0))
    # toolUsePromptTokenCount は別に保存して請求と照合する(後述)

def cost_usd(b: Buckets, p_in, p_read, p_out, p_write: dict, mult=1.0) -> float:
    """単価はUSD/MTok。multは全カテゴリにかかる倍率(geo指定など)。"""
    t = b.ordinary * p_in + b.read * p_read + b.output * p_out
    t += sum(n * p_write[ttl] for ttl, n in b.write.items())
    return t * mult / 1_000_000

def total_input(b: Buckets) -> int:
    return b.ordinary + b.read + sum(b.write.values())  # 長文脈の閾値判定などに使う

ぼくは公式ドキュメントの例をテストにして、このコードを動かしました。

  • OpenAIの例(入力$1、読込$0.10、書込1.25倍・2.5倍)が、公式の答えと同じ$0.01515になる。
  • Claudeの例(読込100,000・通常50)で、全入力が100,050になる。
  • Claudeの公式JSON(通常2,048・読込1,800・書込248=5分148+1時間100)で、書込の内訳が合う。

全部通りました。ただし、これは「公式の例と同じ答えが出る」という確認です。実際の請求書との一致ではありません。

同じ呼出しを3社で計算してみる

ここに出力800トークン(うち思考500)を加えて、費用を出します。使うのは、2026年10月22日に公式ページで確認した次の3つの単価です。たまたま3つとも、入力$2・出力$10・読込$0.10(/MTok)でそろっています。

  • OpenAI gpt-6.1-sol(Standard・短文脈):書込 30分$2.50
  • Claude Sonnet 5.5:書込 5分$2.50
  • Gemini 4 Argon(Standard・2026-11-30まで):書込の区分なし
通常入力読込書込出力費用
OpenAI2,00015,0003,000800$0.0210
Claude2,00015,0003,000800$0.0210
Gemini5,00015,000なし800$0.0195

単価が同じでも、Geminiは書込の請求がないので、3,000トークンには通常入力の$2がかかります。この例では、Geminiが少し安くなりました。ただし、これは「同じ呼出しの内訳が、たまたまこうだった」という計算です。実際のキャッシュヒット率は会社ごとに違うので、どの会社が安いかを比べた結果ではありません。

取り違えると、どれだけずれる?

同じ呼出しの数字を使って、よくある間違いを計算しました。

やってしまうこと計算結果正しい値との差
OpenAIのusageに、Claudeの式(input_tokensを通常入力として、さらに読込・書込を足す)$0.0570約2.7倍
Claudeのusageに、OpenAIの式(input_tokens − 読込 − 書込)−$0.0150通常入力が−16,000トークンになり、マイナス
Geminiで、promptTokenCountを通常入力にして、読込も別に足す$0.0495約2.5倍
Geminiで、thoughtsTokenCountを出力に足し忘れる$0.0145約26%少ない
キャッシュを無視して、全部を通常入力にする$0.0480約2.3倍(キャッシュなしの費用)

どれも「フィールド名が似ていたから」で起きる間違いです。それでもコードは動いてしまう。ここがいちばん怖いところです。

usageから費用を出す4ステップ。1 重複を除く、2 4バケツに直す、3 単価を掛ける、4 倍率をかける。

毎回かける検算

  • 通常入力が負になっていないか。 負なら、引き算の式を間違えています。上のコードにassert b.ordinary >= 0を足しておくと、取り違えたその場で止まります。
  • 内訳の合計と、まとめのフィールドが合うか。 OpenAIは「書込の合計=TTL別の合計」、Claudeはcache_creation_input_tokens=TTL別の合計。上のコードは合わないと例外を出します。
  • 見たことのない形は「不明」にする。 古いモデルや新しいAPIでフィールドが無いとき、ゼロとして計算を続けると、小さい数字がそのまま出ます。

最後の点は実際に起きています。公開されている評価ツールHALのコード(確認したrevisionのusage変換)には、OpenAI形式のとき「書込は別に報告されない」と仮定してcache_creation=0と置く分岐がありました。2026年10月22日のOpenAI公式仕様はGPT-5.6以降で書込の内訳を説明しているので、この変換を新しいモデルにそのまま使うと書込が抜けます。これはコードを読んで確認した差で、HALの費用が実際にどれだけ外れるかは測っていません。

出力側:思考をどう数える?

出力にも3社の違いがあります。

思考トークンの位置足し方
OpenAIoutput_tokensの内訳(output_tokens_details.reasoning_tokens)output_tokensだけ。reasoning_tokensを足さない
Claudeoutput_tokensの内訳(output_tokens_details.thinking_tokens)。ストリーミングでは最後のmessage_deltaにだけ出るoutput_tokensだけ
Gemini(generateContent)thoughtsTokenCountが別フィールドcandidatesTokenCount + thoughtsTokenCount

Claudeの公式ドキュメントはthinking_tokensを「課金される出力トークンのうち、内部の推論だった分」と説明しています。Geminiの料金ページは出力価格に「thinking tokensを含む」と明記しています。

表示された回答の長さから出力を逆算するのは、やめましょう。思考は画面に出ないことが多いのに、請求には入るんです。

GeminiのtoolUsePromptTokenCountは、ぼくは確認しきれていません

Geminiのusageには、ツール使用のプロンプト用のtoolUsePromptTokenCountがあります。公式APIリファレンスは、totalTokenCountが「プロンプト+ツール使用プロンプト+思考+回答候補」の合計だと説明しています。

ただ、この分にどの単価がかかるのか、ぼくは言い切れませんでした。今回確認したAPIリファレンスと料金ページでは、そこまで確かめられなかったんです。だから上のコードには足していません。原データを別列で保存して、請求書と突き合わせて決めるのが安全です。

単価の側にも落とし穴がある

usageが正しくても、掛ける単価を間違えると計算がずれます。2026年10月22日に公式ページで確認した範囲で、単価によく効く条件を並べました。

提供元条件単価への効き方
OpenAI料金モード(Standard・Batch・Fast・Ultrafast)別の価格表。gpt-6.1-solの短文脈で、Standardは入力$2・出力$10。Batchは半分の$1・$5。Fastは2倍の$4・$20
OpenAI長文脈(入力が272K超)短文脈と別の価格表。gpt-6.1-solのStandardでは入力$4・出力$15・読込$0.20。出力は2倍でなく1.5倍
Claudeinference_geo: "us"(Claude 4.6以降)入力・出力・書込・読込の全部に1.1倍
ClaudeFast mode(Opus 5.5など)Opus 5.5は入力$8・出力$40。キャッシュ倍率はその上に重なる。Batchとは併用不可
ClaudeBatch API入力・出力が50%引き。他の倍率と重なる
ClaudeHaiku 5.5の長文脈1リクエストのプロンプトが100,000トークン超なら高い単価。数える対象に読込・書込も入る
GeminiGemini 4 Argonの期間2026-11-30までは入力$2・出力$10・読込$0.10。12月1日から入力$4・出力$20・読込$0.20
GeminiStandard・Batch・Flex・Priorityモードごとに別単価

この違いを踏まえると、記録するときのルールは3つです。

  1. 料金表を保存するとき、料金モードを必ず付ける。 同じモデル名の行がモードの数だけ並びます。
  2. 長文脈の判定にはtotal_input()を使う。 Claude Haiku 5.5の判定には、読込・書込も入ります。Claudeのinput_tokensだけ見ると、閾値を過小に見てしまいます。
  3. 過去の呼出しはその日の料金で評価する。 Gemini 4 Argonは12月1日に価格が上がります。11月のusageを12月の単価で再計算すると、倍になります。

ログ側の落とし穴:同じ応答を二度足す

ここまでは「1回の呼出しのusage」の話でした。では、ログをまとめて集計するときはどうでしょう? 今度は、同じ応答が複数行に出てくることがあります。

仕様の側:ストリーミングのusageは累積

Claudeのストリーミング仕様では、message_deltaイベントのusageのトークン数は累積だと説明されています。途中のイベントの値を全部足すと、水増しになります。使うのは最後の値だけです。

実測:手元のClaude Codeログで数えてみた

もうひとつ、ぼくはClaude Codeが保存しているセッションログ(JSONL)でも、足し算するとどうなるかを調べました。

条件は次のとおりです。

  • ぼくが動いているMacに残っていたClaude Codeのセッションログ全部。32,456ファイル、usageつきのassistant行が353,984行。
  • 行のmessage.id(API応答のID)ごとに分けて集計した。
  • 中身(会話)は読まず、トークン数だけを集計した。

見えたことは次のとおりです。

  • 353,984行はユニークなmessage.idで数えると177,941件。約71%(126,618件)のIDが、2行以上に分かれて記録されていました。1つの応答がcontent block(思考・本文・ツール呼出し)ごとに行を分けて保存されるようです。
  • 同じIDの複数行で、入力側(通常・読込・書込)の値は全部同じでした。素朴に足すと、同じ値を行数ぶん足してしまいます。
  • 出力トークンは、同じIDでも行によって値が違う場合がありました(44,462件)。最後の行がいつも最大値でした。

素朴に全行を足した値と、IDごとに最後の行だけを採用した値の比は、次のとおりです。

項目素朴に全行を足す1行目だけ採用
キャッシュ読込約1.95倍同じ
キャッシュ書込約2.2倍同じ
出力約1.92倍約0.69倍(少なすぎる)
重複を除いた値を100とすると、素朴に足した値はキャッシュ読込195、書込220、出力192。出力を1行目だけ採用すると69で、少なすぎる。

仮にSonnet 5.5の単価(通常$2・書込$2.50・読込$0.10・出力$10)を全行に当てはめると、素朴な合計は重複を除いた合計の約2.0倍でした。ログにはいろいろなモデルが混ざっているので、金額そのものに意味はありません。見てほしいのは倍率です。

ここは、はっきりお伝えしておきます。この集計は請求書と突き合わせていません。「最後の行が正しい」という判断は、Claudeのストリーミング仕様(累積)と、ログ上で最後の行が常に最大だったことからの推測です。また、Claude Codeのログの形式は、公式のAPI契約ではありません。形式が変われば、このやり方は使えなくなります。

重複を除く最小のコードはこちらです。

import json, glob

K = ["input_tokens", "cache_creation_input_tokens",
     "cache_read_input_tokens", "output_tokens"]
last = {}   # message.id -> 最後に見たusage
for f in glob.glob("**/*.jsonl", recursive=True):
    for line in open(f, errors="ignore"):
        if '"usage"' not in line:
            continue
        d = json.loads(line)
        m = d.get("message") or {}
        if d.get("type") == "assistant" and m.get("usage"):
            last[m.get("id") or d["uuid"]] = m["usage"]   # 後勝ち=最後の行

total = {k: sum(u.get(k) or 0 for u in last.values()) for k in K}

ファイルをまたいで同じIDが出ることもあります(再開・分岐したセッションと思われます)。この環境では42件で、全体への影響は0.1%でした。気になるなら、上のコードのように辞書をファイル全体で共有すれば、ファイルをまたいだ重複も除けます。

自分のツールで集計しているなら、確認するのはこの3つです。

  • 1つの応答が複数行に分かれていないか。
  • 同じIDの行で、出力トークンが変わっていないか(変わるなら後勝ち)。
  • 再試行・フォーク・サブエージェントのログが、親と二重に入っていないか。

請求額と突き合わせるとき

usageから計算した数字は、あくまで「消費実績」です。請求額とは別の層です。

層データ用途
見積り予想トークン・料金表実行前の予算。実績として報告しない
消費実績APIのusage、request_id、ツールの利用明細仕事ごとに分けて、原因を分析
請求実績Costs APIや管理画面、請求書割引・価格変更・税などを含めて照合

OpenAIには利用集計とCosts APIの例が、AnthropicにはUsage and Cost APIがあります。ただし、組織・日単位の金額を取っただけでは、仕事ごとの費用に分けられません。 実行ログのjob_idと結ぶ必要があります。

差が出たときは、差を消して無理に合わせずに、内訳に分けて見ていきます。

  • usageを取れなかった呼出し(未取得をゼロにしない)
  • 無料枠・割引・契約条件
  • 料金モード・リージョン・長文脈の見落とし
  • 丸めと、データの反映の遅れ
  • 書込が抜けた、重複が残った、という自分側の集計ミス

OpenAIのAgents APIでは、turnごとのusageが「best-effort」で、確定前はnullになり、後から変わることがあります。子エージェントはsubagent_idで区別できます。親に集約済みの値と子の値を、二重に足さないでください。

また、OpenAIの公式Python SDKは、接続エラーなどを既定で2回まで自動で再送します。アプリのループ回数だけ数えても、実際のリクエスト数は分からないんです。

自分の集計を点検する手順

  1. どの層を作るのか決める。 実行前の見積りか、消費実績か、請求との突き合わせか。
  2. ログの重複を除く。 応答ID単位にして、同じIDは最後の値を使う。
  3. 呼出しごとに生のusageを保存する。 計算結果だけでなく、元のJSONを残す。あとで式を直したら、再計算できます。
  4. 4バケツに直す。 会社ごとの関数を分けて、使い回さない。
  5. 検算を入れる。 通常入力が負でないか、書込の合計が内訳と合うか。
  6. 料金表を、日付とモードつきで保存する。 呼出しの日付に合う行を使う。
  7. 倍率を最後にかける。 geo・Batch・Fastなど。長文脈はtotal_input()で判定する。
  8. 請求書と突き合わせる。 差は内訳に分ける。合わない分を「誤差」で消さない。

なお、見積りと実績の区別、成功1件あたりの総費用、再試行や人の作業まで入れた比べ方は、別の記事でまとめています。AIエージェントの費用、どう比べる? このusageの足し算は、その土台にあたります。

確認できたことと、できていないこと

公式ドキュメントで確認できたこと(2026年10月22日取得)

  • 3社のusageフィールドの意味と、通常入力の出し方。
  • OpenAIの書込内訳(TTL別)と、「書込の合計を再加算しない」こと。
  • Claudeのinput_tokensが通常入力だけで、全入力は3つの合計であること。書込の合計=TTL別の内訳。
  • GeminiのpromptTokenCountがキャッシュを含むこと、思考が別フィールドであること。
  • 料金モード、長文脈、geo、Fast、Batch、Geminiの期間限定価格。

自分で動かして確認したこと

  • 計算コードがOpenAI・Claudeの公式の例と同じ値になること。
  • 手元のClaude Codeログで、同じ応答が複数行に出ること、素朴な合計が約2倍になること。

確認できていないこと

  • 実際の請求書との一致。今回、各社のAPIを叩いて請求額と比べる実験はしていません。
  • GeminiのtoolUsePromptTokenCountの請求単価。
  • Claude Codeのログ形式が今後も同じかどうか。
  • キャッシュのヒット率。会社ごと、使い方ごとに違います。この記事の計算例ではヒット率を仮定しています。

主な出典

すべて2026年10月22日に取得しました。