SDKコスト追跡

Claude Code SDKは、Claudeとの各インタラクションに対して詳細なトークン使用量情報を提供します。このガイドでは、特に並列ツール使用や複数ステップの会話を扱う際に、コストを適切に追跡し、使用量レポートを理解する方法について説明します。 完全なAPIドキュメントについては、TypeScript SDKリファレンスを参照してください。

トークン使用量の理解

Claudeがリクエストを処理する際、メッセージレベルでトークン使用量を報告します。この使用量データは、コストを追跡し、ユーザーに適切に請求するために不可欠です。

主要概念

  1. ステップ: アプリケーションとClaude間の単一のリクエスト/レスポンスペア
  2. メッセージ: ステップ内の個別メッセージ(テキスト、ツール使用、ツール結果)
  3. 使用量: アシスタントメッセージに添付されたトークン消費データ

使用量レポート構造

単一 vs 並列ツール使用

Claudeがツールを実行する際、ツールが順次実行されるか並列実行されるかに基づいて使用量レポートが異なります:
import { query } from "@anthropic-ai/claude-code";

// 例: 会話での使用量追跡
const result = await query({
  prompt: "このコードベースを分析してテストを実行",
  options: {
    onMessage: (message) => {
      if (message.type === 'assistant' && message.usage) {
        console.log(`メッセージID: ${message.id}`);
        console.log(`使用量:`, message.usage);
      }
    }
  }
});

メッセージフローの例

典型的な複数ステップ会話でのメッセージと使用量の報告方法は以下の通りです:
<!-- ステップ1: 並列ツール使用を含む初期リクエスト -->
assistant (text)      { id: "msg_1", usage: { output_tokens: 100, ... } }
assistant (tool_use)  { id: "msg_1", usage: { output_tokens: 100, ... } }
assistant (tool_use)  { id: "msg_1", usage: { output_tokens: 100, ... } }
assistant (tool_use)  { id: "msg_1", usage: { output_tokens: 100, ... } }
user (tool_result)
user (tool_result)
user (tool_result)

<!-- ステップ2: フォローアップレスポンス -->
assistant (text)      { id: "msg_2", usage: { output_tokens: 98, ... } }

重要な使用量ルール

1. 同じID = 同じ使用量

同じidフィールドを持つすべてのメッセージは同一の使用量を報告します。Claudeが同じターンで複数のメッセージを送信する場合(例:テキスト + ツール使用)、それらは同じメッセージIDと使用量データを共有します。
// これらのメッセージはすべて同じIDと使用量を持ちます
const messages = [
  { type: 'assistant', id: 'msg_123', usage: { output_tokens: 100 } },
  { type: 'assistant', id: 'msg_123', usage: { output_tokens: 100 } },
  { type: 'assistant', id: 'msg_123', usage: { output_tokens: 100 } }
];

// 一意のメッセージIDごとに一度だけ請求
const uniqueUsage = messages[0].usage; // このIDを持つすべてのメッセージで同じ

2. ステップごとに一度請求

ユーザーにはステップごとに一度だけ請求すべきです、個別のメッセージごとではありません。同じIDを持つ複数のアシスタントメッセージを見た場合、そのうちの任意の一つから使用量を使用してください。

3. 結果メッセージには累積使用量が含まれる

最終的なresultメッセージには、会話のすべてのステップからの累積使用量の合計が含まれます:
// 最終結果には総使用量が含まれます
const result = await query({
  prompt: "複数ステップのタスク",
  options: { /* ... */ }
});

console.log("総使用量:", result.usage);
console.log("総コスト:", result.usage.total_cost_usd);

実装: コスト追跡システム

コスト追跡システムを実装する完全な例は以下の通りです:
import { query } from "@anthropic-ai/claude-code";

class CostTracker {
  private processedMessageIds = new Set<string>();
  private stepUsages: Array<any> = [];
  
  async trackConversation(prompt: string) {
    const result = await query({
      prompt,
      options: {
        onMessage: (message) => {
          this.processMessage(message);
        }
      }
    });
    
    return {
      result,
      stepUsages: this.stepUsages,
      totalCost: result.usage?.total_cost_usd || 0
    };
  }
  
