◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/API / SDK
◈ API / SDK/2026-04-19上級

Gemini API キャッシュ戦略の運用ノート — Context Caching と Implicit Caching を本番で組み合わせて月額費用を抑える

Context Caching と Implicit Caching を個人開発アプリの本番運用で組み合わせた実装メモ。動くコード・実測コスト・運用で気づいた7つの落とし穴を、AdMob 連携アプリの文脈で整理しています。

gemini-api287cachingcost-optimization22python106production105

✦ プレミアム記事

個人で iPhone / Android の壁紙・癒し系アプリを運営していると、月単位の API 費用は広告収益(主に AdMob)から直接引かれる経費になります。だから「Gemini API の月額費用」というテーマは、私にとっては書類上の最適化ではなく、夕食の予算を決めるのと同じ重さで向き合うものになっています。

きっかけはささやかなものでした。壁紙アプリに「画像の雰囲気から短い詩を生成する」機能を入れた最初の月、Cloud Billing のメールに $412 と書かれていて、AdMob の月次レポートを開きながら少し息を止めました。広告収益は黒字でしたが、Gemini に支払う API 費用が想定の 3 倍になっていて、原因の大半はシステムプロンプトとガイドラインを毎リクエストで送っていたことでした。Context Caching と Implicit Caching を真剣に学び始めたのはそこからです。

ここでは、その後 6 ヶ月の本番運用で身についた「キャッシュをコスト削減装置として動かす」設計を、動くコードと実測値で整理します。コードは、目に見えないところで費用と時間を節約してくれる道具だと考えてきました。Gemini API のキャッシュも、まさにその系譜にある仕組みだと感じています。

Gemini API の料金構造とキャッシュが効く理由

まず、なぜキャッシュがこれほど重要なのかを理解するために、Gemini API の料金構造を整理します。

料金は大きく「入力トークン」「出力トークン」「キャッシュ保存料」の3種類に分かれています。注目すべきは、入力トークンは毎回のリクエストで課金されるという点です。

たとえば、10,000 トークンのシステムプロンプトを持つカスタマーサポートボットが 1 日 1,000 回のリクエストを受ける場合、システムプロンプトだけで 1 日 1,000 万トークンが課金対象になります。1 ヶ月では 3 億トークン。これが gemini-2.5-pro の料金(〜200K トークンで $1.25 / 100 万)で計算すると、システムプロンプト分だけで月 $375 ほどかかる計算になります。

ここにキャッシュを適用すると話が変わります。

  • Context Caching: 大きなコンテンツを事前にキャッシュし、後続リクエストでそのキャッシュを参照します。キャッシュされたトークンはフル料金の約 75% 割引で請求されます
  • Implicit Caching: 同じプロンプトプレフィックスが繰り返されると、自動的に割引料金が適用されます(Gemini 2.5 以降のモデルで既定有効)

この 2 つを使い分けること、そして組み合わせることが、API 費用を根本から削減するカギです。

キャッシュが特に効果的なコンテンツ

どんなコンテンツでもキャッシュが効くわけではありません。効果が大きいのは「繰り返し使われる固定コンテキスト」です:

  • 長いシステムプロンプト(5,000 トークン超の仕様書・指示書)
  • 大きなドキュメント(製品マニュアル、API 仕様書、法律文書)
  • 画像・動画ファイル(マルチモーダルな参照コンテキスト)
  • コードベース全体(コードレビューや質問応答に使う参照コード)

逆に、毎回内容が変わるユーザーメッセージ部分はキャッシュの対象になりません。この「固定部分」と「変動部分」を意識してプロンプトを設計することが、キャッシュ効率を最大化する第一歩です。

私の壁紙アプリの場合は、「アプリの世界観・トーン・禁則語リスト・出力フォーマット指示」を合わせて約 12,400 トークンの固定コンテキストにまとめてあります。これがキャッシュ側、ユーザーメッセージとして送るのは「画像の特徴量と数行の指示」だけ。この設計に切り替えてから、後述の通り月額費用が $281 から $98 まで下がりました(いちばん低かった月は $96 です)。

キャッシュ以外の打ち手も含めた費用の下げ方は、Gemini API の月額5万円を8,400円まで下げた運用設計に書き残しています。

Context Caching の仕組みと実装

Context Caching は、コンテンツを明示的にキャッシュする API です。一度キャッシュを作成して ID を取得し、後続のリクエストでその ID を参照することで割引料金が適用されます。

