このページの目次

コスト、パフォーマンス、信頼性

LLMを生産環境に組み込む際、避けられない3つの課題がある。それは「請求(トークンをどう節約するか)」「レイテンシ(どう遅延を防ぐか)」「安定性(レートリミットとエラーをどう処理するか)だ」。これらは以前の投稿で散発的に触れられていたが、今回はそれらを1つのエンジニアリングチェックリストに集約する。

概要

プロトタイプが動作し、本番環境へデプロイするまでの間には、エンジニアリングの壁がある。デモでは messages.create を1回呼び出して結果を得られれば十分だが、本番環境では以下の質問に答える必要がある。この呼び出し1万回でいくらかかるのか? ユーザーは何秒待つ必要があるのか? 429エラー(レートリミット超過)に遭遇したらどうするか? リトライによってメールが2通送られてしまうことはないか?

これらの答えは以前の投稿に散らばっている。プロンプトキャッシングストリーミングと思考usage フィールド能力に応じたモデル選択などだ。今回はこれらをコスト / パフォーマンス / 信頼性の3つの軸で整理し、本番デプロイ前のチェックリストとする。

コスト: トークンは唯一の課金単位

請求額 = 入力トークン数 × 単価 + 出力トークン数 × 単価(出力は通常5倍高額)。コスト削減のレバレッジは、効果の大きい順に以下の通り。

1. プロンプトキャッシング —— 最大のレバレッジ

マルチターンやエージェントのループ処理では、プロンプトのプレフィックス部分が高度に重複する。キャッシュにヒットした部分は ~0.1x のレートで課金される(コンテキストエンジニアリング参照)。計算してみよう(書き込み時のTTLが5分の場合、レートは1.25x)。

  • キャッシュなし: 毎回 1.0x。
  • キャッシュあり: 初回書き込みで 1.25x、以降の読み取りで 0.1x。2回目の呼び出しで元が取れる⁠(1.25 + 0.1 = 1.35 < 2.0)。TTLが1時間の書き込みは 2x となり、元を取るには3回必要。

前提条件として、プレフィックスがバイト単位で安定していることが重要だ。system プロンプトに now() や UUID を挿入したり、途中でツールセットを変更したりモデルを切り替えたりすると、キャッシュは静かに無効化される。usage.cache_read_input_tokens を確認して、長期間 0 のままになっている場合は「静かな無効化要因」が存在する可能性がある。

2. モデルの適切な選択

すべてのタスクに最高性能のモデルが必要ではない。Models API を使って能力を確認し、コストに応じて層分けする。単純な分類や抽出には安価なモデルを使い、複雑な推論やエージェント処理には Opus を使う。マルチエージェントのシナリオでは、サブタスクを安価なモデルに委譲する(ただし、⁠同じループ内でモデルを切り替えるとキャッシュが無効化されるため注意。別のサブエージェントとして独立させること。マルチエージェントオーケストレーション参照)。

3. 出力と思考の制御

出力は入力より高額だ。max_tokens は切り捨てを防ぐために十分な値を設定するが、無闇に最大値にする必要はない。effort を使って思考の深さを調整する(推論と思考参照)。effort が低いほどトークン消費が抑えられるため、単純なタスクで xhigh のような高い思考レベルを反射的に使用しないこと。

4. バッチ処理 —— リアルタイムでないタスクは半額

即時レスポンスが不要なタスク(オフライン分析、バッチ分類、インデックス作成など)には Batches API を使用する。すべてのトークンが50%オフになる。

batch = client.messages.batches.create(requests=[
    Request(custom_id="job-1", params=MessageCreateParamsNonStreaming(
        model="claude-opus-4-8", max_tokens=1024,
        messages=[{"role": "user", "content": "..."}])),
    # ... 最大10万件 / 256MB
])
# batches.retrieve(id).processing_status が "ended" になるまでポーリング(通常1時間以内、最大24時間)

結果は順序不同で返ってくるため、custom_id で正しく対応付ける必要がある。順序に依存してはならない。 これは後述の「順序に依存した帰属分析を行ってはならない」という鉄則と同じだ。

見積もりを先に行う

リクエストを送信する前に、count_tokens(ステートレス、トークンとサンプリング参照)を使って入力量を推計し、単価を掛けてコストを予測できる。Claude の見積もりに tiktoken を使用しないでほしい。 15〜20% 少なく見積もってしまう可能性がある。

パフォーマンス: レイテンシはどこから来て、どう抑えるか

レイテンシは主に出力長の関数(トークンごとに逐次デコード)+ 思考のオーバーヘッド + ネットワーク遅延による。

  • ストリーミングがデフォルト。 長い入力 / 長い出力 / 大きな max_tokens の場合はすべてストリーミングを使用する。最初のトークンが返り始めると、ユーザー体験(TTFT)が改善されるだけでなく、⁠非ストリーミングリクエストのHTTPタイムアウトを回避できる⁠(SDKは、大きな max_tokens の非ストリーミングリクエストに対して、10分以上接続を引きずって切断される可能性があるため、直接拒否する)。完全な結果が必要な場合は .get_final_message() / .finalMessage() を使用する。
  • effort とターン数は非線形。 エージェント的なタスクでは、高い effort がターン数を減らし⁠、結果としてより速く、よりコスト効率が良くなることが多い。「低い effort なら必ず速い」とは限らない。独自の評価データセットでスキャンして確認すること(評価と観測参照)。
  • キャッシングもレイテンシのレバレッジ。 キャッシュにヒットしたプレフィックスは再計算をスキップするため、初回のリクエストが遅くなる場合は、⁠ウォームアップ⁠(起動時に max_tokens: 0 のリクエストを送ってプレフィックスをキャッシュに書き込む)を行うことで解消できる。
  • 並列処理にはキャッシングのタイミングに関する罠がある。 N個の同じプレフィックスを持つリクエストを同時に送信すると、どのリクエストも他のリクエストがまだ書き込んでいるキャッシュを読み取ることができず、すべてフル価格で課金される。1つを先に送り、ストリーミングが開始されるのを待ってから、残りの N-1 を送信すれば、後続のリクエストはキャッシュにヒットする。

