Cache Savings Calculator — Python source
See what prompt caching saves — uncached vs cached cost over N requests, with the write-premium break-even point.
This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.
"""cache_savings — uncached vs prompt-cached LLM cost comparison.
Language: Python (3.9+, standard library only)
Source: CosmoDev polyglot showcase port of the Cache Savings Calculator
tool, ported from src/lib/cacheSavings.ts (the canonical
TypeScript implementation).
Tool: https://dev.cosmolabs.org/tools/cache-savings-calculator
License: display source — part of CosmoDev's polyglot tool pages.
Design goals:
- Pure + deterministic; never raises.
- Functionally equivalent to the TS reference: same inputs -> same outputs.
- Self-contained: stdlib only (no pip packages).
The TS original takes a full ``AiModel`` record but reads only its four
pricing rates, so this port narrows the parameter to exactly those fields
(snake_cased per Python convention: ``inputPerM`` -> ``input_per_m`` etc.).
Any ``None`` rate makes every output ``None`` — the caller renders an
explanatory empty state instead of partial math. All rates are per-1M-token
USD, mirroring the cost conventions of llmCost.ts.
Reference vectors (fixture model 10 / 50 / 1 / 12.5 — see
cacheSavings.test.ts, the lock-step contract every port mirrors):
10k in / 1k out / 5 hits -> uncached 0.75, cached 0.425, savings 0.325,
43.333...% saved, break-even 13 hits. At 1 hit caching LOSES 0.035 (an
honest negative saving). hits < 1 counts as 1. Zero tokens -> zero costs
with 0%. cache_read 0 -> break_even_hits None (write premium never repaid).
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Optional
__all__ = ["ModelRates", "CacheInput", "CacheMath", "cache_math"]
@dataclass(frozen=True)
class ModelRates:
"""The four per-1M-token USD pricing rates ``cache_math`` reads from the
TS ``AiModel`` record."""
input_per_m: Optional[float]
"""Uncached prompt (input) rate, USD per 1M tokens."""
output_per_m: Optional[float]
"""Completion (output) rate, USD per 1M tokens."""
cache_read_per_m: Optional[float]
"""Cached prompt read rate, USD per 1M tokens."""
cache_write_per_m: Optional[float]
"""Cache write premium rate, USD per 1M tokens."""
@dataclass(frozen=True)
class CacheInput:
"""Request shape (TS ``CacheInput``)."""
prompt_tokens: float
"""Prompt (input) tokens per request."""
output_tokens: float
"""Completion (output) tokens per request."""
hits: float
"""Requests reusing the cached prompt. Values < 1 are treated as 1."""
@dataclass(frozen=True)
class CacheMath:
"""Result shape. Any missing rate nulls every field."""
uncached: Optional[float]
"""hits × (prompt·in$/M + output·out$/M) / 1e6."""
cached: Optional[float]
"""(prompt·write$/M + hits × (prompt·read$/M + output·out$/M)) / 1e6 —
one cache write, ``hits`` cache reads, output billed every request."""
savings: Optional[float]
"""uncached − cached (negative when caching costs more)."""
savings_pct: Optional[float]
"""savings / uncached × 100; 0 when uncached is 0."""
break_even_hits: Optional[int]
"""ceil(write$/M / read$/M) when read$/M > 0 — cache hits needed for
cumulative READ spend to equal ONE write premium; None otherwise."""
def cache_math(model: ModelRates, inp: CacheInput) -> CacheMath:
"""Compare uncached vs prompt-cached cost for one model. Never raises;
an unpriced rate yields the all-``None`` result so callers can render an
explanatory empty state instead of partial math."""
if (
model.input_per_m is None
or model.output_per_m is None
or model.cache_read_per_m is None
or model.cache_write_per_m is None
):
return CacheMath(None, None, None, None, None)
hits = max(1, inp.hits)
in_t = inp.prompt_tokens
out_t = inp.output_tokens
# One cache write, `hits` cache reads; output tokens are billed on every request.
uncached = (hits * (in_t * model.input_per_m + out_t * model.output_per_m)) / 1_000_000
cached = (
in_t * model.cache_write_per_m
+ hits * (in_t * model.cache_read_per_m + out_t * model.output_per_m)
) / 1_000_000
savings = uncached - cached
savings_pct = 0 if uncached == 0 else (savings / uncached) * 100
break_even_hits = (
math.ceil(model.cache_write_per_m / model.cache_read_per_m)
if model.cache_read_per_m > 0
else None
)
return CacheMath(uncached, cached, savings, savings_pct, break_even_hits)
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →