AI最新ニュース 2026.04.21

Claude APIプロンプトキャッシュ完全ガイド【TTL・コスト削減・実装コード付き・2026年版】

タグ:Claude / API / コスト削減 / プロンプトキャッシュ / 開発費

プロンプトキャッシュとは

Claude APIは、同じコンテンツを繰り返し送る場合に「プロンプトキャッシュ」で処理コストを削減できます。例えば:

  • 長いシステムプロンプト(ルール定義・役割定義)を何度も使う
  • 大きなドキュメントを参照しながら複数の質問をする
  • チャットボットで同じコンテキストを持ちながら会話を続ける

これらのケースで、初回リクエスト後はキャッシュされたトークンを再利用することで、入力トークンコストが約90%削減されます。

キャッシュ料金の仕組み

項目料金(Sonnet系の例)
通常入力トークン$3.00 / 1M tokens
キャッシュ作成(初回)$3.75 / 1M tokens(通常の1.25倍)
キャッシュ読み込み(ヒット時)$0.30 / 1M tokens(通常の10%)

初回はやや高くなりますが、2回目以降は90%引きで利用できます。5分以内に十分なリクエストがあれば大幅なコスト削減になります。

基本的な実装方法

システムプロンプトをキャッシュする

import anthropic

client = anthropic.Anthropic()

# 長いシステムプロンプト(1024トークン以上推奨)
SYSTEM_PROMPT = """あなたは高度な技術サポートアシスタントです。

## 対応ルール
- 必ず日本語で回答する
- コード例を必ず含める
- エラーの原因と解決策を両方提示する
- 公式ドキュメントの参照先も明示する

## 対応技術スタック
Python / JavaScript / TypeScript / Go / Rust / Java / C++ / SQL /
Docker / Kubernetes / AWS / GCP / Azure / Terraform / Ansible /
React / Vue / Angular / Next.js / FastAPI / Django / Flask / Express ...
(長いプロンプトほどキャッシュ効果が高い)
"""

def chat_with_cache(user_message: str, conversation_history: list) -> str:
    """キャッシュを活用したチャット関数"""
    
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        system=[
            {
                "type": "text",
                "text": SYSTEM_PROMPT,
                "cache_control": {"type": "ephemeral"}  # ← キャッシュ指定
            }
        ],
        messages=conversation_history + [
            {"role": "user", "content": user_message}
        ]
    )
    
    # キャッシュヒット状況を確認
    usage = response.usage
    print(f"通常入力: {usage.input_tokens} tokens")
    print(f"キャッシュ作成: {getattr(usage, 'cache_creation_input_tokens', 0)} tokens")
    print(f"キャッシュ読込: {getattr(usage, 'cache_read_input_tokens', 0)} tokens")
    
    return response.content[0].text

# 使用例
history = []
reply1 = chat_with_cache("Pythonのリスト内包表記を教えて", history)
# 初回: キャッシュ作成(少し高い)

history.append({"role": "user", "content": "Pythonのリスト内包表記を教えて"})
history.append({"role": "assistant", "content": reply1})

reply2 = chat_with_cache("ジェネレーター式との違いは?", history)
# 2回目(5分以内): キャッシュヒット → 90%引き

大きなドキュメントをキャッシュする

def analyze_document(document_text: str, question: str) -> str:
    """長いドキュメントをキャッシュしながら複数の質問に回答"""
    
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "text",
                        "text": f"以下のドキュメントを参照して質問に答えてください:\n\n{document_text}",
                        "cache_control": {"type": "ephemeral"}  # ドキュメント部分をキャッシュ
                    },
                    {
                        "type": "text",
                        "text": f"\n\n質問:{question}"
                        # 質問部分はキャッシュしない(毎回変わるため)
                    }
                ]
            }
        ]
    )
    return response.content[0].text

# 同じドキュメントに対して複数の質問を5分以内に実行
with open("large_document.txt") as f:
    doc = f.read()

answer1 = analyze_document(doc, "第3章の主な結論は?")  # 初回: キャッシュ作成
answer2 = analyze_document(doc, "第5章で推奨されている手法は?")  # ヒット: 90%引き
answer3 = analyze_document(doc, "全体のまとめを200字で")  # ヒット: 90%引き

キャッシュヒット率を最大化するコツ

コツ1:静的部分と動的部分を分ける

# NG: ユーザー名がシステムプロンプトに入っている(毎回異なるためキャッシュ不可)
system_bad = f"あなたは{user.name}さん専属のアシスタントです。..."

# OK: 静的部分だけをキャッシュ、動的部分はメッセージに含める
system_good = "あなたは専門的なアシスタントです。..."  # ← キャッシュ対象
first_message = f"私は{user.name}です。以下の質問に答えてください。..."

コツ2:キャッシュブロックの位置を統一する

cache_control は必ずコンテンツの末尾に設定します。途中で変わる部分より前にあるコンテンツだけがキャッシュされます。

# 正しい設計:大きなブロックの末尾にキャッシュポイント
messages = [
    {
        "role": "user",
        "content": [
            {"type": "text", "text": large_context},
            {"type": "text", "text": "[キャッシュポイント]", "cache_control": {"type": "ephemeral"}},
            {"type": "text", "text": dynamic_question}  # キャッシュ後に追加
        ]
    }
]

TTL5分制限への対応:独自キャッシュレイヤー

バッチ処理など、リクエスト間隔が5分を超える場合は、Redisで結果をキャッシュします。

import redis
import hashlib
import json
import anthropic

client = anthropic.Anthropic()
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)

CACHE_TTL = 3600  # 1時間キャッシュ

def get_cache_key(prompt: str, model: str) -> str:
    """プロンプトのハッシュをキーにする"""
    content = f"{model}:{prompt}"
    return f"claude_cache:{hashlib.sha256(content.encode()).hexdigest()}"

def claude_with_redis_cache(prompt: str, system: str = "", model: str = "claude-sonnet-4-6") -> str:
    """Redis経由でClaudeレスポンスをキャッシュ"""
    cache_key = get_cache_key(f"{system}:{prompt}", model)
    
    # Redisから取得を試みる
    cached = redis_client.get(cache_key)
    if cached:
        print(f"Redis cache hit: {cache_key[:20]}...")
        return json.loads(cached)["response"]
    
    # キャッシュミス: APIを呼び出す
    response = client.messages.create(
        model=model,
        max_tokens=1024,
        system=system if system else None,
        messages=[{"role": "user", "content": prompt}]
    )
    result = response.content[0].text
    
    # Redisに保存
    redis_client.setex(
        cache_key,
        CACHE_TTL,
        json.dumps({"response": result, "model": model})
    )
    print(f"API call made, cached for {CACHE_TTL}s")
    return result

# 使用例
answer1 = claude_with_redis_cache("Pythonのfor文の使い方を教えて")
answer2 = claude_with_redis_cache("Pythonのfor文の使い方を教えて")  # Redisから返る

コスト削減効果の試算

月間100万リクエスト、平均システムプロンプト2000トークンの場合:

戦略月間入力コスト(Sonnet)削減率
キャッシュなし$6,000-
Anthropicキャッシュ(ヒット率70%)$2,610-56%
Anthropicキャッシュ(ヒット率90%)$1,140-81%
Redis + Anthropicキャッシュ$600〜-90%〜

あわせて読みたい

参考ソース