OpenAI's demo relies on automatic prefix caching. Gemini can also reuse common prefixes implicitly, but its explicit context-caching API exposes a different application model.
The application creates a named cache resource, chooses its lifetime, and refers to that resource from later generation requests.
That makes caching visible as lifecycle-managed infrastructure.
First: What Is Gemini Context Caching?
Gemini context caching lets an application upload a substantial stable context once and reuse it across shorter requests.
stable policy
↓
create cached content resource
↓
cachedContents/policy-1
↓
question A ─┐
question B ─┼→ generate with cached_content
question C ─┘
The cached content becomes a prefix to each generation request. The model still reasons over it together with the new question.
Implicit and Explicit Caching Are Different Paths
Gemini 2.5 and newer models support implicit caching automatically. The application sends the repeated prefix normally and may receive cache savings when a match occurs.
Explicit caching adds a resource API:
IMPLICIT
send full common prefix repeatedly
provider may reuse it automatically
EXPLICIT
create named cache once
send cache name with later questions
manage TTL and cleanup
The project compares a no-explicit-cache baseline with two explicit cache resources.
Creating the Cache
The stable policy and system instruction are provided during cache creation:
cache = client.caches.create(
model=model,
config=types.CreateCachedContentConfig(
display_name=label,
system_instruction=SYSTEM,
contents=[policy],
ttl="300s",
),
)
The returned cache.name is the durable handle for subsequent requests:
cachedContents/...
The demo validates that a name exists before continuing. A cache without a usable identifier cannot be referenced safely.
The TTL Is Part of the Cost Decision
The project uses a five-minute lifetime:
ttl="300s"
Gemini bills explicit-cache storage according to cached token count and storage duration, in addition to generation charges. A longer TTL creates a larger reuse window and more storage duration.
short TTL
→ smaller storage window
→ cache may expire before later work
long TTL
→ larger reuse window
→ more storage duration
The right value depends on request frequency and current model pricing. It should be chosen from expected reuse rather than convenience alone.
Cached Requests Send Only the Changing Case
Without an explicit cache, the request includes policy and question:
contents = f"{policy}\n\nCustomer question: {case}"
config = types.GenerateContentConfig(
system_instruction=SYSTEM,
)
With a named cache, the stable content is already attached to the resource:
contents = f"Customer question: {case}"
config = types.GenerateContentConfig(
cached_content=cache_name,
)
That difference is application-visible. The cached path does not resend the policy string from the local process on each generation call.
Minimum Cache Size Matters
Explicit cache creation is model-specific and requires enough input tokens.
The current Gemini documentation lists a 2,048-token minimum for Gemini 2.5 Flash and Gemini 2.5 Pro. The project's 75-rule fictional policy was expanded to clear that threshold for the default gemini-2.5-flash model.
policy below model minimum
→ cache creation rejected
policy above model minimum
→ cache can be created
This limit can vary by model. Applications should verify the current documentation when changing models rather than carrying one constant forever.
The Same Policy Revision Reuses One Resource
The explicit-cache phase creates revision A once and asks five questions against its name:
create cache(policy revision A)
↓
question 1 ───┤
question 2 ───┤
question 3 ───┤→ same cached_content name
question 4 ───┤
question 5 ───┘
The cache resource is immutable context for those requests. The variable case remains outside it.
A Policy Revision Becomes a New Cache
The project changes the revision marker near the beginning:
changed = POLICY.replace(
"revision 2026-09-A",
"revision 2026-09-B",
1,
)
It then creates another resource:
cachedContents/policy-A → old policy
cachedContents/policy-B → revised policy
This is safer than pretending an old name now represents different content. The application can associate every request with the exact policy revision it used.
Usage Metadata Shows the Reuse
The script reads:
usage = response.usage_metadata
prompt_tokens = usage.prompt_token_count or 0
cached_tokens = usage.cached_content_token_count or 0
output_tokens = usage.candidates_token_count or 0
Cached content tokens are included in prompt tokens.
prompt tokens = cached content + new prompt content
The demo reports the cached share:
total_cached / total_prompt
As with OpenAI, token metadata is the evidence. A latency difference by itself cannot prove cache reuse.
Controlled Output From the Real Gemini Flow
I ran the project's real run(), create_cache(), show_phase(), ask(), and usage-reading code against a deterministic offline Gemini client fixture.
The fixture made no network requests and incurred no provider charges. It returned controlled usage metadata so cache creation, reuse, revision, aggregation, and cleanup could be exercised end to end.
Phase 1: no explicit cache
1: 2300 prompt, 0 cached, 40 output, 0.25s
2: 2300 prompt, 0 cached, 40 output, 0.25s
3: 2300 prompt, 0 cached, 40 output, 0.25s
4: 2300 prompt, 0 cached, 40 output, 0.25s
5: 2300 prompt, 0 cached, 40 output, 0.25s
cached share 0.0%; mean latency 0.25s
Phase 2: create explicit cache for policy revision A
created cachedContents/policy-1; cached policy tokens: 2200
Phase 2: reuse revision A cache
1: 2300 prompt, 2200 cached, 40 output, 0.25s
2: 2300 prompt, 2200 cached, 40 output, 0.25s
3: 2300 prompt, 2200 cached, 40 output, 0.25s
4: 2300 prompt, 2200 cached, 40 output, 0.25s
5: 2300 prompt, 2200 cached, 40 output, 0.25s
cached share 95.7%; mean latency 0.25s
Phase 3: policy revision B requires a new cache
created cachedContents/policy-2; cached policy tokens: 2200
Phase 3: reuse revision B cache
1: 2300 prompt, 2200 cached, 40 output, 0.25s
2: 2300 prompt, 2200 cached, 40 output, 0.25s
cached share 95.7%; mean latency 0.25s
The totals and cleanup were:
no explicit prompt=11500 cached=0 output=200
cached A prompt=11500 cached=11000 output=200
cached B prompt=4600 cached=4400 output=80
Deleted cachedContents/policy-1
Deleted cachedContents/policy-2
These are controlled fixture values that demonstrate application behavior. A live run must use the provider's returned usage metadata and current pricing.
Cleanup Belongs in finally
The demo tracks every created cache name and deletes it even if a later generation call raises an error:
caches = []
try:
# create and use caches
finally:
for name in caches:
client.caches.delete(name=name)
If deletion fails, the script reports that condition and relies on the five-minute TTL.
This is the same resource-management rule used for files, database connections, and temporary infrastructure: creation requires an explicit cleanup path.
Cache Creation Is Not Free Preprocessing
An explicit cache can introduce:
- a cache-creation API call
- cached-input charges
- storage-duration charges
- ordinary non-cached input charges
- output-token charges
- lifecycle and cleanup work
The relevant comparison is total workload cost across expected reuse, not the price of one cached generation call in isolation.
few requests
→ creation and storage may not pay back
many requests sharing large context
→ explicit caching may become useful
Model Support Must Be Checked
The demo intentionally targets gemini-2.5-flash, which supports explicit caching.
Google's current documentation marks explicit context caching as beta and states that Gemini 4 Argon and later Gemini 4 models use implicit caching rather than the explicit cachedContents flow.
An application changing model families must revalidate:
- whether explicit caching is supported
- minimum cached-token count
- TTL behavior
- SDK and API version
- usage metadata fields
- current pricing
Trust Still Depends on Prompt Structure
The cached content contains a fictional support policy and a system instruction that treats each customer message as case data rather than policy.
cached trusted policy
+
changing untrusted customer case
↓
generation
Caching does not create the trust boundary. It preserves the prompt structure that the application already chose. Putting untrusted user content inside a broadly reused cache would be a design error.
What the Tests Prove
The Gemini portion has three offline tests. They verify that:
- the uncached request contains both policy and question
- the explicit-cache request sends only the question and cache name
- cached-token usage is read from Gemini metadata
- cache creation receives the policy and five-minute TTL
The combined project run completed with:
Ran 5 tests in 0.001s
OK
The tests validate integration logic without creating billable caches. A separate live experiment is needed to observe real cache token counts.
OpenAI and Gemini Side by Side
OPENAI GEMINI EXPLICIT CACHE
automatic eligible prefix reuse named cache resource
normal generation request create then reference cache
provider-managed lifetime application-selected TTL
no cache deletion call explicit deletion or expiry
cached_tokens in response usage cached_content_token_count
Both systems reward stable shared context followed by changing input. Their lifecycle and billing models are different enough that one abstraction should not hide the provider-specific details.
What I Took Away
Gemini explicit caching turns repeated context into a managed resource.
identify substantial stable context
create a versioned cache
reuse its name across changing requests
measure cached tokens
delete it or let its TTL expire
That control is useful when reuse is predictable. It also makes the application responsible for cache creation, revision naming, TTL choice, cleanup, and cost measurement.
Source Code
The complete project includes explicit cache creation, revision-specific resources, five-minute TTLs, usage aggregation, cleanup in finally, the OpenAI comparison, and offline tests.