信頼性: レートリミット、リトライ、冪等性

エラーコードとリトライの可否

コード意味リトライ可否
400不正なリクエスト(パラメータ/フォーマット)❌ リクエストを修正
401 / 403認証/権限
404モデルIDまたはエンドポイントの間違い
429レートリミット超過(RPM/TPM/TPD)✅ 待避後にリトライ
500 / 529サーバーエラー/過負荷✅ 待避後にリトライ

鉄則:4xx(429を除く)はリトライしない⁠。同じリクエストを再送しても同じエラーになる。429/5xx のみ待避してリトライする。

待避とSDKの組み込みリトライ

429レスポンスには retry-after ヘッダーが含まれている(何秒待つべきか示す)。SDKはデフォルトで 408/409/429/5xx に対して指数関数的待避リトライ(max_retries=2)を実装している⁠——多くの場合、自分で実装する必要はない。より積極的、またはより慎重な挙動が必要な場合にのみカスタマイズする。指数関数的待避+ジッター(ランダム化)を設定し、上限を設ける。

タイムアウトもリトライ対象となるため、最悪の場合の壁時計時間は timeout × (max_retries+1) になる可能性がある。timeout を設定する際はこれを考慮に入れること。

冪等性: リトライで副作用が2回実行されないようにする

待避リトライは、⁠副作用を持つツール⁠(メール送信、課金、git push など)に対して危険である。実際には最初の呼び出しが成功していたが、レスポンスのタイムアウトにより、リトライによって2通のメールが送られてしまう可能性がある。この対策はモデル側ではなく、ホスト側で講じる。

  • 危険/不可逆なアクションは human-in-the-loop による確認を行う(エージェントループ参照)。
  • 副作用を伴う操作には冪等キーを使用する。同じ論理アクションには同じキーを使い、サーバー側で重複を排除する。
  • 「実行を決定する」と「実際に実行する」を分離する。モデルが意図を出力し、ホスト側で検証+冪等実行を行う。

帰属分析: 並列/バッチ結果を順序に依存してはならない

並列リクエストやBatchesの結果は順序不同で返されることがある。custom_id や trace id を使って結果をリクエストに対応付ける(評価と観測参照)。⁠到着順序に依存してはならない⁠——これはバッチシナリオで最も一般的なバグの原因だ。

ベストプラクティス

  • プロンプトキャッシングを優先し、プレフィックスを固定する。 安定したプレフィックスを前に置き、動的な部分を後ろに配置。2回目の呼び出しで元が取れる。cache_read_input_tokens を監視してヒットを確認する。
  • コストに応じてモデルを層別化する。 単純なタスクには安価なモデル、複雑なタスクには Opus を使用。Models API で能力を確認し、推測しない。
  • リアルタイムでないタスクには Batches API を使用し、半額で処理する。 結果は custom_id で対応付け、順序に依存しない。
  • 長い入力/出力はすべてストリーミングを使用する。 TTFTの改善と非ストリーミングHTTPタイムアウトの回避。完全な結果は get_final_message で取得。
  • リトライは 429/5xx のみに限定し、4xx はリトライしない。 SDKの組み込み待避を優先し、再発明しない。
  • 副作用を伴うアクションは冪等性+手動確認を行う。 待避リトライ × 副作用 = 重複実行。冪等キーと human-in-the-loop で対応。
  • コスト/レイテンシをモニタリングに組み込む。 usage の3つのフィールド、stop_reason の分布、キャッシュヒット率をダッシュボードに反映(評価と観測参照)。

トレードオフと失敗パターン

  • キャッシュの静かな無効化: システムプロンプトにタイムスタンプを挿入したり、途中でモデルを切り替えたりすると、cache_read が長期間 0 となり、コストが数倍に跳ね上がる → プレフィックスを固定し、バイト単位で差分を調べて原因を特定。
  • 無闇な最高性能モデルの使用: 単純なタスクでも Opus + max を使用すると、高額で遅い → タスクごとに層別化し、effort をスキャン。
  • 大きな max_tokens の非ストリーミング: HTTPタイムアウト、接続の強制切断 → ストリーミング + get_final_message を使用。
  • 4xx のリトライ: 同じリクエストを再送してもエラーになり、クォータが無駄に消費される → 429/5xx のみリトライ。
  • リトライによる副作用: タイムアウトで再送され、メールが2通送られる → 冪等キー+危険なアクションの手動確認。
  • 順序依存の帰属分析: Batches/並列処理の順序不同により、結果が混線する → 一律 custom_id/trace id で対応付け。
  • tiktoken による Claude コストの見積もり: 15〜20% 低く見積もってしまう → count_tokens を使用。

参考

Keywords: コスト最適化, トークン課金, プロンプトキャッシングの経済学, cache_read_input_tokens, Batches API, custom_id, 順序不同帰属分析, 50%割引, count_tokens, モデル選択, Models API, effort, ストリーミング, TTFT, HTTPタイムアウト, キャッシュウォームアップ, max_tokens 0, レートリミット, 429, retry-after, 指数関数的待避, exponential backoff, ジッター, max_retries, 冪等性, idempotency, human-in-the-loop, エラーコード, 4xx はリトライしない