このページの目次

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

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)を実行している。 多くの場合、自分で実装する必要はない。より積極的、またはより抑制的に行う場合にのみカスタマイズせよ。指数関数的待避 + ジッター(jitter)を設定し、上限を設けよ。

タイムアウトもリトライ対象となるため、最悪の場合の壁時計時間は 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 を使い、半額で処理せよ。 結果は custom_id で対応付け、順序に依存するな。
  • 長い入力/出力はすべてストリーミングせよ。 TTFTを改善し、非ストリーミングのHTTPタイムアウトを回避。完全な結果は get_final_message で取得。
  • リトライは 429/5xx のみに。4xx はリトライしない。 SDKの組み込み待避を優先し、車輪の再発明を避ける。
  • 副作用のあるアクションは冪等性 + 人間の確認を設けよ。 待避リトライ × 副作用 = 重複実行。冪等キー + human-in-the-loop で対応。
  • コスト/レイテンシを監視に組み込め。 usage の3つのフィールド、stop_reason の分布、キャッシュヒット率をダッシュボードに反映(評価と観測参照)。

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

  • キャッシュの静かな無効化: system にタイムスタンプを挿入したり途中でモデルを切り替えたりすると、cache_read が長期間 0 になり、コストが数倍に跳ね上がる → プレフィックスを固定し、バイト単位で diff を取り調査。
  • 無闇な最高スペックモデル: 単純なタスクでも Opus + max を使うと、高くつき、遅くなる → タスクごとに階層化し、effort をスキャン。
  • 大きな max_tokens の非ストリーミング: HTTPタイムアウト、接続が引きずられて切断される → ストリーミング + get_final_message を使用。
  • 4xx のリトライ: 同じリクエストを再送してもエラーになり、クォータが無駄に消費される → 429/5xx のみリトライ。
  • リトライによる副作用: タイムアウトで再送し、メールが2通送られる → 冪等キー + 危険なアクションの人間の確認。
  • 順序に依存した帰属: Batches/並列の順序不同により、結果が混同される → 一律 custom_id/trace id で対応付け。
  • Claude のコスト見積もりに tiktoken を使用: 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 はリトライしない