◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — The old Deep Research agent shut down on 10/23. If calls fail, move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●CLI — Gemini API CLI (gemini-api) is out in beta (10/27): a zero-dependency client for agents and CI●STORE — New paid projects now save Generate Content requests by default (store on) as of 10/26●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — The old Deep Research agent shut down on 10/23. If calls fail, move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●CLI — Gemini API CLI (gemini-api) is out in beta (10/27): a zero-dependency client for agents and CI●STORE — New paid projects now save Generate Content requests by default (store on) as of 10/26●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/API / SDK
◈ API / SDK/2026-05-25Advanced

Designing a Semantic Cache for the Gemini API — Embedding-based Answer Caching That Actually Pays for Itself

A practical design for a semantic cache that sits in front of the Gemini API. Combines text-embedding-004, cosine similarity thresholds, versioned cache keys, and TTL design to balance hit rate and answer quality, with Python and Cloudflare Vectorize code that runs in production.

gemini-api291embedding10semantic-cachecost-optimization30vectorizeproduction140

✦ Premium Article

The Gemini 2.5 Flash pricing is cheap on paper, but when you operate several apps that together pass 50 million downloads, the monthly invoice from the Gemini API starts to bite. In my case, one healing app I run uses Gemini to infer mood tags from a user's daily journal entry. At peak, that endpoint fires dozens of requests per second, and when I broke down the logs I noticed that most of them were variations of the same prompt arriving from different users.

The exact-match cache I had in place wasn't doing much. People write journal entries in their own way: "I think I'm a bit tired today", "Today I'm a little tired", "today I'm sort of tired" — semantically identical, lexically different. An exact-match cache obviously misses all of those. I was paying Gemini every time to return the same three relaxation tags.

After living with that for a while I decided to build a proper semantic cache. This article is the design and lessons I extracted from running it in production. I'll also pull in context from running indie iOS / Android apps since 2014, a cumulative 50M downloads, and the four-site Dolice Labs network, because the "what's worth optimizing for a one-person shop" boundary matters as much as the code.

Why a hash-keyed cache won't save you on LLM responses

A typical HTTP cache uses the URL plus query string as the key. Same URL, same query, same response — that's the contract. LLM responses break that assumption immediately, because the cache key is now a natural-language prompt. Users vary punctuation, politeness level, typos, every parameter you can imagine. The two journal sentences I quoted above hash to completely different keys.

In my healing app the exact-match cache ran at a 6–9% hit rate. Roughly 90% of every 100 requests still hit Gemini directly. That is the classic "the cache is in but doesn't help" pattern. It was tolerable while AdMob revenue absorbed it, but as the share of free-tier users grew, the bill became a real factor.

What you want is a cache that maps semantically close prompts to the same entry. You vectorize the incoming prompt with an embedding model and ask the cache store whether anything close enough already exists.

What semantic caching solves, and what it introduces

A semantic cache absorbs lexical variation, which is the point. It also introduces a new failure mode: prompts that look close in vector space but expect different answers.

"What relaxation methods can I try when I can't sleep?" and "What relaxation music can I try when I can't sleep?" are very close in embedding distance. But the first expects breathing exercises and stretching, the second expects a playlist. Reusing a cached answer across that boundary collapses the answer quality.

So a semantic cache is fundamentally a hit-rate versus quality trade-off. Loose threshold, high hit rate, more wrong reuses. Strict threshold, low hit rate, less benefit. In production I ended up with a threshold that adapts to the length of the user's journal entry. More on that below.

✦

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
✦You can implement similarity-based cache lookups with Gemini text-embedding-004, with the threshold-tuning pitfalls and a real measurement table you can copy
✦You get a concrete picture of how a 40–60% hit rate on a 50M-download indie app stack translated into 35% lower monthly API spend, and what trade-offs you accept along the way
✦You take home three concrete patterns: TTL design, versioned cache keys, and a Cloudflare KV + Vectorize edge deployment, each with the pitfalls I hit during rollout
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.

or
Unlock all articles with Membership →
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.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ API / SDK2026-04-19
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.
◈ API / SDK2026-04-07
Gemini API Semantic Router: Implementation Notes for Splitting Flash and Pro Smartly
Implementation notes for building a production-grade semantic router that automatically dispatches Gemini queries between Flash and Pro. Includes Python and TypeScript working code, a two-stage design pattern, and seven implementation insights from running it inside an indie wallpaper app.
◈ API / SDK2026-07-23
When to Hand Conversation State to the Server: previous_interaction_id vs. Pruning History Yourself
With the Interactions API, passing previous_interaction_id lets the server hold conversation state so you stop resending history. But a large tool output that lands mid-conversation can't be pruned afterward, and every later turn drags its weight. Here is a branching design that mixes server-side state with client-side pruning, plus a thin, working Python wrapper.