Context Caching が使える条件(まずここを確認)

Context Caching にはいくつかの制限があります。実装前に必ず確認してください:

  • 最小トークン数: キャッシュ対象のコンテンツが、モデルごとに決められた最小トークン数を超えていること(この値は世代で変わります。次の項で表にしました)
  • API の系統: 明示的な Context Caching は generateContent 系の API で使えます。Interactions API は Implicit Caching のみの対応です(これも次の項で触れます)
  • キャッシュの有効期限: 既定は 1 時間。TTL または expire_time で伸ばせます

「最小トークン数」の条件は見落としがちで、これを知らないまま短いプロンプトをキャッシュしようとすると BadRequestError が返ります。

世代を上げた朝に、割引だけが消えていました

この記事を最初に書いたあと、壁紙アプリのタグ付けバッチを新しい世代の Flash に載せ替えました。翌朝の集計で、cached_content_token_count が 0 のまま返ってくる実行が並んでいます。コードは一行も触っていません。TTL を疑い、display_name を疑い、最後に気づいたのはキャッシュが効き始める最小トークン数がモデルの世代で変わっていたことでした。

公式の Context caching には、2026 年 9 月時点で次の値が載っています。

モデル最小トークン数
Gemini 3.8 Flash4,096
Gemini 3.7 Flash4,096
Gemini 3.6 Flash4,096
Gemini 3.5 Flash4,096
Gemini 3.1 Pro Preview4,096
Gemini 2.5 Flash2,048
Gemini 2.5 Pro2,048

2.5 世代で通っていた「2,048 トークンあれば効く」という前提は、3 系では通りません。やっかいなのは、しきい値を割ったプロンプトがエラーも警告も出さずに、ただ割引されないまま課金されることです。私のシステムプロンプトは 12,400 トークンあったので Context Caching 側は無傷でしたが、バッチで使っていた 3,100 トークンの短いプロンプトだけが Implicit Caching の対象から外れていて、月末の請求書で初めて分かりました。

しきい値はコードに書かず、モデル名から引ける表として持たせています。 定数として埋め込んだ 2,048 は、世代を上げた日から気づかれない値上げに変わります。

# モデルごとの最小トークン数(2026-09 時点・公式ドキュメント準拠)
IMPLICIT_CACHE_MIN_TOKENS = {
    "gemini-3.8-flash": 4096,
    "gemini-3.7-flash": 4096,
    "gemini-3.6-flash": 4096,
    "gemini-3.5-flash": 4096,
    "gemini-3.1-pro-preview": 4096,
    "gemini-2.5-flash": 2048,
    "gemini-2.5-pro": 2048,
}
 
# 表に無いモデルには「大きいほうの値」を既定にしておきます。
# 小さいほうを既定にすると、新しい世代が出た日から
# 割引されない請求が黙って混ざり始めます。
UNKNOWN_MODEL_FLOOR = 4096
 
 
def cache_floor(model_name: str) -> int:
    key = model_name.replace("models/", "")
    return IMPLICIT_CACHE_MIN_TOKENS.get(key, UNKNOWN_MODEL_FLOOR)
 
 
def prefix_reaches_floor(model_name: str, prefix_tokens: int) -> bool:
    """プロンプトの固定部分が、そのモデルの最小トークン数に届いているか"""
    return prefix_tokens >= cache_floor(model_name)

移行の前にこれを 1 回通しておけば、「載せ替えたのに安くならない」を請求書ではなく手元で見つけられます。私はモデル名を切り替える設定ファイルの隣にこの表を置き、prefix_reaches_floor() が False を返したらデプロイ前のチェックで落とすようにしました。届かないと分かったプロンプトは、固定部分に出力フォーマットの説明を足して意図的に 4,096 を超えさせています——短く保つことより、割引が効くほうが月末に効きます。

Interactions API では、明示キャッシュという選択肢が消えます

もう一つ、設計の分かれ目になる変化があります。Interactions API は Implicit Caching だけに対応していて、この記事で扱う明示的な Context Caching(cachedContents を自分で作って ID を参照する方式)は使えません。明示キャッシュでコスト削減を保証したいのであれば、その処理は generateContent 系の API に残す判断が要ります。

