# 推論トークンを二重計上しない：Gemini・OpenAI・Claudeのusage検算

> OpenAIとClaudeは出力合計に推論を含み、GeminiのgenerateContentは候補出力と推論を合算します。APIごとのusage項目、欠損値の扱い、ストリーミングと再試行の落とし穴を、計算例とPythonで整理します。

- Source: https://www.aifreeapi.com/ja/posts/reasoning-token-billing
- Language: ja
- Published: 2026-09-07
- Updated: 2026-09-07
- Publisher: AI Free API (https://www.aifreeapi.com)

APIの回答が短いのに、出力トークン数が多い。あるいは、管理画面の見積額だけが提供元より高い。こうしたときは、まず**推論分が出力合計に含まれているか**を確認します。推論に料金がかかることと、同じ推論分を集計処理で二度足すことは別の問題です。

OpenAIとClaudeでは、推論トークンは出力合計の内訳です。一方、Geminiのネイティブな `generateContent` では、候補出力と推論が別項目で返るため、通常のテキスト出力の課金対象を求めるには両方を足します。モデル名だけで共通の式を選ぶと、過大計上にも過少計上にもなります。

本稿は2026年9月7日時点の公式仕様に基づく、**出力トークン数の検算**です。入力、キャッシュ、ツールなどを含む請求書全体の再現や、月額契約の二重決済は扱いません。

## 最初に、usageの「合計」と「内訳」を分ける

ネイティブAPIの返却値を直接扱う場合は、次の関係を出発点にできます。

| API | 出力の課金対象として数える値 | 推論分の項目 | 二重計上を防ぐ扱い |
| --- | --- | --- | --- |
| OpenAI Responses | `usage.output_tokens` | `usage.output_tokens_details.reasoning_tokens` | 内訳を出力合計へ足さない |
| OpenAI Chat Completions | `usage.completion_tokens` | `usage.completion_tokens_details.reasoning_tokens` | 内訳を出力合計へ足さない |
| Gemini `generateContent` | `usageMetadata.candidatesTokenCount + usageMetadata.thoughtsTokenCount` | `usageMetadata.thoughtsTokenCount` | 別項目なので合算する。入力込みの総数には足さない |
| Claude Messages | `usage.output_tokens` | 返される場合の `usage.output_tokens_details.thinking_tokens` | 内訳を出力合計へ足さない |

OpenAIは推論も出力料金の対象とし、出力合計に含めます。Claudeの現在の資料も `thinking_tokens` を `output_tokens` の内訳として定義しています。Claudeの内訳は古いSDKや中継サービスでは取得できない場合がありますが、出力合計まで不明になるとは限りません。[OpenAIの推論ガイド](https://developers.openai.com/api/docs/guides/reasoning)、[Claudeの推論と料金](https://platform.claude.com/docs/en/build-with-claude/thinking-steering-and-cost#pricing)

Geminiの `totalTokenCount` は入力、候補出力、推論の合計です。通常のテキスト生成では、`promptTokenCount + candidatesTokenCount + thoughtsTokenCount` と照合できます。`totalTokenCount` に推論を足すと重複し、総数全体に出力単価を掛けると入力まで出力扱いになります。[GeminiのUsageMetadata仕様](https://ai.google.dev/api/generate-content#UsageMetadata)

この表の式を、Gemini Interactions、Vertex、Live、互換エンドポイントへそのまま移してはいけません。中継サービスがGeminiの値を推論込みの `completion_tokens` に変換していれば、その値に推論を再加算する必要はありません。利用しているエンドポイント、SDK、変換処理の仕様を確認し、変換前のusageを保存してください。

## 三つの数値例で、足す場所を確認する

### OpenAI：1,186に1,024を足さない

OpenAIの公式例では、入力が75、出力合計が1,186、うち推論が1,024、入力と出力の総数が1,261です。

- 出力料金の対象：**1,186トークン**
- 誤った加算：1,186 + 1,024 = 2,210トークン
- 入出力の検算：75 + 1,186 = 1,261トークン

仮に出力単価を100万トークン当たり10米ドルと置くと、出力分は0.01186米ドルです。二重加算した計算では0.02210米ドルになります。この単価は説明用であり、実際のモデルの料金ではありません。使用量は公式資料の例で、APIを呼び出して取得した実測値ではありません。[公式のusage例](https://developers.openai.com/api/docs/guides/reasoning)

差し引きの `1,186 − 1,024 = 162` は「推論以外の出力分」です。**画面に見える回答が正確に162トークンある、とは言えません。** 出力には非表示の書式、チャネル、ツール呼び出しの構造などが含まれ得るためです。回答文字列を手元のトークナイザーで数えた値と一致しなくても、それだけで課金の異常とは判断できません。[OpenAIの出力トークン数の説明](https://developers.openai.com/api/docs/guides/token-counting#understand-output-token-counts)

![出力トークンの合計と推論の内訳を帯で示した検算図](https://www.aifreeapi.com/posts/ja/reasoning-token-billing/img/output-subset.webp)

### Gemini：候補出力500と推論1,500なら、出力分は2,000

説明用の合成データとして、`promptTokenCount = 800`、`candidatesTokenCount = 500`、`thoughtsTokenCount = 1,500`、`totalTokenCount = 2,800` を考えます。

出力単価の対象は `500 + 1,500 = 2,000` トークンです。候補出力500だけなら推論分が抜け落ち、総数2,800を出力として扱うと入力800まで混ざります。公式料金表も、出力料金にはthinking tokensを含むとしています。StandardとBatchなど、実際に適用された区分の出力単価を使います。[Gemini API料金表](https://ai.google.dev/gemini-api/docs/pricing)

### Claude：348のうち312が推論でも、出力合計は348

Claudeの公式例は `input_tokens = 25`、`output_tokens = 348`、`thinking_tokens = 312` です。出力として数えるのは348であり、`348 + 312 = 660` ではありません。内訳が返らない場合は、「出力合計348、推論分不明」と記録できます。

表示されるthinkingの要約と、課金される実際の推論量も分けて考えます。要約の長さから推論トークン数を逆算することはできません。公式資料では、要約生成そのものには料金がかからず、実際のthinkingに基づいて課金されると説明しています。[Claudeの料金と推論内訳](https://platform.claude.com/docs/en/build-with-claude/thinking-steering-and-cost#pricing)

## 欠損をゼロにすると、計算ミスを発見できなくなる

集計コードでよくある `value or 0` や `value ?? 0` は、usageの調査では注意が必要です。ゼロは「計測した結果、消費がなかった」という数値であり、フィールドがないこととは意味が違います。

| 受け取った状態 | 記録できること | 避ける処理 |
| --- | --- | --- |
| OpenAIまたはClaudeの出力合計はあるが推論内訳がない | 出力合計は確定、推論内訳は不明 | 推論ゼロと断定する |
| Geminiの候補出力はあるが推論項目がない | この情報だけでは合算値を確定できない | モデルやAPIの仕様確認なしでゼロを補う |
| ストリームが途切れ、最終usageを取得できない | 使用量未確定として保留できる | 無料だったものとして集計する |
| 出力合計より推論内訳が大きい | データか変換処理に矛盾がある | 差をゼロに丸めて正常扱いする |

特定のAPIやSDKの仕様で、省略がゼロを意味すると確認できた場合は、その変換処理に条件を明示できます。ただし「推論内訳は省略可能だから、どのAPIでもゼロでよい」という共通処理にはしません。

![usageの合計と内訳がある場合、内訳が不明な場合、最終値がない場合を並べた図](https://www.aifreeapi.com/posts/ja/reasoning-token-billing/img/missing-usage.webp)

### 出力合計だけを正規化するPython例

次の関数は、ネイティブAPIの `usage` または `usageMetadata` オブジェクトを受け取り、出力合計と推論内訳を返します。欠損は `None` のまま残し、負数、小数、真偽値、内訳の超過を拒否します。料金表や入力分の計算は含めていません。

```python
def count(value):
    if value is None:
        return None
    if type(value) is not int or value < 0:
        raise ValueError("Invalid token count")
    return value


def normalize_output(api, usage):
    if api == "gemini.generateContent":
        candidate = count(usage.get("candidatesTokenCount"))
        reasoning = count(usage.get("thoughtsTokenCount"))
        total = (candidate + reasoning
                 if candidate is not None and reasoning is not None
                 else None)
    else:
        fields = {
            "openai.responses": (
                "output_tokens", "output_tokens_details", "reasoning_tokens"),
            "openai.chat": (
                "completion_tokens", "completion_tokens_details", "reasoning_tokens"),
            "claude.messages": (
                "output_tokens", "output_tokens_details", "thinking_tokens"),
        }
        if api not in fields:
            raise ValueError("Unsupported API contract")
        total_key, details_key, reasoning_key = fields[api]
        total = count(usage.get(total_key))
        details = usage.get(details_key)
        if details is None:
            details = {}
        if not isinstance(details, dict):
            raise ValueError("Invalid token details")
        reasoning = count(details.get(reasoning_key))
        if total is not None and reasoning is not None and reasoning > total:
            raise ValueError("Reasoning exceeds output total")
    return {"output_total": total, "reasoning": reasoning}
```

この関数の出力では、`reasoning` は分析用の内訳です。正規化後の課金計算は `output_total` を一度だけ使い、そこへ `reasoning` を足しません。Geminiの加算は関数の中で済んでいます。`output_total` が `None` のレコードは確定費用の集計から分け、未確定件数も一緒に表示すると見落としを防げます。

上の公式例と合成データに加え、内訳欠損、合算に必要な値の欠損、不正な値をローカルで検算しました。APIへの有料リクエストは実行していません。この短い関数は、互換サービスの独自形式や、音声・画像などの単価区分を網羅する請求処理ではありません。

## ストリーミングと再試行は、別の重複原因になる

推論の式が正しくても、同じ使用量をイベントごとに足すと過大計上になります。Claudeの `message_delta` に含まれるusageは累積値です。出力数が順に40、90、120と通知された合成例では、最終値は120であり、合計250ではありません。SDKの最終メッセージか、最後の確定した累積値を使います。推論内訳は最終 `message_delta` に現れる場合があります。[Claudeのストリーミング仕様](https://platform.claude.com/docs/en/build-with-claude/streaming)、[推論内訳の取得](https://platform.claude.com/docs/en/build-with-claude/thinking-steering-and-cost#pricing)

保存するときは、利用者の一回の操作と、提供元へ送った各リクエストを区別します。調査に必要なのは、API種別、モデル、提供元のリクエストまたはレスポンスID、ローカルの試行ID、元のusage、最終値か未確定値かという情報です。IDの取得方法はAPIやSDKによって異なります。

同じ最終イベントの再配送なら、同一レコードの更新として扱えます。一方、タイムアウト後の再試行で別のリクエストが処理されていれば、それぞれ消費が発生する可能性があります。プロンプトが同じという理由で片方を削除すると、今度は過少計上になります。

また、Claudeではモデルによって前のthinkingが文脈に保持され、後続リクエストで入力料金の対象になる場合があります。これは同一レスポンス内の内訳を二度足すバグとは異なります。会話全体を調べるときは、各ターンの入力と出力を分けて追います。[Claudeのthinkingと文脈の扱い](https://platform.claude.com/docs/en/build-with-claude/thinking)

## 見積額が合わないときの確認順

最初から請求額の差だけを追うより、同じリクエストの生データから順に確認すると、原因を絞り込めます。

1. **APIと返却形式を特定する。** ネイティブAPIか互換サービスか、どのエンドポイントとSDKかを記録します。
2. **元のusageと保存値を比較する。** 内訳を再加算していないか、欠損をゼロに置き換えていないか、累積イベントを足していないかを確認します。
3. **出力合計を検算する。** OpenAIとClaudeは合計を採用し、Gemini `generateContent` は必要な項目がそろっている場合に候補出力と推論を合算します。
4. **リクエストを突き合わせる。** 同じイベントの重複と、実際に行われた再試行を分けます。
5. **最後に単価を当てる。** 実際のモデル、サービス区分、適用日、入力とキャッシュの扱い、別途発生するツール料金などをそろえます。

通常のテキスト出力を単一の出力単価で計算する範囲なら、`出力費用 = 出力合計 ÷ 1,000,000 × 100万トークン当たりの出力単価` です。推論に一律の倍率を追加する式ではありません。モデル選定や料金体系全体を比較したい場合は、[Gemini・OpenAI・ClaudeのAPIコスト比較](/ja/posts/gemini-api-vs-openai-vs-claude-2026-cost-guide)を参照してください。

回答が空でも、OpenAIでは推論中に出力上限へ達し、表示用のテキストを生成できないまま費用が発生することがあります。見える回答の長さではなく、usageと終了状態を確認します。[OpenAIの出力上限と不完全なレスポンス](https://developers.openai.com/api/docs/guides/reasoning)

修正後は、内訳がある正常例だけでなく、「合計はあるが内訳は不明」「最終usageがない」「内訳が合計を超える」レコードでも、数値の意味が保たれることを確かめてください。合計、内訳、未確定を分けて保存できれば、推論の二重計上を直した後も、別の原因による費用差を追跡できます。
