【Claude Code 連載 第92回】コストと使用状況を追跡する(agent-sdk/cost-tracking)

スポンサーリンク
【Claude Code 連載 第92回】コストと使用状況を追跡する(agent-sdk/cost-tracking) 用語解説
【Claude Code 連載 第92回】コストと使用状況を追跡する(agent-sdk/cost-tracking)
この記事は約31分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

スポンサーリンク

背景

この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。

公式ドキュメント:https://code.claude.com/docs/ja/agent-sdk/cost-tracking

1. 一言でいうと

Agent SDK は、エージェントとのやり取りごとに、どれだけのトークンを使い、いくらくらいかかったかの情報を返します。このページは、その数字をどこから読み、どう集計すれば二重に数えたり数え漏らしたりせずに済むかを説明しています。プロンプトキャッシュの保持時間(TTL)の設定も扱っています。

最初に押さえておくべき大前提があります。SDK が返す費用(total_cost_usd と costUSD)は、手元で計算した推定値であって、請求額ではありません。 開発中の目安や、おおまかな予算づくりに使うものです。正式な請求額は、Claude API の「使用状況とコスト API」か、Claude Console の使用状況のページで確かめます。原文は、これらのフィールドの値でエンドユーザーに請求したり、お金に関わる判断を自動で下したりしないよう、はっきり書いています。


2. どういう場面で役立つか

シーン1:開発中に、どの処理が高くついているかを知る

エージェントの処理を組み立てている途中で、1回の問い合わせにどれくらいかかるのか、どのモデルがトークンを多く使っているのかを確かめます。サブエージェントに安いモデルを使わせたら全体の費用がどう変わったか、といった比較にも使えます。

シーン2:社内の利用を部署ごとにおおまかに集計する

社内向けのエージェントの利用状況を、部署やプロジェクトごとにおおまかに把握します。原文が言う「おおまかな予算づくり」にあたります。

シーン3:キャッシュが効いているかを確かめる

同じシステムプロンプトで何度も問い合わせるエージェントで、キャッシュから読んだトークンがどれだけあるかを見て、キャッシュの設定が効いているかを判断します(第86回で触れた確認方法です)。

不要・向かないケース

  • エンドユーザーへの請求に使う:推定値なので使ってはいけません。請求は「使用状況とコスト API」などの正式なデータで行います。
  • 支出の上限を守らせたいだけ:集計するより、maxBudgetUsd で上限を設定するほうが確実です(第89回)。ただしこれも推定値に基づく上限です(3-3)。

3. コードと仕組みの解説

原文の fenced コードブロックは8本(対訳3組と、TypeScript だけの例2本)です。全数を引用します。コードは原文から機械的に抜き出したもので、入れ子に由来する先頭インデントを外した以外は変えていません。

3-0. バージョン要件

挙動必要バージョン
データレジデンシーの価格(inference_geo: "us" で1.1倍)を推定に反映するTypeScript Agent SDK v0.3.239 以降/Python Agent SDK v0.2.144 以降
modelUsage の各項目に costBasis(どの価格表で計算したか)が付くClaude Code v2.1.246 以降
Python で会話のリセットを示す ConversationResetMessage を受け取れる(それ以前は Python のイテレーターが捨てていた)Python Agent SDK v0.2.137 以降
セッションを再開したとき、それまでの費用の合計が引き継がれる(それ以前は、SDK や claude -p で再開したセッションは合計がゼロから始まった)Claude Code v2.1.277 以降

3-1. 推定値であることの意味

total_cost_usd と costUSD は、SDK が手元で計算したものです。管理された価格表(modelPricing の設定)が有効でない限り、SDK のビルド時に同梱された価格表で計算しています。そのため、次の場合に実際の請求額とずれることがあります。

  • 価格が変わった
  • インストールしている SDK のバージョンが、そのモデルを知らない
  • クライアント側では表現できない請求の規則が当てはまる

