Agent SDK でコストと使用量を追う:total_cost_usd と modelUsage

Claude Agent SDK エンジニアリング 7分で読めます

Claude Agent SDK は、1回の対話ごとにトークン使用量と推定コストを細かく返します。結果メッセージの total_cost_usd を読むだけで呼び出し1回分の推定額がわかり、アシスタントメッセージの usage や結果メッセージの modelUsage を使えば、ステップ別・モデル別の内訳も追えます。ただし、並列ツール呼び出しでの二重計上やサブエージェント分の扱いなど、集計を誤りやすい点がいくつかあります。

この記事では、公式ドキュメント「Track cost and usage」に沿って、コスト値の性質、合計コストの読み方、ステップ別・モデル別の集計、複数回の呼び出しやストリーミング入力での合算、エラー時とキャッシュの注意点を整理します。SDK の基本は「Claude Agent SDK でエージェントを構築する」を、セッションの再開は「Agent SDK のセッション」を参照してください。

コスト値は推定であり請求額ではない

最初に押さえておくべきなのは、total_cost_usd と costUSD がクライアント側で計算した推定値だという点です。SDK はビルド時に同梱した価格表(設定で modelPricing の表が有効な場合はそちら)を使って手元で計算しており、次のような場合は実際の請求額とずれることがあります。

SDK が再現する課金ルールの1つにデータレジデンシー価格があります。レスポンスの usage が inference_geo: "us" を報告した場合、そのレスポンスのトークンの定価に1.1を掛けます(Web 検索のようなリクエスト単位の料金には掛けません)。これには TypeScript Agent SDK v0.3.239 以降、または Python Agent SDK v0.2.144 以降が必要です。

公式は、これらのフィールドを開発中の目安やおおまかな予算管理に使い、エンドユーザーへの請求や金銭的な判断には使わないよう求めています。正式な請求額は Claude Console の Usage ページか、「Usage and Cost API」で確認します。

使用量の単位:query・ステップ・セッション

単位意味
query() 呼び出しSDK の query() を1回呼ぶこと。Claude の応答、ツール実行、結果を受けての再応答と、複数のステップを含みうる。最後に result メッセージが1つ出る(ストリーミング入力モードではターンごとに1つ)
ステップquery() 内の1回のリクエスト/レスポンス。ステップごとにトークン使用量付きのアシスタントメッセージが出る
セッションresume オプションでセッション ID によってつながった一連の query() 呼び出し。再開した呼び出しの結果は、その呼び出し分だけでなくセッション全体の支出を報告する

TypeScript と Python は同じデータを別の名前で出します。TypeScript はアシスタントメッセージの message.message.id と message.message.usage、結果メッセージの modelUsage。Python はアシスタントメッセージの message.usage と message.message_id、結果メッセージの model_usage と total_cost_usd です。計算方法と粒度は同じです。

1回の query の合計コストを読む

推定合計だけが必要なら、ステップ別の値は無視して結果メッセージ(TypeScript の SDKResultMessage、Python の ResultMessage)の total_cost_usd を1つ読めば足ります。この値はその呼び出しの全ステップの累計で、セッションを再開した呼び出しではセッションの過去の支出も含みます。

import { query } from "@anthropic-ai/claude-agent-sdk";

try {
  for await (const message of query({ prompt: "Summarize this project" })) {
    if (message.type === "result") {
      console.log(`Total cost: $${message.total_cost_usd}`);
    }
  }
} catch (error) {
  // 単発の query() はエラー結果を返したあとに例外を投げる。
  // エラー結果にも total_cost_usd は入っているので上の分岐は実行済み。
  // 接続やプロセスの障害では結果メッセージ自体が出ない
  console.error(`Session ended with an error: ${error}`);
}

Python では isinstance(message, ResultMessage) で判定し、message.total_cost_usd or 0 のように読みます。Python ではこのフィールドが省略可能(Optional)な型なので、None でないか確認してから使います。成功・エラーどちらの結果にも入っていますが、セッションがクラッシュしたときの最終結果では0になっていることがあります(後述)。

サブエージェント分が含まれるかはフィールドで違う

エージェントがサブエージェントを起動する場合、結果メッセージの3つのフィールドは数える範囲が異なります。

