CacheLens
Non-invasive prompt cache instrumentation for LLM API apps. Wrap your client in one line. Get terminal reports, JSON exports, and OTEL metrics.
Prompt caching gives steep discounts on cached tokens — but nothing tells you whether your app is actually getting cache hits, or why not. CacheLens wraps your Anthropic, Gemini, or OpenAI client and reports cache hit rate, cost, savings, and the money you're leaving on the table, broken down by prompt layer.
See docs/architecture.md for the full design.
Install
pip install cachelens # core + rich
pip install cachelens[anthropic] # + Anthropic SDK
pip install cachelens[gemini] # + Gemini SDK
pip install cachelens[openai] # + OpenAI SDK
pip install cachelens[otel] # + OpenTelemetry
pip install cachelens[all] # everything
Quickstart
import anthropic
from cache_lens import wrap
client = wrap(anthropic.Anthropic())
# ... use client exactly as before; report prints on exit
Explicit session boundary with exports:
from cache_lens import CacheLens
with CacheLens(client, json_export="report.json", otel=True) as session:
agent.run(...) # your code, unchanged
report = session.report
Suppress the terminal report in CI with CACHE_LENS_TERMINAL=0.
Custom pricing
CacheLens ships a default price table, but you can override or extend it without forking — handy when a new model lands. User entries merge over the defaults:
# in-memory dict (native format, USD per 1M tokens)
wrap(client, pricing={"openai": {"gpt-5": {"input": 1.25, "output": 10.0, "cache_read": 0.125}}})
# or a JSON file (native or LiteLLM model_prices_and_context_window.json format)
wrap(client, pricing="pricing.json")
Or point at a file process-wide with CACHE_LENS_PRICING=/path/to/pricing.json.
A bad pricing file falls back to defaults rather than breaking the run. See
docs/architecture.md for the full design.
A pricing= argument applies to that wrapped client's session only — two
clients with different overrides don't leak into each other. For a
process-wide override use the env var or cache_lens.pricing.load(...).
Privacy & what gets captured
CacheLens never sends your prompts anywhere. By default it doesn't even keep
them: each request segment is reduced to (role, SHA-256 hash, length) at
capture time — enough for prefix diffing and layer attribution — so memory
stays bounded and no prompt content lingers in the process heap. Reports
(terminal, JSON, OTEL) contain only aggregates: token counts, costs, hit
rates, and tips. Pass capture_content=True to wrap() / CacheLens to
retain full text for your own inspection.
Async clients (AsyncAnthropic, AsyncOpenAI, generate_content_async) are
instrumented like sync ones. Streaming calls (stream=True,
messages.stream) are not instrumented yet — they are counted and shown
as "skipped" in the report, so missing tokens are visible rather than silently
under-counted.
The wrapper is a proxy: isinstance(wrapped, anthropic.Anthropic) is False.
Call wrapped.unwrap() to reach the original client where identity matters.
How this helps you develop LLM applications
Prompt caching only pays off if your prompt's prefix is stable and byte-identical across calls — but most agent loops accumulate per-turn state (timestamps, counters, mutating progress trackers) that silently breaks the prefix without anyone noticing. The API still works fine, the bill just stays high. CacheLens turns that invisible problem into a concrete, iterable workflow during development:
- Wrap your client once and run your normal dev/test loop — no changes to your app logic required.
- Read the layer table to see whether your prompt is actually splitting
into stable layers (system prompt, schema/context, conversation) or
collapsing into one big
conversationblob — the latter is a strong signal that something near the top of your prompt changes every turn. - Use the tips as a diagnosis, not just a metric. "No stable prompt prefix detected" tells you why your hit rate is 0% and what to fix (move static content first, make it byte-identical); "X% of input tokens are uncached" tells you how much headroom restructuring is worth before you spend time on it.
- Re-run after each change and compare
Savings,Cached/Hit Rate, and whether the tips changed — this is the feedback loop that tells you whether a refactor (e.g. splitting prompt-building into a stable prefix and a volatile trailer) actually moved the needle, before you ever look at a billing dashboard.
See examples/queryargus.md for a real before/after walkthrough of this loop on a 30-turn Gemini agent.
Status
v1.1. Implemented: wrapper interception (sync and async) with request capture (content-free by default — hash + length only), provider extraction
- capture (Anthropic + Gemini + OpenAI), content-based layer
classification (longest-common-prefix → named system_prompt / context /
conversation layers, cross-referenced against actual cache reads),
terminal/JSON/OTEL outputs, per-session overridable pricing, latency
percentiles, skipped-call accounting, tests.
Pending: streaming usage extraction (currently counted as skipped),
cache-lens runCLI injection, and cross-run static/semi-static separation (see docs/architecture.md and docs/improvement-plan.md).
Develop
pip install -e .[dev]
pytest
Metadata
Release files for cachelens 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| cachelens-1.1.0.tar.gz | 42.7 kB | With provenance | Details for cachelens-1.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cachelens-1.1.0-py3-none-any.whl | Python 3 | none | any | Details for cachelens-1.1.0-py3-none-any.whl |
Total release size: 67.5 kB
Release files / cachelens-1.1.0.tar.gz
| Download URL | cachelens-1.1.0.tar.gz |
|---|---|
| Size | 42.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c9a72aeabdd390fa70e94c7cac0ace0b906ea5b13e15e2ec8714f3c10830d857
|
|
BLAKE2b-256 checksum How to use checksums |
4588efd4b3ec577dfd087d5cd62ef86c821a3686efaa17dc7c6c644125cc7907
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logRelease files / cachelens-1.1.0-py3-none-any.whl
| Download URL | cachelens-1.1.0-py3-none-any.whl |
|---|---|
| Size | 24.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a2bd7ab0e7b99c6edfd380b49930cf50dba36141330e9939752b5a7e6fab776b
|
|
BLAKE2b-256 checksum How to use checksums |
2003d00c3ba840417bc7376063af520a1e13b0a06a4f1cb5adae7fa6c1a31ed5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logSecurity for cachelens 1.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for cachelens-1.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.