私のアプリでは、会話の状態をサーバーに預けたい機能だけを Interactions API に寄せ、システムプロンプトが重いタグ付けと詩の生成は generateContent 側に残しました。片方に全部を寄せる必要はありません。状態を預けたい処理と、割引を保証したい処理を、別の API に分けて置いています。

なお、以降のコードは google-generativeai(genai.GenerativeModel / caching.CachedContent)で書いてあります。現行の google-genai SDK に移す場合は、caching.CachedContent.create() が client.caches.create()、.list() が client.caches.list()、.delete() が client.caches.delete(name) に対応します。キャッシュの更新で変えられるのは ttl と expire_time だけで、中身は作り直しになる点は、どちらの SDK でも同じです。

Context Cache の作成(基本実装)

以下は、長いシステムプロンプトをキャッシュする基本的な実装です。

import google.generativeai as genai
from google.generativeai import caching
import datetime
import time
 
# APIキーの設定(実際は Environment Variable から取得)
genai.configure(api_key="YOUR_GEMINI_API_KEY")
 
# キャッシュするコンテンツ(長いシステムプロンプトの例)
SYSTEM_DOCUMENT = """
あなたは高度なカスタマーサポート AI です。以下のルールと知識ベースに基づいて回答してください。
 
[製品ドキュメント: 10,000 文字以上の詳細な仕様書]
--- 製品仕様 ---
製品名: ExampleApp Pro
バージョン: 3.2.1
... (長い内容が続く) ...
"""  # 実際には 2,048 トークン以上になるコンテンツを使用
 
def create_cache() -> caching.CachedContent:
    """Context Cache を作成し、キャッシュオブジェクトを返す"""
    try:
        cached_content = caching.CachedContent.create(
            model="models/gemini-2.5-pro",
            display_name="customer-support-system-prompt",  # 後で検索しやすい名前を付ける
            system_instruction=SYSTEM_DOCUMENT,
            ttl=datetime.timedelta(hours=12),  # 12 時間キャッシュを保持
        )
        print(f"✅ キャッシュ作成成功: {cached_content.name}")
        print(f"   有効期限: {cached_content.expire_time}")
        print(f"   使用トークン数: {cached_content.usage_metadata.total_token_count}")
        return cached_content
    except Exception as e:
        print(f"❌ キャッシュ作成失敗: {e}")
        raise
 
def chat_with_cache(cached_content: caching.CachedContent, user_message: str) -> str:
    """キャッシュを参照しながらチャットリクエストを送信する"""
    try:
        # キャッシュからモデルを作成(これが Context Caching を有効にするポイント)
        model = genai.GenerativeModel.from_cached_content(cached_content)
        response = model.generate_content(user_message)
 
        # 使用トークン数を確認(キャッシュが効いているかここで検証)
        usage = response.usage_metadata
        print(f"   キャッシュトークン: {usage.cached_content_token_count}")
        print(f"   入力トークン(合計): {usage.prompt_token_count}")
        print(f"   出力トークン: {usage.candidates_token_count}")
 
        return response.text
    except Exception as e:
        print(f"❌ リクエスト失敗: {e}")
        raise
 
# 使用例
if __name__ == "__main__":
    # キャッシュを 1 回だけ作成
    cache = create_cache()
 
    # 同じキャッシュを複数のリクエストで再利用(ここでコスト削減)
    questions = [
        "返品ポリシーを教えてください",
        "製品の保証期間はどれくらいですか?",
        "サポートの営業時間を教えてください",
    ]
 
    for question in questions:
        print(f"\n❓ {question}")
        answer = chat_with_cache(cache, question)
        print(f"💬 {answer[:100]}...")
        time.sleep(1)  # レート制限対策

このコードで特に注目してほしいのは、response.usage_metadata.cached_content_token_count を確認している部分です。この値が 0 の場合はキャッシュが参照されていないため、設定に問題がある可能性があります。本番環境では必ずこの値をモニタリングしてください。

Context Cache の管理(一覧・更新・削除)

本番環境ではキャッシュのライフサイクルを適切に管理する必要があります。以下のマネージャークラスを使うと、キャッシュの検索・更新・削除を安全に扱えます。

import google.generativeai as genai
from google.generativeai import caching
import datetime
from typing import Optional
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
 