SDK が表現している請求の規則の1つが、データレジデンシーの価格です。応答の usage が inference_geo: "us" を報告している場合、SDK はその応答のトークンの定価に1.1を掛けます。Web 検索のような、リクエストごとの料金には掛けません。

古い SDK を使い続けると、価格の改定や新しいモデルが推定に反映されません。費用の推定を見るなら、SDK を更新しておくことが前提になります(筆者の指摘)。

3-2. 使用量の数え方の単位

コストを正しく追うには、使用量がどの範囲を数えたものかを理解する必要があります。原文は3つの単位を挙げています。

単位意味
query() 呼び出しquery() 関数を1回呼ぶこと。その中に複数のステップがありうる(Claude が答え、ツールを使い、結果を受け取り、また答える)。各呼び出しは最後に result メッセージを1つ出す。ただしストリーミング入力では、1回の呼び出しで複数のユーザーのターンを回し、ターンごとに result が出る
ステップquery() 呼び出しの中の、1回のリクエストと応答の組。各ステップは、トークンの使用量を持つアシスタントメッセージを生む
セッションresume オプションでつながった一連の query() 呼び出し。セッションを再開した呼び出しの結果は、その呼び出しの分だけでなく、セッション全体の支出を報告する

使用量のデータの置き場所は言語で違います。数えている中身は同じです。

何を見るかTypeScriptPython
ステップごとの使用量各アシスタントメッセージの message.message.usage(ID は message.message.id)各アシスタントメッセージの message.usage(ID は message.message_id)
モデルごとの内訳結果メッセージの modelUsage結果メッセージの model_usage
推定費用の合計結果メッセージの total_cost_usd結果メッセージの total_cost_usd

1回の query() 呼び出しの流れは次のとおりです。

  1. 各ステップがアシスタントメッセージを出す。 TypeScript では、各アシスタントメッセージに入れ子の BetaMessage(message.message で届く)があり、そこに id と、トークン数(input_tokens、output_tokens)を持つ usage が入っています。Python では AssistantMessage が message.usage と message.message_id で同じデータを直接持っています。Claude が1つのターンで複数のツールを使うと、そのターンのメッセージは全て同じ ID を共有するので、二重に数えないよう ID で重複を除きます(第77回の「1応答=1メッセージではない」)。
  2. 結果メッセージが累計の推定値を出す。 query() 呼び出しが終わると、SDK は total_cost_usd と累計の usage を持つ結果メッセージを出します。推定の合計だけが要るなら、ステップごとの使用量は無視して、この1つの値を読めば済みます。

独立した query() 呼び出しを何回も行う場合、各結果はその呼び出しの費用だけを表します。セッションを再開した呼び出しは、セッションのそれまでの支出も含みます。

3-3. ストリーミング入力での数え方

ストリーミング入力(第91回)では、1回の query() 呼び出しで複数のユーザーのターンを回し、ターンごとに結果メッセージが出ます。結果のフィールドによって、数えている範囲が違います。

フィールド範囲
usageそのターンだけ。しかもメインのエージェントのループだけで、動かしたサブエージェントは含まない
total_cost_usd、modelUsage(Python は model_usage)その呼び出しのここまでの累計。セッションを再開した呼び出しでは、復元されたそれまでの支出も含む

アプリが /clear・/reset・/new のどれも送らない呼び出しでは、結果を全部足すのではなく、最新の結果を読めば呼び出しの合計になります。足すと二重に数えます。

累計がリセットされるのは、アプリがこの3つのコマンドのどれかを送ったときだけで、query() 呼び出しの中でほかに累計をリセットするものはありません。会計の上で大事な結果は3つです。

結果中身
/clear のターン自身の結果リセット以降に動いた分だけを数える。新しい session_id を持つ
その後の全ての結果そのリセットから数え続ける
各 /clear の直前の最後の結果前のリセット以降のターンの合計を持つ

呼び出し全体の合計を出すには、各 /clear の直前の最後の結果を、呼び出しの最後の結果に足します。それ以外の結果(/clear のターン自身の結果も含む)は、後の結果に置き換わるので数えません。