フィールドサブエージェントの活動
usage含まない。トップレベルのエージェントループだけを数える
total_cost_usd含む。トップレベルのループと合わせて数える
modelUsage / model_usage含む。さらにモデル別に分けて数える

入れ子が生じた時点で usage は少なく出るため、ツリー全体のトークン集計には modelUsage を使います。単一メッセージ入力モードで、最終ターンの終わりにバックグラウンドのサブエージェントがまだ動いている場合、Claude Code は上限時間までそれを待ってから結果を出し、その待ち時間の作業も total_cost_usd・duration_api_ms・modelUsage に含めます。サブエージェントがどこまで費用を足せるかは、深さ・並行数・支出の上限で制限できます。

ステップ別・モデル別に使用量を見る

ステップ別:メッセージ ID で重複を除く

各アシスタントメッセージには、TypeScript では message.message に入れ子の BetaMessage があり、その id と usage(input_tokens、output_tokens など)を持ちます。Claude が1つのターンで複数のツールを並列に使うと、そのターンのメッセージはすべて同じ ID と同じ usageを持つため、ID ごとに1回だけ数えないと合計が膨らみます。

また、重複を除いたステップ別の値が正確なのは入力トークンとキャッシュトークンだけです。ステップ別の output_tokens は仮の値なので、出力トークンは結果メッセージから読みます。次の例は、メインループのメッセージ ID ごとに入力トークンを1回ずつ足し(サブエージェントのメッセージは parent_tool_use_id で除外)、出力トークンは結果メッセージから取ります。

import { query } from "@anthropic-ai/claude-agent-sdk";

const seenIds = new Set<string>();
let totalInputTokens = 0;
let resultOutputTokens = 0;

try {
  for await (const message of query({ prompt: "Summarize this project" })) {
    if (message.type === "assistant" && !message.parent_tool_use_id) {
      const msgId = message.message.id;
      // 並列ツール呼び出しは同じ ID を共有するので1回だけ数える
      if (!seenIds.has(msgId)) {
        seenIds.add(msgId);
        totalInputTokens += message.message.usage.input_tokens;
      }
    }
    if (message.type === "result") {
      // ステップ別の output_tokens は仮の値。累計は結果メッセージにある
      resultOutputTokens = message.usage.output_tokens;
    }
  }
} catch (error) {
  console.error(`Session ended with an error: ${error}`);
}

console.log(`Steps: ${seenIds.size}`);
console.log(`Input tokens: ${totalInputTokens}`);
console.log(`Output tokens: ${resultOutputTokens}`);

モデル別:modelUsage を回す

結果メッセージの modelUsage は、モデル名からモデル別のトークン数とコストへの対応表です。メインエージェントに Opus、サブエージェントに Haiku のように複数のモデルを使うとき、どこでトークンを使っているかを見るのに役立ちます。

for await (const message of query({ prompt: "Summarize this project" })) {
  if (message.type !== "result") continue;
  for (const [modelName, usage] of Object.entries(message.modelUsage)) {
    console.log(`${modelName}: $${usage.costUSD.toFixed(4)}`);
    console.log(`  Input tokens: ${usage.inputTokens}`);
    console.log(`  Output tokens: ${usage.outputTokens}`);
    console.log(`  Cache read: ${usage.cacheReadInputTokens}`);
    console.log(`  Cache creation: ${usage.cacheCreationInputTokens}`);
  }
}

(公式例ではこのループも try/catch で囲んでいます。)各エントリの costBasis は、そのモデルの直近のリクエストをどの価格表で計算したかを示し、定価なら list、modelPricing の表なら managed、どちらにも一致しなければ unknown になります。このフィールドには Claude Code v2.1.246 以降が必要です。

複数回の呼び出しとストリーミング入力での集計

複数の query() を合算する

let totalSpend = 0;
const prompts = [
  "Read the files in src/ and summarize the architecture",
  "List all exported functions in src/auth.ts"
];

for (const prompt of prompts) {
  try {
    for await (const message of query({ prompt })) {
      if (message.type === "result") {
        totalSpend += message.total_cost_usd;
        console.log(`This call: $${message.total_cost_usd}`);
      }
    }
  } catch (error) {
    console.error(`Call failed: ${error}`);  // 次のプロンプトへ進む
  }
}
console.log(`Total spend: $${totalSpend.toFixed(4)}`);