class CacheManager:
    """Context Cache の CRUD 操作をまとめたマネージャークラス"""
 
    @staticmethod
    def list_caches() -> list:
        """現在アクティブなキャッシュ一覧を取得"""
        caches = list(caching.CachedContent.list())
        print(f"アクティブなキャッシュ数: {len(caches)}")
        for c in caches:
            now = datetime.datetime.now(datetime.timezone.utc)
            remaining = (c.expire_time - now).total_seconds()
            print(f"  - {c.display_name}: 残り{remaining/3600:.1f}時間")
        return caches
 
    @staticmethod
    def get_cache_by_name(display_name: str) -> Optional[caching.CachedContent]:
        """名前でキャッシュを検索して取得(プロセス再起動後の再利用に重要)"""
        for c in caching.CachedContent.list():
            if c.display_name == display_name:
                now = datetime.datetime.now(datetime.timezone.utc)
                remaining_hours = (c.expire_time - now).total_seconds() / 3600
                if remaining_hours > 0.5:  # 30 分以上残っている場合のみ使用
                    return c
        return None
 
    @staticmethod
    def extend_cache_ttl(cache: caching.CachedContent, hours: int = 24):
        """キャッシュの有効期限を延長する"""
        try:
            cache.update(ttl=datetime.timedelta(hours=hours))
            print(f"✅ TTL 更新: {cache.display_name} → +{hours}時間")
        except Exception as e:
            print(f"❌ TTL 更新失敗: {e}")
 
    @staticmethod
    def delete_cache(cache: caching.CachedContent):
        """キャッシュを削除する(保存コストを止めるため、不要になったら削除)"""
        try:
            display_name = cache.display_name
            cache.delete()
            print(f"🗑️ キャッシュ削除: {display_name}")
        except Exception as e:
            print(f"❌ 削除失敗: {e}")

get_cache_by_name が特に重要です。サーバーを再起動するたびに新しいキャッシュを作ってしまうと、古いキャッシュの保存コストが積み重なります。名前で既存キャッシュを検索して再利用することで、この問題を防げます。

Context Caching のコスト計算(実例)

具体的な数値で節約効果を見てみましょう。

条件: gemini-2.5-pro、システムプロンプト 10,000 トークン、1 日 1,000 リクエスト

キャッシュなし(月額):

  • 入力トークン: 10,000 × 1,000 × 30 = 3 億トークン
  • 推定コスト: $375 / 月

Context Caching あり(月額):

  • キャッシュ作成・保存料(12 時間 TTL、日 1 回更新): 約 $24 / 月
  • キャッシュ参照料(フル料金の約 75% 割引): 約 $95 / 月
  • 合計: 約 $119 / 月(節約率: 約 68%)

この試算は、実際にシステムプロンプトが毎リクエストで送信されている場合の効果です。料金の正確な数値は Google AI料金ページ で確認してください。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦個人開発アプリの本番で Context Caching を 6 ヶ月運用した、月額コストの実測値($281 → $98)とキャッシュヒット率の週次推移
✦公式ドキュメントには書かれていない 7 つの運用インサイト(最小トークン要件・URI 必須・保存コストの隠れた山・スレッド競合・display_name 衝突・期限切れ間際の挙動・count_tokens の課金)
✦AdMob 連携アプリで Flash と Pro をキャッシュと組み合わせる時の判断ライン、TTL の早見表、月予算 ÷ 30 × 1.3 のアラート式
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

◈ API / SDK2026-04-07
Gemini API セマンティックルーター実装ノート ― Flash と Pro を賢く振り分けて API 費用を抑える
Gemini API でクエリ内容を自動判定し最適モデルへ振り分けるセマンティックルーターの実装ノート。Flash と Pro を 2 段階で賢くディスパッチして API 費用を抑える設計パターン、Python・TypeScript の動くコード、運用で気づいた 7 つの実装インサイトをまとめました。
◈ API / SDK2026-07-04
Gemini API の英語出力に日本語が『たまに』混ざるとき — 混入率を計測して段階的に締める運用メモ
英語出力を指示したGemini APIが、100回に数回だけ日本語を混ぜてくる——この『たまに』を止められない本当の理由と、混入率をSLOとして計測し、段階的リカバリで本番品質まで締める運用パターンを実装コード付きで整理します。
◈ API / SDK2026-05-09
Gemini API に画像を送る前に Pillow で縮小しておくべき理由
Gemini API のマルチモーダル入力で iPhone の写真をそのまま投げているとレイテンシも料金も静かに膨らみます。Pillow で前処理するだけで結果が変わった実例と、私が普段使っている縮小関数をまとめました。