リセットはストリームから検出できます。TypeScript では SDK がリセットのたびに SDKConversationResetMessage を出し、Python では同様に ConversationResetMessage を出します。ただし Python SDK v0.2.137 より前は Python のイテレーターがこのメッセージを捨てていたので、その版ではアプリが送った /clear のターンを自分で数えます。

支出の上限との関係。 maxBudgetUsd(Python は max_budget_usd)は、その呼び出し自身の支出だけを数えます。再開したセッションから復元された合計は数えず、/clear を送ると予算もリセットされます。

最後の点は意外に効いてきます。ストリーミング入力で /clear を繰り返し送るアプリでは、1回の呼び出しの中で予算が何度もリセットされるので、maxBudgetUsd は呼び出し全体の上限にはなりません。呼び出し全体で上限を守らせたいなら、アプリ側でも上の方法で合計を数えておく必要があります(原文の記述からの筆者の指摘)。

3-4. 1回の問い合わせの総額を取る

結果メッセージ(TypeScript は SDKResultMessage、Python は ResultMessage)は、query() 呼び出しのエージェントのループの終わりを示し、その呼び出しの全ステップの累計の推定費用 total_cost_usd を持っています。セッションを再開する呼び出しは、セッションのそれまでの支出も含みます。読むときの注意は2つです。

  • Python ではこのフィールドは省略可能な型なので、読む前に None でないことを確かめる。
  • 成功の結果にもエラーの結果にも入っているが、セッションのクラッシュ後の最後の結果ではゼロになりうる(3-7)。

サブエージェントの扱いは、3つのフィールドで違います。 ツリー全体のトークンを数えるなら modelUsage(Python は model_usage)を使います。usage は、入れ子が起きた途端に少なく数えます。

フィールドサブエージェントの分
usage含まない。最上位のエージェントのループだけを数える
total_cost_usd含む。最上位のループに加えてサブエージェントのリクエストも数える
modelUsage/model_usage含む。サブエージェントのリクエストも数え、モデル別に分ける

第77回で「usage はメインのループだけ、ツリー全体は model_usage」と見たことの、費用の面からの確認です。

シングルメッセージ入力で、最後のターンが終わった時点でバックグラウンドのサブエージェントがまだ動いている場合、Claude Code は結果を出す前に、それらを待ちます(待つ上限は第75回で見た「終了時のバックグラウンドタスク」の規則)。結果の total_cost_usd、duration_api_ms、modelUsage(Python は model_usage)には、その待ち時間中の作業も含まれます。第89回で見たとおりサブエージェントは既定でバックグラウンドで動くので、この待ちは珍しくありません。

次の例は、query() 呼び出しのメッセージを順に見て、result メッセージが届いたら総額を表示します。

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) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, it still carried total_cost_usd and the
  // branch above has already run; connection or process failures yield
  // no result message.
  console.error(`Session ended with an error: ${error}`);
}
from claude_agent_sdk import query, ResultMessage
import asyncio


async def main():
    try:
        async for message in query(prompt="Summarize this project"):
            if isinstance(message, ResultMessage):
                print(f"Total cost: ${message.total_cost_usd or 0}")
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the branch above has already run;
        # connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

Python 版は message.total_cost_usd or 0 で、値が None のときに0を表示しています。上の「Python では None を確かめる」の具体的な書き方です。コメントにあるとおり、エラーの結果も total_cost_usd を持っているので、例外が投げられる前に表示されています。接続やプロセスの失敗では結果メッセージ自体が出ません。

サブエージェントが total_cost_usd に足せる額を抑えるには、第89回で見た深さ・同時実行数・支出の上限を設定します。

3-5. ステップごと・モデルごとに見る

この節の例は TypeScript のフィールド名を使っています。Python では、ステップごとの使用量は AssistantMessage.usage と AssistantMessage.message_id、モデルごとの内訳は ResultMessage.model_usage が対応します。