どちらの場合も、合算した値はやはりクライアント側の推定値です。

ストリーミング入力モードでの読み方

ストリーミング入力モードでは、1回の query() が複数のユーザーターンを運び、ターンごとに結果メッセージが出ます。このときフィールドの範囲は次のように分かれます。

アプリが /clear・/reset・/new を送らない限り、結果を足し合わせず最新の結果を読みます。これら3つのコマンドを送ると累計はリセットされます(query() 内でほかにリセットされる契機はありません)。/clear のターン自身の結果はリセット以降の分だけで、新しい session_id を持ちます。呼び出し全体の合計は、各 /clear の直前の結果と呼び出しの最終結果を足して求めます。TypeScript ではリセットのたびに SDKConversationResetMessage、Python では ConversationResetMessage が出るので、ストリームからリセットを検出できます(Python SDK v0.2.137 より前はこのメッセージが落ちていたため、送った /clear を自分で数えます)。

予算上限の maxBudgetUsd(Python は max_budget_usd)は、その呼び出し自身の支出だけを数えます。再開したセッションから復元された累計は対象外で、/clear で予算もやり直しになります。推定値は上限を超えることがあるため、公式は余裕を持たせるよう勧めています。

エラー時・キャッシュ・出力トークンの注意点

出力トークンは結果メッセージから読む

Claude Code はアシスタントメッセージを、レスポンス開始時(message_start)に API が報告した使用量から組み立てます。そのため各アシスタントメッセージの output_tokens は生成前の暫定値で、1つのレスポンスから出る複数のメッセージすべてが同じ仮の値を持ちます。実際の出力数はレスポンス終了時に API から届き、結果メッセージに加算されるので、結果の usage か modelUsage から読みます。ストリーミング中に出力数の増加を見たい場合は、includePartialMessages(Python は include_partial_messages)を有効にし、message_delta ストリームイベントの usage を読みます。

失敗した会話のコストも数える

途中で失敗した会話でも、失敗までに使ったトークンは消費されています。結果メッセージの subtype が success でもエラー系(error_max_turns、error_during_execution、error_max_budget_usd、error_max_structured_output_retries)でも、コストを読み取ります。ただしエラー結果の一部では usage が実際より少なく出ます。

選べるなら usage ではなく total_cost_usd か modelUsage で集計します。クラッシュで最終結果が0の場合は、ストリーミング入力モードならクラッシュ直前のターンの結果(その時点の累計)を使います。単発呼び出しや初回ターンでのクラッシュなど、使える結果がない場合は、アシスタントメッセージの usage をレスポンスごとに1回ずつ足せばメインループの入力・キャッシュトークンは復元できます。サブエージェント分、出力トークン、USD のコストはこの方法では復元できません。

キャッシュトークンと1時間 TTL

Agent SDK はプロンプトキャッシュを自動で使うので、自分で設定する必要はありません。usage には次の2つのフィールドがあり、input_tokens とは分けて追うとキャッシュの効果がわかります。

API キー認証や Amazon Bedrock などのクラウド経由で使う場合、自分のターンのキャッシュは既定で5分の TTL です。同じシステムプロンプトとコンテキストで短いセッションを5分以上の間隔で繰り返すと、毎回キャッシュが切れて入力が定価になります。その場合は環境変数 ENABLE_PROMPT_CACHING_1H を設定すると、キャッシュ書き込みに1時間の TTL を要求できます(シェルで export するか options.env で渡します)。1時間 TTL の書き込みは5分の書き込みより単価が高いので、書き込みコストと読み取りの増加を比べて判断します。

メイン会話とそれ以外(サブエージェントなど)で TTL を分けたい場合は、CLAUDE_CODE_PROMPT_CACHE_TTL(または設定 promptCacheTtl)と CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL(または設定 subagentPromptCacheTtl)に 5m か 1h を指定します。これらは ENABLE_PROMPT_CACHING_1H より優先されます。

本ページは Track cost and usage の内容に基づく非公式の日本語解説で、結果メッセージのフィールドとサブタイプ、maxBudgetUsd の説明は TypeScript SDK リファレンスの SDKResultMessage と照合しています(確認日 2026-10-22)。フィールド名・コードは公式表記のまま記載し、コード例は一部コメントを日本語にして短くしています。最新の仕様は公式ドキュメントを確認してください。