「見積もりよりAPIコストが大幅に高い」「トークン数が予測できない」「キャッシュを使っているのにコストが減らない」「モデル別の料金計算が間違っていた」といった問題が多く報告されています。本記事では、Claudeのトークン計算の仕組みと課金額を正確に予測するための方法を解説します。
問題の症状
問題1:想定より課金が多い
月次の請求額が想定の2〜3倍になっており、原因がわからない。
問題2:トークン数の事前見積もりができない
プロンプトを送信する前にトークン数がわからないため、コスト予測ができない。
問題3:キャッシュ設定後もコストが変わらない
プロンプトキャッシングを設定したが、cache_read_input_tokensが0のまま。
問題4:モデル変更後に予期しない料金増加
高性能モデルに移行したら料金が数倍になった。
原因の解説
Claudeの課金構造
Claude APIの課金はトークン単位で計算されます。
課金対象のトークン:
| トークン種別 | 説明 | 課金倍率(基準比) |
|---|---|---|
input_tokens |
通常の入力トークン | 1.0x |
cache_creation_input_tokens |
キャッシュ作成時の入力 | 1.25x(5分)/ 2.0x(1時間) |
cache_read_input_tokens |
キャッシュから読み込んだ入力 | 0.1x(90%削減) |
output_tokens |
出力トークン | 1.0x(入力の約3〜5倍の単価) |
重要: 出力トークンは入力トークンより単価が高いモデルがほとんどです。
モデル別の料金(2026年3月時点)
| モデル | 入力(/MTok) | 出力(/MTok) | コンテキスト |
|---|---|---|---|
| Claude Opus 4.6 | $5.00 | $25.00 | 1M tokens |
| Claude Sonnet 4.6 | $3.00 | $15.00 | 1M tokens |
| Claude Haiku 4.5 | $1.00 | $5.00 | 200k tokens |
バッチ処理(50%割引):
| モデル | 入力(/MTok) | 出力(/MTok) |
|---|---|---|
| Claude Opus 4.6 | $2.50 | $12.50 |
| Claude Sonnet 4.6 | $1.50 | $7.50 |
| Claude Haiku 4.5 | $0.50 | $2.50 |
MTok = 100万トークン
コストが高くなりやすい原因
原因1:Extended Thinkingのトークンが見えていない
Extended Thinking有効時、思考トークンは入力・出力トークンとは別にカウントされます。Summarized Thinking(要約版思考)の場合、表示は短い要約ですが課金はフルの思考トークン数で行われます。
原因2:長い会話履歴の蓄積
マルチターン会話では、毎回すべての会話履歴を入力トークンとして送信します。会話が長くなるほど課金が増えます。
1ターン目:入力100トークン → 出力100トークン 2ターン目:入力300トークン(100+100+100) → 出力100トークン 3ターン目:入力600トークン(300+100+100) → 出力100トークン ...
原因3:画像のトークン数を過小評価
高解像度の画像は大量のトークンを消費します。
トークン計算式: tokens ≈ (width × height) / 750 例: 1000×1000px → 約1,333トークン 2000×2000px → 約5,333トークン 4000×4000px → 約21,333トークン(制限を超えて自動縮小)
原因4:JSONやコードブロックの出力が長い
Claudeにコードを生成させたり、JSONを出力させると出力トークンが多くなります。
解決策
ステップ1:Token Counting APIでリクエスト前にトークンを確認
Token Counting APIを使うと、APIリクエストを送信する前にトークン数を確認できます。
import anthropic client = anthropic.Anthropic() def count_tokens_before_request( model: str, system: str, messages: list, tools: list = None ) -> dict: """リクエスト前にトークン数を確認""" params = { "model": model, "system": system, "messages": messages } if tools: params["tools"] = tools token_count = client.messages.count_tokens(**params) return { "input_tokens": token_count.input_tokens, "estimated_cost_usd": calculate_cost(model, token_count.input_tokens, 0) } def calculate_cost(model: str, input_tokens: int, output_tokens: int) -> float: """トークン数からコストを計算(USD)""" # モデル別の料金($/MTok) pricing = { "claude-opus-4-6": {"input": 5.0, "output": 25.0}, "claude-sonnet-4-6": {"input": 3.0, "output": 15.0}, "claude-haiku-4-5-20251001": {"input": 1.0, "output": 5.0}, } model_price = pricing.get(model, {"input": 3.0, "output": 15.0}) input_cost = (input_tokens / 1_000_000) * model_price["input"] output_cost = (output_tokens / 1_000_000) * model_price["output"] return input_cost + output_cost # 使用例 system_prompt = "あなたは優秀なPythonエンジニアです。" messages = [ {"role": "user", "content": "FastAPIでRESTful APIを作成するサンプルコードを書いてください"} ] count = count_tokens_before_request( model="claude-sonnet-4-6", system=system_prompt, messages=messages ) print(f"予測入力トークン: {count['input_tokens']:,}") print(f"予測コスト(入力のみ): ${count['estimated_cost_usd']:.4f}")
ステップ2:実際のAPI使用量を追跡する
import anthropic from dataclasses import dataclass, field from typing import List client = anthropic.Anthropic() @dataclass class TokenTracker: """API使用量トラッカー""" input_tokens: int = 0 output_tokens: int = 0 cache_creation_tokens: int = 0 cache_read_tokens: int = 0 requests: int = 0 def add(self, usage): self.input_tokens += usage.input_tokens self.output_tokens += usage.output_tokens self.cache_creation_tokens += getattr(usage, 'cache_creation_input_tokens', 0) self.cache_read_tokens += getattr(usage, 'cache_read_input_tokens', 0) self.requests += 1 def calculate_cost(self, model: str = "claude-sonnet-4-6") -> dict: pricing = { "claude-opus-4-6": {"input": 5.0, "cache_write": 6.25, "cache_read": 0.5, "output": 25.0}, "claude-sonnet-4-6": {"input": 3.0, "cache_write": 3.75, "cache_read": 0.3, "output": 15.0}, "claude-haiku-4-5-20251001": {"input": 1.0, "cache_write": 1.25, "cache_read": 0.1, "output": 5.0}, } p = pricing.get(model, pricing["claude-sonnet-4-6"]) M = 1_000_000 input_cost = (self.input_tokens / M) * p["input"] cache_write_cost = (self.cache_creation_tokens / M) * p["cache_write"] cache_read_cost = (self.cache_read_tokens / M) * p["cache_read"] output_cost = (self.output_tokens / M) * p["output"] total = input_cost + cache_write_cost + cache_read_cost + output_cost # キャッシュ節約額(キャッシュなしで全部入力した場合との比較) savings = (self.cache_read_tokens / M) * (p["input"] - p["cache_read"]) return { "input_cost": input_cost, "cache_write_cost": cache_write_cost, "cache_read_cost": cache_read_cost, "output_cost": output_cost, "total_cost": total, "cache_savings": savings } def summary(self, model: str = "claude-sonnet-4-6"): costs = self.calculate_cost(model) print(f"=== トークン使用量サマリー ===") print(f"リクエスト数: {self.requests:,}") print(f"入力トークン: {self.input_tokens:,}") print(f"出力トークン: {self.output_tokens:,}") print(f"キャッシュ書き込み: {self.cache_creation_tokens:,}") print(f"キャッシュ読み込み: {self.cache_read_tokens:,}") print(f"\n=== コスト内訳 ===") print(f"入力コスト: ${costs['input_cost']:.4f}") print(f"キャッシュ書き込みコスト: ${costs['cache_write_cost']:.4f}") print(f"キャッシュ読み込みコスト: ${costs['cache_read_cost']:.4f}") print(f"出力コスト: ${costs['output_cost']:.4f}") print(f"合計: ${costs['total_cost']:.4f}") print(f"キャッシュによる節約: ${costs['cache_savings']:.4f}") # 使用例 tracker = TokenTracker() for prompt in prompts: response = client.messages.create(...) tracker.add(response.usage) tracker.summary("claude-sonnet-4-6")
ステップ3:コスト削減のための最適化
# 1. 長い会話履歴の圧縮 def compress_conversation(messages: list, max_context_tokens: int = 50000) -> list: """会話が長くなりすぎたら古い部分を要約""" total_tokens = sum(estimate_tokens(m) for m in messages) if total_tokens > max_context_tokens: # 古いメッセージを要約 old_messages = messages[:-4] # 最新4件以外 summary_response = client.messages.create( model="claude-haiku-4-5-20251001", # 安いモデルで要約 max_tokens=500, messages=[ {"role": "user", "content": f"以下の会話を簡潔に要約してください:\n{str(old_messages)}"} ] ) summary = summary_response.content[0].text return [{"role": "user", "content": f"[会話履歴の要約]:{summary}"}] + messages[-4:] return messages # 2. 出力トークンを制限する response = client.messages.create( model="claude-sonnet-4-6", max_tokens=500, # 必要最小限に設定(デフォルト値に任せない) messages=[...] ) # 3. タスクに応じてモデルを使い分ける def select_model(task_complexity: str) -> str: """タスクの複雑さに応じてモデルを選択""" models = { "simple": "claude-haiku-4-5-20251001", # 単純な分類・要約 "medium": "claude-sonnet-4-6", # 一般的なタスク "complex": "claude-opus-4-6" # 複雑な推論・コード生成 } return models.get(task_complexity, "claude-sonnet-4-6")
コスト最適化のチェックリスト
| 最適化手法 | 削減効果 | 実装難易度 |
|---|---|---|
| Batch API使用 | 50%削減 | 低(非同期でOKなら) |
| プロンプトキャッシング | 最大90%削減(キャッシュ部分) | 中 |
| 安いモデルを使う(Haiku) | 70〜80%削減 | 低 |
| 出力トークンの最小化 | 10〜30%削減 | 中 |
| 会話履歴の圧縮 | 20〜50%削減 | 中 |
| 画像の最適化(リサイズ) | 画像依存 | 低 |
参照URL
- Token Counting API公式ドキュメント: https://docs.anthropic.com/en/docs/build-with-claude/token-counting
- モデル料金一覧: https://docs.anthropic.com/en/docs/about-claude/models/overview
- プロンプトキャッシング料金: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
- Message Batches API(50%割引): https://docs.anthropic.com/en/docs/build-with-claude/batch-processing
- Anthropic Console(使用量確認): https://console.anthropic.com/