ステップごとの使用量

各アシスタントメッセージの入れ子の BetaMessage(message.message)に、id と、トークン数を持つ usage があります。Claude がツールを並行で使うと、複数のメッセージが同じ id と同じ使用量を持つので、数えた ID を記録し、重複を飛ばします。

ここに大事な注意があります。重複を除いたステップごとの値は、入力トークンとキャッシュのトークンについては正確ですが、ステップごとの output_tokens は仮の値(プレースホルダー)です。 出力トークンは結果メッセージから読みます(3-7)。

次の例は、全てのステップで入力トークンを積み上げ、メインのループのメッセージ ID をそれぞれ1回だけ数え、サブエージェントのメッセージは飛ばし、出力トークンの合計はメインのループを数える結果メッセージから読みます。

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;

      // Parallel tool calls share the same ID, only count once
      if (!seenIds.has(msgId)) {
        seenIds.add(msgId);
        totalInputTokens += message.message.usage.input_tokens;
      }
    }
    if (message.type === "result") {
      // Per-step output_tokens is a placeholder; the result message
      // carries the accumulated output total.
      resultOutputTokens = message.usage.output_tokens;
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result, so the
  // input total below still reflects the steps that ran before the failure.
  console.error(`Session ended with an error: ${error}`);
}

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

!message.parent_tool_use_id でサブエージェントの中のメッセージを除いています(第89回で見た parent_tool_use_id)。出力トークンは結果メッセージの usage.output_tokens から取っていますが、3-4 の表のとおり結果の usage もメインのループだけを数えるので、この例の数字はどちらもサブエージェントを含まないことに注意が要ります。ツリー全体を見たいなら次のモデルごとの内訳を使います(筆者の補足)。

モデルごとの内訳

結果メッセージの modelUsage は、モデル名から、そのモデルのトークン数と費用への対応表です。複数のモデルを使う場合(たとえばサブエージェントに Haiku、メインに Opus)に、どこでトークンが使われているかを見るのに便利です。

各項目の costBasis は、そのモデルの最新のリクエストにどの価格表で値段を付けたかを示します(v2.1.246 以降)。

costBasis の値意味
list定価
managedmodelPricing の価格表
unknownどちらもモデル ID に一致しなかった

unknown は、3-1 で見た「SDK がそのモデルを知らない」場合にあたり、その費用の推定は当てにできないと読めます(筆者の読み)。

次の例は問い合わせを実行し、使ったモデルごとに費用とトークンの内訳を表示します。

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

