●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20●NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaply●OMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflows●AGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactions●MEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuously●THROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and region●DEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Gemini API Caching in Production — Operational Notes from an Indie Mobile Developer
Field notes on running Gemini API's Context Caching and Implicit Caching together inside indie mobile apps. Includes working Python code, six months of measured costs from AdMob-funded apps, and seven non-obvious operational pitfalls.
I'm Masaki Hirokawa (@dolice), an artist and indie developer based in Japan. Since 2014 I've built and operated a family of iPhone and Android apps — wallpapers, calming visuals, manifestation tools — with around 50 million cumulative downloads, monetised mostly through AdMob. That last detail is important for this article: the monthly Gemini API bill comes straight out of ad revenue, so it isn't an abstract optimisation problem for me. It's the same line item as my electricity bill.
The trigger for taking caching seriously was a small one. I added a feature to one of the wallpaper apps that generates a short poem matching the mood of each image. The first month's Cloud Billing email said $412 — about three times what I'd expected — and the cause was almost entirely the system prompt and brand guidelines being shipped on every request. AdMob revenue was still in the black, but I sat with that bill for a while before opening the docs on Context Caching and Implicit Caching.
What follows is what six months of operating these two caching mechanisms in production taught me. Every code sample has been verified in a working system. Numbers come from real Cloud Billing and AdMob reports.
Why Caching Matters: The Gemini API Cost Structure
Before diving into implementation, it helps to understand exactly where the money goes.
Gemini API billing splits into three buckets: input tokens, output tokens, and cache storage. The first one is the killer: input tokens are billed on every single request.
Consider a customer support bot with a 10,000-token system prompt that handles 1,000 requests per day. That's 10 million tokens per day, 300 million per month. At gemini-2.5-pro pricing (~$1.25 per million input tokens up to 200K), the system prompt alone costs about $375 a month — and the user hasn't received a single useful answer yet.
Caching changes the equation:
Context Caching: Explicitly cache a chunk of content and reference it by ID in subsequent requests. Cached tokens are billed at approximately a 75% discount versus full price.
Implicit Caching: When the same prompt prefix repeats, discounted pricing kicks in automatically (gemini-2.0-flash and newer).
Picking the right one for each call site — and combining them deliberately — is what actually drives bills down.
What Caches Well, and What Doesn't
Not every payload benefits. The big wins come from "fixed context reused across requests":
Long system prompts (5,000+ tokens of spec, instructions, brand guidelines)
Reference documents (product manuals, API specs, legal text)
Image and video files used as multimodal context
Whole codebases used for code review or Q&A
User messages that change every call are not cacheable — and trying to design as if they were just wastes effort. Separating "the part that never changes" from "the part that does" is the first real step toward caching efficiency.
In my wallpaper app, the fixed context — world-building, tone rules, banned-word list, output format spec — adds up to about 12,400 tokens. The user message is just an image embedding and a couple of lines of instruction. That split alone took the monthly bill from $281 to $96 over six months.
For a broader treatment of cost optimisation, see Gemini API Cost Optimization Complete Guide.
How Context Caching Works (and How to Implement It)
Context Caching is an explicit API: create the cache, get back an ID, then reference that ID from later requests to get the discount.
Prerequisites — Check These First
There are a few conditions that aren't obvious until you've tripped on them:
Minimum token count: The content being cached must have a minimum number of tokens (2,048+ for gemini-2.5-pro).
Supported models: Not every model qualifies. gemini-2.0-flash, gemini-2.5-pro, and gemini-2.5-flash are the main ones currently supported.
Cache TTL: Default is one hour, extendable up to 24 hours.
The minimum-token requirement is easy to miss; cache something shorter and you'll get a BadRequestError.
Creating a Cache (Basic Implementation)
Here's a minimal pattern for caching a long system prompt.
import google.generativeai as genaifrom google.generativeai import cachingimport datetimeimport time# In real code, pull the key from an environment variablegenai.configure(api_key="YOUR_GEMINI_API_KEY")SYSTEM_DOCUMENT = """You are an advanced customer support AI. Follow the rules and knowledge base below.[Product documentation: 10,000+ characters of detailed spec]--- Product Spec ---Product Name: ExampleApp ProVersion: 3.2.1... (long content continues) ...""" # Use real content that adds up to 2,048+ tokensdef create_cache() -> caching.CachedContent: """Create a Context Cache and return the cached content object.""" try: cached_content = caching.CachedContent.create( model="models/gemini-2.5-pro", display_name="customer-support-system-prompt", # Choose a searchable name system_instruction=SYSTEM_DOCUMENT, ttl=datetime.timedelta(hours=12), # Retain cache for 12 hours ) print(f"OK Cache created: {cached_content.name}") print(f" Expires: {cached_content.expire_time}") print(f" Tokens: {cached_content.usage_metadata.total_token_count}") return cached_content except Exception as e: print(f"FAIL Cache creation failed: {e}") raisedef chat_with_cache(cached_content: caching.CachedContent, user_message: str) -> str: """Send a chat request that references the cache.""" try: model = genai.GenerativeModel.from_cached_content(cached_content) response = model.generate_content(user_message) # Verify the cache is actually being used usage = response.usage_metadata print(f" Cached tokens: {usage.cached_content_token_count}") print(f" Prompt tokens (total): {usage.prompt_token_count}") print(f" Output tokens: {usage.candidates_token_count}") return response.text except Exception as e: print(f"FAIL Request failed: {e}") raiseif __name__ == "__main__": cache = create_cache() questions = [ "What is your return policy?", "How long is the product warranty?", "What are your support hours?", ] for question in questions: print(f"\nQ: {question}") answer = chat_with_cache(cache, question) print(f"A: {answer[:100]}...") time.sleep(1)
The line worth memorising is response.usage_metadata.cached_content_token_count. If this is zero, the cache wasn't actually used and something's wrong — monitor this value in production.
Cache Lifecycle Management
In production you need to handle list/update/delete cleanly. This manager class is what I run in my apps.
import google.generativeai as genaifrom google.generativeai import cachingimport datetimefrom typing import Optionalgenai.configure(api_key="YOUR_GEMINI_API_KEY")class CacheManager: """CRUD operations for Context Cache.""" @staticmethod def list_caches() -> list: """List currently active caches.""" caches = list(caching.CachedContent.list()) print(f"Active caches: {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}h remaining") return caches @staticmethod def get_cache_by_name(display_name: str) -> Optional[caching.CachedContent]: """Look up a cache by name (important for reuse across process restarts).""" 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: # Only use if 30+ minutes left return c return None @staticmethod def extend_cache_ttl(cache: caching.CachedContent, hours: int = 24): """Extend a cache's TTL.""" try: cache.update(ttl=datetime.timedelta(hours=hours)) print(f"OK TTL extended: {cache.display_name} -> +{hours}h") except Exception as e: print(f"FAIL TTL update failed: {e}") @staticmethod def delete_cache(cache: caching.CachedContent): """Delete a cache to stop accruing storage costs.""" try: display_name = cache.display_name cache.delete() print(f"DELETED Cache: {display_name}") except Exception as e: print(f"FAIL Delete failed: {e}")
get_cache_by_name matters more than it looks. If a server restart creates a fresh cache every time, you pile up storage costs for orphaned caches. Look up the existing one by name and reuse it.
Worked Example: Context Caching Cost
Concrete numbers help.
Setup: gemini-2.5-pro, 10,000-token system prompt, 1,000 requests/day
Cache reference (75% discount on cached portion): ~$95 / month
Total: ~$119 / month (about 68% savings)
These numbers assume the system prompt was previously sent on every request. Real prices vary — check the Google AI pricing page for the latest.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Six months of measured monthly cost on a wallpaper app (50M cumulative downloads): $281 → $98 with Context Caching, plus how the hit rate moved week by week
✦Seven implementation insights that aren't in the official docs (min-token requirements, hidden storage costs, display_name collisions, expiry races, count_tokens being billable)
✦Decision rules for combining Flash + Pro with caching in AdMob-funded apps, a TTL cheat sheet by task type, and a 'monthly budget ÷ 30 × 1.3' billing alert formula
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Implicit Caching is newer and conceptually simpler: the API discounts repeated prompt prefixes automatically, without you doing anything. Available on gemini-2.0-flash, gemini-2.5-flash, and similar models.
Hit Conditions
Implicit Caching kicks in when several conditions line up:
Matching prompt prefix: The beginning of the request matches a previous request.
Sufficient token count: The cacheable prefix is long enough.
Server-side decision: Google's infrastructure still has the cache available.
The critical caveat: Implicit Caching does not guarantee a hit. When it hits you get the discount; when it misses you pay full price. Treat it as a bonus, not a budgeted savings, and your designs stay simple.
For a deeper look at this mechanism on its own, see Gemini API Implicit Caching Guide.
Prompt Design That Lifts the Hit Rate
You can't configure Implicit Caching, but you can write prompts that work with it.
import google.generativeai as genaigenai.configure(api_key="YOUR_GEMINI_API_KEY")SYSTEM_DOC = "Long fixed content goes here..." # Several thousand tokens in practice# BAD: Variable content at the frontdef bad_prompt_structure(user_message: str) -> str: """User message at the front blocks Implicit Caching.""" prompt = f"{user_message}\n\nReference document below:\n{SYSTEM_DOC}" model = genai.GenerativeModel("gemini-2.0-flash") response = model.generate_content(prompt) return response.text# GOOD: Fixed content first, variable content lastdef good_prompt_structure(user_message: str) -> str: """Fixed prefix is more likely to hit Implicit Caching.""" prompt = f"""Use the document below to answer the user's question.===Document Start==={SYSTEM_DOC}===Document End===User question: {user_message}""" model = genai.GenerativeModel("gemini-2.0-flash") response = model.generate_content(prompt) usage = response.usage_metadata cached = getattr(usage, "cached_content_token_count", 0) if cached > 0: print(f"HIT: Implicit cache saved {cached} tokens") else: print("MISS: full pricing") return response.text# BEST: Use system_instructiondef use_system_instruction(user_message: str) -> str: """Putting fixed content into system_instruction maximises hit rate.""" model = genai.GenerativeModel( "gemini-2.0-flash", system_instruction=SYSTEM_DOC, ) response = model.generate_content(user_message) usage = response.usage_metadata cached = getattr(usage, "cached_content_token_count", 0) print(f"Input: {usage.prompt_token_count} / Cached: {cached}") return response.text
Gemini's caching is prefix-match. The longer the matching prefix from the start of the request, the better your odds — so put the stable content first.
Choosing Between Context Caching and Implicit Caching
A quick decision matrix:
Use Context Caching when:
System prompt is 5,000+ tokens
The same context is reused for hours or a full day
You need measurable, auditable savings
You need a guaranteed hit on every request
Use Implicit Caching when:
You're on a supported model (e.g. gemini-2.0-flash)
You want zero cache management overhead
A miss is acceptable — savings are a bonus
You're sending many similar requests in a short burst
Combine both when:
You want maximum effect. Configure Context Caching explicitly and structure prompts so Implicit Caching has a chance to fire on top. The next section ties them together.
Production Integration: Combined Caching Architecture
The class below combines both caching mechanisms. It's thread-safe, handles auto-refresh, and falls back gracefully.
import google.generativeai as genaifrom google.generativeai import cachingimport datetimeimport threadingfrom typing import Optionalimport logginggenai.configure(api_key="YOUR_GEMINI_API_KEY")logger = logging.getLogger(__name__)class ProductionCacheStrategy: """ Production cache manager. Combines Context Caching and Implicit Caching to minimise cost. """ def __init__( self, model_name: str = "models/gemini-2.5-pro", system_prompt: str = "", cache_ttl_hours: int = 12, min_token_threshold: int = 2048, cache_display_name: str = "prod-cache", ): self.model_name = model_name self.system_prompt = system_prompt self.cache_ttl_hours = cache_ttl_hours self.min_token_threshold = min_token_threshold self.cache_display_name = cache_display_name self._cache: Optional[caching.CachedContent] = None self._lock = threading.Lock() self._request_count = 0 self._cache_hit_count = 0 def _estimate_tokens(self, text: str) -> int: """Rough token estimate. Use model.count_tokens() for accuracy.""" return len(text) // 3 def _should_use_context_cache(self) -> bool: """Is the system prompt long enough to bother with Context Caching?""" estimated = self._estimate_tokens(self.system_prompt) return estimated >= self.min_token_threshold def _get_or_create_cache(self) -> Optional[caching.CachedContent]: """Thread-safe get-or-create for the cache.""" with self._lock: if self._cache is not None: now = datetime.datetime.now(datetime.timezone.utc) remaining_sec = (self._cache.expire_time - now).total_seconds() if remaining_sec > 300: return self._cache logger.info("Cache near expiry, recreating") if not self._should_use_context_cache(): logger.info("System prompt too short, skipping Context Caching") return None # Try to reuse an existing cache by name (survives process restarts) for existing in caching.CachedContent.list(): if existing.display_name == self.cache_display_name: now = datetime.datetime.now(datetime.timezone.utc) remaining_h = (existing.expire_time - now).total_seconds() / 3600 if remaining_h > 0.5: logger.info(f"Reusing existing cache: {existing.name} ({remaining_h:.1f}h left)") self._cache = existing return self._cache try: self._cache = caching.CachedContent.create( model=self.model_name, display_name=self.cache_display_name, system_instruction=self.system_prompt, ttl=datetime.timedelta(hours=self.cache_ttl_hours), ) logger.info(f"Created new cache: {self._cache.name}") return self._cache except Exception as e: logger.error(f"Cache creation failed (falling back to Implicit Caching): {e}") return None def generate(self, user_message: str) -> dict: self._request_count += 1 cache = self._get_or_create_cache() try: if cache is not None: model = genai.GenerativeModel.from_cached_content(cache) response = model.generate_content(user_message) else: model = genai.GenerativeModel( self.model_name, system_instruction=self.system_prompt, ) response = model.generate_content(user_message) usage = response.usage_metadata cached_tokens = getattr(usage, "cached_content_token_count", 0) if cached_tokens > 0: self._cache_hit_count += 1 return { "text": response.text, "cache_hit": cached_tokens > 0, "cached_tokens": cached_tokens, "input_tokens": usage.prompt_token_count, "output_tokens": usage.candidates_token_count, } except Exception as e: logger.error(f"Generation failed: {e}") raise def get_stats(self) -> dict: hit_rate = ( self._cache_hit_count / self._request_count * 100 if self._request_count > 0 else 0 ) return { "total_requests": self._request_count, "cache_hits": self._cache_hit_count, "hit_rate_percent": round(hit_rate, 1), }SYSTEM_PROMPT = """[2,048+ token system prompt goes here]Product manual, support policy, FAQ canonical answers, etc."""strategy = ProductionCacheStrategy( model_name="models/gemini-2.5-pro", system_prompt=SYSTEM_PROMPT, cache_ttl_hours=12, cache_display_name="support-bot-v1",)result = strategy.generate("What's the return window?")print(f"Answer: {result['text'][:200]}")print(f"Cache hit: {result['cache_hit']}")print(f"Cached tokens: {result['cached_tokens']}")print(f"Stats: {strategy.get_stats()}")
What this design buys you:
Thread safety: Concurrent web requests can't all race to create a cache.
Survives restarts: Looks up existing caches by display_name so you don't pile up orphans.
Graceful fallback: If cache creation fails, the service still runs (with Implicit Caching as a backstop).
Seven Implementation Insights That Aren't in the Docs
This is the section I most wanted to write. Six months of running this in production turned up details that don't appear in the official documentation.
Insight 1: count_tokens() Is Itself Billable
model.count_tokens() makes a real API call. It's billed, even though it only returns a number. My initial implementation called count_tokens() before every cache creation, and a job that ran a few hundred times an hour was quietly costing about $4 a month in token-counting calls.
Fix: only call count_tokens() when the system prompt actually changes, and persist the result.
import jsonimport pathlibimport hashlibimport google.generativeai as genaiCACHE_FILE = pathlib.Path("/tmp/.gemini_token_cache.json")def count_tokens_cached(model_name: str, text: str) -> int: """Persist count_tokens results locally so identical text isn't re-billed.""" key = hashlib.sha256(f"{model_name}::{text}".encode("utf-8")).hexdigest() if CACHE_FILE.exists(): try: store = json.loads(CACHE_FILE.read_text()) except json.JSONDecodeError: store = {} else: store = {} if key in store: return store[key] model = genai.GenerativeModel(model_name) count = model.count_tokens(text).total_tokens store[key] = count CACHE_FILE.write_text(json.dumps(store)) return count
Insight 2: Storage Cost Is Heavier Than You'd Think
Context Cache storage is billed per hour. A 100,000-token cache on gemini-2.5-pro held for 24 hours costs roughly $7–$10 a month in storage alone. The real damage happens when you forget to delete() a no-longer-needed cache — I once left three 30,000-token "test" caches alive for a few weeks and bled $18.
The discipline I now follow:
Test caches always get a test- prefix in display_name.
CI jobs delete every cache matching test-* at the end of their run.
def cleanup_test_caches() -> int: """Delete every test-* cache at end of CI.""" deleted = 0 for c in caching.CachedContent.list(): if c.display_name.startswith("test-"): try: c.delete() deleted += 1 except Exception as e: print(f"WARN failed to delete {c.display_name}: {e}") print(f"CLEANUP: deleted {deleted} test caches") return deleted
Insight 3: display_name Collisions Fail Silently
display_name has no uniqueness constraint. You can create two caches with the same display_name, and list() won't tell you which one is "real". I had a situation where my dev laptop and my production server both created customer-support-system-prompt under different API keys, and code calling get_cache_by_name() started returning whichever one happened to come back first.
The fix is to always include the environment and a month-stamp in the name.
import osfrom datetime import datedef stable_display_name(base: str) -> str: """Build a display_name that includes environment and month.""" env = os.environ.get("APP_ENV", "dev") # Month-key means the cache is recreated exactly once a month month_key = date.today().strftime("%Y-%m") return f"{env}-{base}-{month_key}"
Insight 4: Expiry Race Conditions
You can fetch a cache via CachedContent.list() and have it expire between that call and the moment you try to use it with from_cached_content(). The error comes back as PermissionDenied: 403, which is easy to misread as an auth problem.
The defence is to refuse caches within 5 minutes of expiry and create a fresh one instead.
import datetimefrom google.generativeai import cachingdef is_safely_usable(cache: caching.CachedContent, min_remaining_sec: int = 300) -> bool: """Avoid race conditions by treating near-expiry caches as unusable.""" now = datetime.datetime.now(datetime.timezone.utc) remaining = (cache.expire_time - now).total_seconds() return remaining > min_remaining_sec
Insight 5: Multimodal Requires Files API
If you try to put an image or video into Context Caching by passing a local file path, you'll get InvalidArgument. You have to upload the file via the Files API first and pass the URI into the cache content.
In the wallpaper app I keep a brand palette reference image in Context Caching; I lost two days to this before realising the file had to live in Files first.
def cache_with_image(image_path: str, system_text: str) -> caching.CachedContent: """Cache content that includes an image, the right way.""" print("Uploading via Files API...") uploaded_file = genai.upload_file( path=image_path, display_name="brand-palette-reference", ) print(f"OK uploaded: {uploaded_file.uri}") return caching.CachedContent.create( model="models/gemini-2.5-pro", display_name="prod-brand-palette-2026-04", contents=[ { "role": "user", "parts": [ {"text": system_text}, {"file_data": {"file_uri": uploaded_file.uri}}, ], } ], ttl=datetime.timedelta(hours=6), )
Insight 6: The "Next Use + 30 Minutes" TTL Heuristic
Setting TTL to "24 hours and forget" wastes storage during quiet periods. Setting it to "2 hours" causes misses when unexpected requests arrive. The rule I use in the wallpaper app is next-scheduled-use plus thirty minutes.
If a batch runs at 06:00 and another at 22:00, the cache is dropped after the evening run and recreated just before the morning one. Six hours of idle storage cost simply doesn't exist.
def calculate_smart_ttl(hours_until_next_use: float, buffer_minutes: int = 30) -> int: """Compute TTL based on the next scheduled use.""" raw = hours_until_next_use + (buffer_minutes / 60) return max(1, min(24, int(raw + 0.999)))# Example: next batch is 4 hours outttl_hours = calculate_smart_ttl(4)print(f"Recommended TTL: {ttl_hours}h") # Recommended TTL: 5h
Insight 7: Cap Gemini Spend at 10–15% of AdMob Revenue
This is more business heuristic than technique, but it's the line I actually use for indie apps that monetise via AdMob. Keep monthly Gemini spend below 10–15% of monthly AdMob revenue.
The reason is the spike risk: AdMob eCPM dips for a month sometimes (seasonality, policy changes, ad-load drops), and if Gemini spend is at 30% of revenue normally, a dip can flip the app into the red. I set a daily Cloud Billing alert at monthly_budget / 30 × 1.3 and push it into Slack.
def daily_budget_alert(monthly_budget_usd: float) -> float: """Daily alert threshold: monthly budget divided by 30, then × 1.3.""" return (monthly_budget_usd / 30) * 1.3# An app with a $150/mo budgetthreshold = daily_budget_alert(150)print(f"Daily alert threshold: ${threshold:.2f}") # $6.50
Six Months of Measured Cost on a Real Indie App
These are the actual monthly numbers from one of my wallpaper apps after putting Context Caching into production.
App profile: Wallpaper recommendation app, ~8,200 DAU, fraction of the cumulative downloads
Gemini features used: Image-to-short-poem generation, brand-guideline-aware tagging
System prompt: ~12,400 tokens
Daily requests: ~1,500
Month-by-month cost trajectory:
Pre-caching (October 2025): $281 / month — system prompt shipped on every request, no caching
Week 1 of caching: ~$164 / month (extrapolated) — switched to Context Caching with 12h TTL
A Grafana dashboard built on these metrics lets you see "tokens saved this month", hit-rate trends over time, and the exact moment a cache started missing. Watch at least a week of data before adjusting TTL or prompt structure.
For complementary work on errors and rate limits, see Production Error Handling and Rate Limits with Gemini API.
Closing Thoughts
The thing I want to leave you with is this: caching isn't really a cost-optimisation trick. For an indie developer funding an app with AdMob, it's the mechanism that lets the app keep running. The difference between $281/month and $98/month is the difference between "can I afford to ship the Pro model on a few key features?" and "I should probably turn this off."
My grandfather was a temple carpenter, and he used to say that careful work today is what makes future repairs possible. The same instinct applies to Context Caching: the discipline of month-keyed display_name, smart TTLs, and storage audits costs almost nothing on the day you set it up, and saves your future self hours of confusion when the bill comes in.
The single step I'd recommend if you've read this far: take the system prompt your app is using right now and run it through count_tokens(). If it's over 2,048 tokens, the ProductionCacheStrategy in this article is ready to drop into production. The first time response.usage_metadata.cached_content_token_count returns a non-zero value, you'll feel the bill quietly shrink.
Thanks for reading all the way through. I'm still learning too — happy to keep learning alongside whoever this reaches.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.