  private processMessage(message: any) {
    // 使用量を持つアシスタントメッセージのみを処理
    if (message.type !== 'assistant' || !message.usage) {
      return;
    }
    
    // このメッセージIDを既に処理している場合はスキップ
    if (this.processedMessageIds.has(message.id)) {
      return;
    }
    
    // 処理済みとしてマークし、使用量を記録
    this.processedMessageIds.add(message.id);
    this.stepUsages.push({
      messageId: message.id,
      timestamp: new Date().toISOString(),
      usage: message.usage,
      costUSD: this.calculateCost(message.usage)
    });
  }
  
  private calculateCost(usage: any): number {
    // ここで価格計算を実装
    // これは簡略化された例です
    const inputCost = usage.input_tokens * 0.00003;
    const outputCost = usage.output_tokens * 0.00015;
    const cacheReadCost = (usage.cache_read_input_tokens || 0) * 0.0000075;
    
    return inputCost + outputCost + cacheReadCost;
  }
}

// 使用方法
const tracker = new CostTracker();
const { result, stepUsages, totalCost } = await tracker.trackConversation(
  "このコードを分析してリファクタリング"
);

console.log(`処理されたステップ: ${stepUsages.length}`);
console.log(`総コスト: $${totalCost.toFixed(4)}`);

エッジケースの処理

出力トークンの不一致

稀に、同じIDを持つメッセージで異なるoutput_tokens値を観察する場合があります。これが発生した場合:
  1. 最高値を使用 - グループ内の最後のメッセージが通常正確な合計を含みます
  2. 総コストと照合 - 結果メッセージのtotal_cost_usdが権威的です
  3. 不一致を報告 - Claude Code GitHubリポジトリで問題を報告してください

キャッシュトークンの追跡

プロンプトキャッシュを使用する際は、これらのトークンタイプを個別に追跡してください:
interface CacheUsage {
  cache_creation_input_tokens: number;
  cache_read_input_tokens: number;
  cache_creation: {
    ephemeral_5m_input_tokens: number;
    ephemeral_1h_input_tokens: number;
  };
}

ベストプラクティス

  1. 重複排除にメッセージIDを使用: 二重請求を避けるため、常に処理済みメッセージIDを追跡する
  2. 結果メッセージを監視: 最終結果には権威的な累積使用量が含まれる
  3. ログ記録を実装: 監査とデバッグのためにすべての使用量データをログに記録する
  4. 失敗を適切に処理: 会話が失敗した場合でも部分的な使用量を追跡する
  5. ストリーミングを考慮: ストリーミングレスポンスの場合、メッセージが到着するにつれて使用量を蓄積する

使用量フィールドリファレンス

各使用量オブジェクトには以下が含まれます:
  • input_tokens: 処理されたベース入力トークン
  • output_tokens: レスポンスで生成されたトークン
  • cache_creation_input_tokens: キャッシュエントリの作成に使用されたトークン
  • cache_read_input_tokens: キャッシュから読み取られたトークン
  • service_tier: 使用されたサービス階層(例:“standard”)
  • total_cost_usd: USD単位の総コスト(結果メッセージのみ)

例: 請求ダッシュボードの構築

請求ダッシュボード用の使用量データを集約する方法は以下の通りです:
class BillingAggregator {
  private userUsage = new Map<string, {
    totalTokens: number;
    totalCost: number;
    conversations: number;
  }>();
  
  async processUserRequest(userId: string, prompt: string) {
    const tracker = new CostTracker();
    const { result, stepUsages, totalCost } = await tracker.trackConversation(prompt);
    
    // ユーザー合計を更新
    const current = this.userUsage.get(userId) || {
      totalTokens: 0,
      totalCost: 0,
      conversations: 0
    };
    
    const totalTokens = stepUsages.reduce((sum, step) => 
      sum + step.usage.input_tokens + step.usage.output_tokens, 0
    );
    
    this.userUsage.set(userId, {
      totalTokens: current.totalTokens + totalTokens,
      totalCost: current.totalCost + totalCost,
      conversations: current.conversations + 1
    });
    
    return result;
  }
  
  getUserBilling(userId: string) {
    return this.userUsage.get(userId) || {
      totalTokens: 0,
      totalCost: 0,
      conversations: 0
    };
  }
}

関連ドキュメント