try {
  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}`);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the per-model breakdown above has already
  // printed; connection or process failures yield no result message.
  console.error(`Session ended with an error: ${error}`);
}

modelUsage の中のフィールド名は costUSD・inputTokens・outputTokens・cacheReadInputTokens・cacheCreationInputTokens と camelCase です。ステップごとの usage の input_tokens・cache_read_input_tokens などの snake_case とは書き方が違うので、両方を扱うコードでは取り違えに注意が要ります(筆者の指摘)。

3-6. 複数の呼び出しの費用を足し上げる

各 query() 呼び出しは結果に total_cost_usd を返します。どう組み合わせるかは、呼び出しがセッションを共有しているかで決まります。

呼び出しの関係合計の出し方
resume も continue も使わない独立した呼び出し各結果はその呼び出しの分だけなので、自分で足す(下の例)
同じセッションを再開する呼び出しClaude Code はプロセスが正常に終わるときにセッションの合計をトランスクリプトに保存し、後の呼び出しがセッションを再開・fork するときに復元する。各結果はすでにそれまでの支出を含むので、最新の結果を読む。足すと復元された支出を二重に数える

v2.1.277 より前は、SDK や claude -p で再開したセッションは合計がゼロから始まり、各呼び出しの結果はその呼び出しの分だけでした。つまり、同じ集計のコードでも、Claude Code のバージョンによって正しい書き方が逆になります。古い版では足すのが正しく、新しい版では足すと二重になります。集計のコードを書くときは、SDK が同梱している Claude Code のバージョンを確かめておく必要があります(原文の記述からの筆者の指摘)。

どちらの場合も、足した値はクライアント側の推定値であることに変わりはありません。ストリーミング入力では 3-3 の方法で、セッションのクラッシュで終わった呼び出しは 3-7 の方法で合計を読みます。

次の例は、2つの query() 呼び出しを順に実行し、各呼び出しの total_cost_usd を合計に足して、呼び出しごとの額と合計の両方を表示します。

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

// Track cumulative cost across multiple query() calls
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) {
    // A single-shot query() throws after yielding an error result. If the
    // failure was an error result, this call's cost was already counted;
    // connection or process failures yield no result message. Continue
    // with the next prompt.
    console.error(`Call failed: ${error}`);
  }
}

console.log(`Total spend: $${totalSpend.toFixed(4)}`);
from claude_agent_sdk import query, ResultMessage
import asyncio


async def main():
    # Track cumulative cost across multiple query() calls
    total_spend = 0.0

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

    for prompt in prompts:
        try:
            async for message in query(prompt=prompt):
                if isinstance(message, ResultMessage):
                    cost = message.total_cost_usd or 0
                    total_spend += cost
                    print(f"This call: ${cost}")
        except Exception as error:
            # A single-shot query() raises after yielding an error result. If
            # the failure was an error result, this call's cost was already
            # counted; connection or process failures yield no result message.
            # Continue with the next prompt.
            print(f"Call failed: {error}")

    print(f"Total spend: ${total_spend:.4f}")


asyncio.run(main())

この例はどちらの呼び出しも resume や continue を使っていない、独立した呼び出しなので、足すのが正しい場面です。コメントのとおり、失敗した呼び出しもエラーの結果に費用を持っているので、その分も合計に入っています。

3-7. エラー・キャッシュ・出力トークン

正確に追うには、アシスタントメッセージの仮の出力トークン数、失敗した会話が使ったトークン、キャッシュのトークンの値段を考える必要があります。

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

Claude Code は、応答が始まったときに API が報告した使用量からアシスタントメッセージを作ります。そのため、メッセージの output_tokens は、応答が生成される前に API が message_start で報告した数だけです。1つの API の応答から複数のアシスタントメッセージが生まれ、それぞれが同じ仮の値を持ちます。

API は応答の終わりに実際の出力トークン数を報告し、Claude Code はそれを結果メッセージに入れます。出力トークンは、結果の usage から、またはモデルごとの詳しい内訳なら modelUsage から読みます。

応答の生成中に出力トークン数が増えていく様子を見たいなら、includePartialMessages(Python は include_partial_messages)を設定し、各 message_delta のストリームイベントから usage を読みます。型は TypeScript では SDKPartialAssistantMessage、Python では StreamEvent です。

失敗した会話の費用

成功の結果にもエラーの結果にも usage と total_cost_usd があります。Python では両方とも省略可能な型なので、None でないことを確かめてから読みます。

会話が途中で失敗しても、失敗した時点までのトークンは使っています。subtype が success でもエラーのどれかでも、全ての結果メッセージから費用を読みます。ただし一部のエラーの結果では、usage が実際より少なく報告されます。

エラー少なく出るもの
セッションのクラッシュ後の error_during_execution全ての費用のフィールドがゼロになりうる
error_max_budget_usdusage は予算を超えた応答を除くが、total_cost_usd と modelUsage はそれを含む

選べるなら、usage ではなく total_cost_usd か modelUsage で計上します。

セッションのクラッシュ後に合計を取り戻す

Claude Code のプロセスがクラッシュすると、最後に error_during_execution の結果を出して終わります(シングルメッセージ入力でもストリーミング入力でも同じ)。その結果の usage・total_cost_usd・modelUsage はゼロになりうるので、それより前に届いたものから呼び出しの合計を取り戻します。

  1. クラッシュの前のターンの結果を使う。 ストリーミング入力では、3-3 で見た累計を持っています。ただし次の場合は役に立たないので、2に進みます。
    • 呼び出しがシングルメッセージ入力なので、前の結果が無い
    • クラッシュが最初のターンで起きた
    • クラッシュの前のターンが /clear 自身で、その結果はリセットの分しか数えていない
  2. アシスタントメッセージの usage を、API の応答ごとに1回ずつ足す(3-5 の「ステップごとの使用量」の例と同じやり方)。シングルメッセージ入力では全部を、ストリーミング入力では最後の結果より後に届いたものを足します。これで、メインのループの入力トークンとキャッシュのトークンが分かります。サブエージェントの使用量、出力トークン、費用(ドル)はこの方法では取り戻せません。ステップごとの output_tokens は仮の値だからです。

1の方法なら全部取り戻せるのに対し、2の方法では一部しか取り戻せません。ストリーミング入力で結果メッセージを毎回記録しておけば、クラッシュしても直前の累計が残る、ということです(筆者の整理)。

キャッシュのトークンを追う

Agent SDK は、繰り返し送る内容の費用を減らすために、プロンプトキャッシュを自動で使います。自分で設定する必要はありません。使用量のオブジェクトには、キャッシュを追うためのフィールドが2つあります。

フィールド意味
cache_creation_input_tokens新しいキャッシュの項目を作るのに使ったトークン。通常の入力トークンより高い単価
cache_read_input_tokens既存のキャッシュの項目から読んだトークン。安い単価

キャッシュでどれだけ節約できたかを知るには、これらを input_tokens とは分けて追います。TypeScript では Usage オブジェクトの型にあり、Python では ResultMessage.usage の辞書のキーとして現れます(例:message.usage.get("cache_read_input_tokens", 0))。

第86回では、プリセットのシステムプロンプトに作業ディレクトリなどが埋め込まれるとキャッシュを共有できない、と見ました。excludeDynamicSections を設定する前と後でこの2つの値を比べれば、効果を数字で確かめられます(第86回との組み合わせの提案は筆者)。

3-8. キャッシュの保持時間を1時間に延ばす

キャッシュには保持時間(TTL)があり、これを過ぎると消えます。保持時間は2つの「バケツ」で別々に決まります。

  • メインの会話のバケツ:自分のターンと、Claude Code がそれと一緒にその場で走らせる補助の処理
  • それ以外:Claude Code が会話の外で行うリクエスト(サブエージェントなど)。別の TTL の設定がある

自分のターンのキャッシュは、次の場合に既定で5分の TTL です。

  • API キーで認証している
  • Amazon Bedrock、Google Cloud の Agent Platform、Microsoft Foundry、Claude Platform on AWS で動かしている

同じシステムプロンプトと文脈で短いセッションをたくさん動かし、セッションとセッションの間が5分以上空く使い方では、キャッシュがセッションの間に切れ、新しいセッションはそのたびに入力の全額を払うことになります。

キャッシュの書き込みで1時間の TTL を求めるには、環境変数 ENABLE_PROMPT_CACHING_1H を設定します。シェルやコンテナの環境に書き出すか、options.env で渡します。

次の例は、Amazon Bedrock で動かすエージェントで1時間の TTL を有効にします。CLAUDE_CODE_USE_BEDROCK を設定しているので、Amazon Bedrock の有効な AWS の認証情報が必要で、それが無いと問い合わせは失敗します。

from claude_agent_sdk import ClaudeAgentOptions, query
import asyncio


async def main():
    options = ClaudeAgentOptions(
        env={
            "CLAUDE_CODE_USE_BEDROCK": "1",
            "ENABLE_PROMPT_CACHING_1H": "1",
        },
    )

    async for message in query(prompt="Summarize this project", options=options):
        print(message)


asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";

const options = {
  env: {
    ...process.env,
    CLAUDE_CODE_USE_BEDROCK: "1",
    ENABLE_PROMPT_CACHING_1H: "1",
  },
};

for await (const message of query({ prompt: "Summarize this project", options })) {
  console.log(message);
}

env の扱いは、これまでと同じく TypeScript が置き換え(...process.env を展開して残す)、Python が重ね合わせです(第79回)。

引き換えになるもの。 1時間の TTL でのキャッシュの書き込みは、5分の書き込みより高い単価です。有効にすると、書き込みが高くなる代わりに、キャッシュから読める回数が増えます。セッションの間隔が5分以内に収まる使い方なら、有効にしても書き込みが高くなるだけです(筆者の補足)。

サブスクリプションの場合。 Claude のサブスクリプションで、プランに含まれる使用量の範囲内なら、この変数を設定しなくても自分のターンは1時間の TTL になります。Claude Code が自分のターンを5分の TTL に下げるのは、使用クレジット(プランを超えた分の追加の利用)を使っているときです。

バケツごとに決める。 ENABLE_PROMPT_CACHING_1H は、両方のバケツの全てのリクエストに1時間の TTL を求めます。バケツごとに別々に決めたいなら、次の設定を使います。どちらも 5m か 1h を取り、ENABLE_PROMPT_CACHING_1H より優先されます。

バケツ環境変数設定
メインの会話CLAUDE_CODE_PROMPT_CACHE_TTLpromptCacheTtl
それ以外CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTLsubagentPromptCacheTtl

promptCacheTtl を 1h にすると、使用クレジットを使っているときでも、メインの会話で1時間のキャッシュを保ちます。全体の優先順位は、プロンプトキャッシュのページの「TTL を自分で選ぶ」に回されています。


4. まとめ

  • total_cost_usd と costUSD は手元で計算した推定値で、請求額ではない。同梱の価格表で計算するので、価格の改定や未知のモデルでずれる。請求には使用状況とコスト API か Claude Console を使い、エンドユーザーへの請求や財務判断に使わない。
  • 単位は呼び出し・ステップ・セッション。ステップごとの使用量は TypeScript が message.message.usage、Python が message.usage。モデルごとは modelUsage/model_usage。
  • 並行ツールのメッセージは同じ ID を共有するので、ID で重複を除く。ステップごとの output_tokens は仮の値なので、出力トークンは結果メッセージから読む。
  • usage はサブエージェントを含まない。ツリー全体は total_cost_usd か modelUsage。
  • ストリーミング入力では、usage はそのターンのメインのループだけ、total_cost_usd と modelUsage は累計。合計は最新の結果を読み、/clear・/reset・/new の直前の結果だけを足す。maxBudgetUsd は /clear でリセットされる。
  • 独立した呼び出しは足す。再開したセッションは最新の結果を読む(v2.1.277 以降。それより前は再開しても0から)。
  • エラーの結果も費用を持つ。error_max_budget_usd では usage が少なく出て、クラッシュ後の error_during_execution はゼロになりうる。選べるなら total_cost_usd か modelUsage で計上。クラッシュ後は直前の結果か、ステップの使用量から取り戻す。
  • キャッシュは自動。cache_creation_input_tokens(高い)と cache_read_input_tokens(安い)を分けて追う。
  • API キーやクラウド経由では自分のターンのキャッシュは既定5分。ENABLE_PROMPT_CACHING_1H で1時間にできるが書き込みは高くなる。バケツごとに CLAUDE_CODE_PROMPT_CACHE_TTL と CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL で決められる。

次回予告

次回は 「セッションの保存先」(agent-sdk/session-storage) を取り上げる予定です。

第87回では、セッションのファイルは作ったマシンにしか無く、別のマシンやサーバーレスの環境で再開するには SessionStore アダプターでトランスクリプトを共有ストレージに写す、と見ました。今回も、再開したセッションの費用の合計がトランスクリプトに保存されて引き継がれることを見ています。次回は、そのセッションの保存先を自分のバックエンドに切り替える仕組みを見ていきます。


よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました