Openrouter caching strategy
'Implement caching for OpenRouter API responses to reduce cost and latency. Use when optimizing repeat queries, building RAG systems, or reducing API spend. Triggers: ''openrouter cache'', ''cache llm responses'', ''openrouter caching'', ''reduce openrouter cost''.From its SKILL.md
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill openrouter-caching-strategyAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.4 KB, ~2.1k tokens by cl100k_base, as published. Nobody here has run it
OpenRouter Caching Strategy
Overview
OpenRouter charges per token, so caching identical or similar requests can dramatically cut costs. Deterministic requests (temperature=0) with the same model and messages produce identical outputs -- these are safe to cache. This skill covers in-memory caching, persistent caching with TTL, and Anthropic prompt caching via OpenRouter.
Prerequisites
- An OpenRouter API key (
sk-or-v1-...) exported asOPENROUTER_API_KEY— see theopenrouter-install-authskill for setup - Python 3.8+ with the OpenAI SDK, plus the
redisclient package for the persistent cache; Node.js 18+ with the OpenAI SDK for the TypeScript variant in the references - A Redis server reachable at
localhost:6379for Persistent Cache with Redis (the in-memoryLLMCacheneeds no infrastructure) - Deterministic request settings — caching is only safe at
temperature=0
Instructions
- Confirm the requests you want to cache are deterministic (
temperature=0); non-zero temperatures produce different outputs each call and must never be cached. - Start with the In-Memory Cache:
LLMCachepluscached_completion()gives you TTL expiry and hit/miss counters in a single process. - For multi-instance deployments, switch to Persistent Cache with Redis —
redis_cached_completion()stores results underor:<sha256>keys withr.setexTTL expiry and falls through to a direct API call on a miss. - Build keys per Cache Key Design: include the model ID (with variants like
:floor), messages, temperature, max_tokens, and top_p; excludestreamand the HTTP-Referer/X-Title headers. - For large static system prompts (RAG context), add
cache_control: {"type": "ephemeral"}per Anthropic Prompt Caching via OpenRouter — cache reads bill at 0.1x the input rate. - Wire the Cache Invalidation table: flush per-model keys on model version updates, flush everything on system prompt changes, and let TTL handle the rest.
In-Memory Cache
import os, hashlib, json, time
from typing import Optional
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},
)
class LLMCache:
def __init__(self, ttl_seconds: int = 3600):
self._cache: dict[str, tuple[dict, float]] = {}
self._ttl = ttl_seconds
self.hits = 0
self.misses = 0
def _key(self, model: str, messages: list, **kwargs) -> str:
blob = json.dumps({"model": model, "messages": messages, **kwargs}, sort_keys=True)
return hashlib.sha256(blob.encode()).hexdigest()
def get(self, model: str, messages: list, **kwargs) -> Optional[dict]:
k = self._key(model, messages, **kwargs)
if k in self._cache:
data, ts = self._cache[k]
if time.time() - ts < self._ttl:
self.hits += 1
return data
del self._cache[k]
self.misses += 1
return None
def set(self, model: str, messages: list, response: dict, **kwargs):
k = self._key(model, messages, **kwargs)
self._cache[k] = (response, time.time())
cache = LLMCache(ttl_seconds=1800)
def cached_completion(messages, model="anthropic/claude-3.5-sonnet", **kwargs):
"""Only cache deterministic requests (temperature=0)."""
kwargs.setdefault("temperature", 0)
kwargs.setdefault("max_tokens", 1024)
cached = cache.get(model, messages, **kwargs)
if cached:
return cached
response = client.chat.completions.create(model=model, messages=messages, **kwargs)
result = {
"content": response.choices[0].message.content,
"model": response.model,
"usage": {"prompt": response.usage.prompt_tokens, "completion": response.usage.completion_tokens},
}
cache.set(model, messages, result, **kwargs)
return result
Persistent Cache with Redis
import redis, json, hashlib
r = redis.Redis(host="localhost", port=6379, db=0)
def redis_cached_completion(messages, model="openai/gpt-4o-mini", ttl=3600, **kwargs):
"""Cache in Redis with automatic TTL expiry."""
kwargs["temperature"] = 0 # Must be deterministic
key = f"or:{hashlib.sha256(json.dumps({'m': model, 'msgs': messages, **kwargs}, sort_keys=True).encode()).hexdigest()}"
cached = r.get(key)
if cached:
return json.loads(cached)
response = client.chat.completions.create(model=model, messages=messages, **kwargs)
result = {
"content": response.choices[0].message.content,
"model": response.model,
"tokens": response.usage.prompt_tokens + response.usage.completion_tokens,
}
r.setex(key, ttl, json.dumps(result))
return result
Anthropic Prompt Caching via OpenRouter
Anthropic models on OpenRouter support prompt caching -- large system prompts are cached server-side, reducing input cost by 90% on cache hits.
# Mark large static content blocks with cache_control
response = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[
{
"role": "system",
"content": [
{
"type": "text",
"text": "You are an expert. Here is the full source:\n" + large_context,
"cache_control": {"type": "ephemeral"}, # Cache this block
}
],
},
{"role": "user", "content": "What does the main() function do?"},
],
max_tokens=1024,
)
# First call: cache_creation_input_tokens charged at 1.25x
# Subsequent: cache_read_input_tokens charged at 0.1x (90% savings)
Cache Key Design
def cache_key(model: str, messages: list, **params) -> str:
"""Deterministic cache key. Include everything that affects output.
Include: model ID (with variant like :floor), messages, temperature,
max_tokens, top_p, transforms, provider routing.
Exclude: stream (doesn't affect content), HTTP-Referer, X-Title.
"""
canonical = json.dumps({
"model": model, "messages": messages,
"temperature": params.get("temperature", 0),
"max_tokens": params.get("max_tokens"),
"top_p": params.get("top_p"),
}, sort_keys=True)
return hashlib.sha256(canonical.encode()).hexdigest()
Cache Invalidation
| Trigger | Action | Why |
|---|---|---|
| Model version update | Flush keys for that model | New version may give different outputs |
| System prompt change | Flush all keys | Output semantics changed |
| TTL expiry | Automatic eviction | Prevents stale data |
| Manual purge | r.delete(key) or clear by prefix | Debugging or policy change |
Output
- Cached completion payloads returned without an API round-trip:
{"content", "model", "usage"}from the in-memory cache or{"content", "model", "tokens"}from Redis - Redis keys of the form
or:<sha256-of-canonical-request>that expire automatically via TTL - Hit/miss counters and a
hit_ratefigure you can use to justify the caching infrastructure - On Anthropic models,
cache_creation_input_tokensbilled at 1.25x on the first call andcache_read_input_tokensat 0.1x (90% savings) on subsequent hits
Examples
Two identical deterministic calls through the ResponseCache from the references — the second returns instantly from cache:
result1 = cached_completion("What is Python?") # [Cache MISS] key=3f8a92c1... (stored)
result2 = cached_completion("What is Python?") # [Cache HIT] key=3f8a92c1...
print(f"Hit rate: {cache.hit_rate:.0%}") # Hit rate: 50%
More worked examples, including a TypeScript Redis-style cache: references/examples.md.
Error Handling
| Error | Cause | Fix |
|---|---|---|
| Stale cache response | TTL too long | Reduce TTL or version cache keys |
| Cache miss storm | Cold start or invalidation | Warm cache with common queries at deploy |
| Redis connection error | Redis down | Fall through to direct API call |
| Non-deterministic cache | temperature > 0 cached | Only cache when temperature=0 |
Enterprise Considerations
- Only cache deterministic requests (
temperature=0) -- non-zero temperatures produce different outputs each time - Use Anthropic prompt caching for large system prompts (RAG context) -- 90% cost reduction on cache hits
- Set TTL based on content freshness needs (30 min for dynamic, 24h for reference data)
- Track cache hit rate to justify caching infrastructure cost
- Use Redis or Memcached for multi-instance deployments; in-memory only works for single-process
- Version cache keys when updating system prompts or switching model versions
References
- Examples | Errors
- Prompt Caching | Models API
What ships with it: 8 files
15.6 KB alongside SKILL.md
references/
- cache-invalidation.md990 B
- caching-strategies.md2.4 KB
- cost-savings-analysis.md1.4 KB
- errors.md478 B
- examples.md4.2 KB
- in-memory-caching.md2.5 KB
- redis-caching.md2.0 KB
- semantic-caching.md1.6 KB
Gives 0 of the 12 instructions most roadmap strategy skills give in ~2.1k tokens
Counted across 614 of the 719 authors here whose files we hold, read 2026-09-06
- Check for product marketing context firstin 39 of 614, across 16 files
- Use compact with a custom summaryin 32 of 614, across 16 files
- Compact after planningin 30 of 614, across 16 files
- Do not compact mid-implementationin 30 of 614, across 17 files
- Structure launch marketing across three channel typesin 21 of 614, across 5 files
- Read the compaction suggestionin 20 of 614, across 10 files
- Write before compactingin 17 of 614, across 11 files
- Write important context to files before compactingin 16 of 614, across 5 files
- Recruit early users one-on-onein 16 of 614, across 3 files
- Identify 3-5 core content pillarsin 15 of 614, across 9 files
- Target a specific keyword or questionin 14 of 614, across 4 files
- Stagger announcements to maintain momentumin 14 of 614, across 4 files
Said here and by no other author read
- Confirm requests are deterministic before caching
- Include model and messages in cache keys
- Mark large static content blocks with cache control
- Flush per model keys on version updates
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.