A production-grade semantic caching library for LLM applications. Reduce latency and cost by serving semantically similar responses from cache.
from semacache import CacheMetadata, CachePolicy, SemanticCache
async with SemanticCache.in_memory(namespace="demo") as cache:
result = await cache.get_or_generate(
query="What is the capital of France?",
generator=lambda: "Paris",
metadata=CacheMetadata(agent="bot"),
policy=CachePolicy.READ,
)
print(result.status, result.value)- Semantic caching — cache hits on semantically similar (not just identical) queries with configurable similarity threshold
- Exact caching — fast SHA-256 keyed lookups as the first cache layer
- Dual-layer architecture — exact + semantic cache for optimal latency
- Pluggable backends — in-memory (default), Redis, or custom via protocols
- Flexible deployment — single-process in-memory or distributed with Redis
- Request coalescing — deduplicate concurrent identical LLM requests
- Multi-tenant — namespace and tenant isolation built-in
- Type-safe — generic
SemanticCache[T]andCacheResult[T] - FastAPI integration — REST API for cache query, stats, invalidation, warmup
- Extensible — protocols for stores, embeddings, serialization, validation, eviction, coalescing, metrics
- Configurable failure policies — raise, bypass, return_none on failures
- Response validation — reject empty or malformed responses before caching
- Health checks —
cache.health()returns per-component status - Serialization — pluggable
ValueSerializer[T]with JSON support
pip install semacacheOptional extras:
pip install "semacache[fastapi]" # FastAPI integration
pip install "semacache[prometheus]" # Prometheus metricsRequires Python 3.11+.
import asyncio
from semacache import CacheMetadata, CachePolicy, SemanticCache
async def main():
async with SemanticCache.in_memory(namespace="quickstart", ttl_seconds=3600) as cache:
meta = CacheMetadata(agent="demo")
r1 = await cache.get_or_generate(
query="What is semantic caching?",
generator=lambda: "Caching based on meaning, not exact keys.",
metadata=meta,
policy=CachePolicy.READ,
)
print(f"{r1.status}: {r1.value}") # miss: ...
r2 = await cache.get_or_generate(
query="What is semantic caching?",
generator=lambda: "Caching based on meaning, not exact keys.",
metadata=meta,
policy=CachePolicy.READ,
)
print(f"{r2.status}: {r2.value}") # exact_hit: ...
r3 = await cache.get_or_generate(
query="Tell me about semantic caching",
generator=lambda: "Caching based on meaning, not exact keys.",
metadata=meta,
policy=CachePolicy.READ,
)
print(f"{r3.status}: {r3.value}") # semantic_hit: ...
asyncio.run(main())from semacache import SemanticCache, SemanticCacheConfig
config = SemanticCacheConfig(
namespace="my-app",
ttl_seconds=3600,
exact_cache={"provider": "redis", "url": "redis://localhost:6379/0"},
semantic_cache={"provider": "redis", "enabled": True, "similarity_threshold": 0.9},
embeddings={"provider": "ollama", "model": "nomic-embed-text"},
)
cache = SemanticCache(config=config)config = SemanticCacheConfig.from_yaml("semacache.yaml")# semacache.yaml
namespace: my-app
ttl_seconds: 3600
exact_cache:
provider: redis
url: redis://localhost:6379/0
semantic_cache:
provider: redis
enabled: true
similarity_threshold: 0.9config = SemanticCacheConfig() # reads SEMACACHE_* env vars
# e.g. SEMACACHE_NAMESPACE=my-app SEMACACHE_TTL_SECONDS=7200from semacache import semantic_cached
cache = SemanticCache.in_memory(namespace="qa")
@semantic_cached(cache, policy=CachePolicy.READ)
async def ask(question: str) -> str:
return f"Answer to: {question}"# Lookup only
lookup = await cache.lookup(query="What is Python?", metadata=meta)
if lookup.hit:
return lookup.entry.response
# Store only
await cache.store(query="What is Python?", value="A programming language", metadata=meta)
# Get stats
stats = await cache.get_stats()
print(stats.hit_rate)
# Invalidate
result = await cache.invalidate(InvalidateRequest(tenant="acme"))
print(f"Invalidated {result.invalidated_count} entries")
# Clear
count = await cache.clear()from semacache import CachePolicy
# Normal operation — read from cache, fall back to generator
policy = CachePolicy.READ
# Bypass cache — always call generator, don't store result
policy = CachePolicy.BYPASS
# Refresh — always call generator, update cache
policy = CachePolicy.REFRESH
# Disabled — skip cache entirely
policy = CachePolicy.DISABLEDfrom semacache import CacheMetadata
meta = CacheMetadata(
tenant="acme-corp", # Multi-tenant isolation
user_scope="user-abc123", # Per-user namespace
model="gpt-4", # Track which model generated the response
agent="support-bot", # Logical agent name
extra={"custom_field": "x"}, # Arbitrary metadata dict
)from semacache import JSONSerializer, SemanticCache
# Store complex types as JSON
cache: SemanticCache[str] = SemanticCache.in_memory(
serializer=JSONSerializer(),
)health = await cache.health()
print(f"Healthy: {health.healthy}")
print(f"Exact cache: {health.exact_cache}")
print(f"Semantic cache: {health.semantic_cache}")Prevents duplicate LLM calls when multiple requests arrive simultaneously for the same cache miss:
config = SemanticCacheConfig(
coalescing={
"enabled": True,
"mode": "memory", # "redis" for distributed coalescing
"lock_timeout_seconds": 30,
},
)Configure how the cache behaves when downstream components fail:
from semacache import SemanticCacheConfig
config = SemanticCacheConfig(
failure_policy={
"on_cache_read_failure": "bypass", # raise, return_none, bypass
"on_cache_write_failure": "log", # raise, log
"on_embedding_failure": "raise", # raise, return_none
},
)config = SemanticCacheConfig(
max_concurrent_llm_requests=5, # Max concurrent LLM calls
llm_request_queue_timeout_seconds=60, # Wait timeout for concurrency slot
request_timeout_seconds=120, # Per-request timeout
)from semacache import SemanticCache
from semacache.integrations.fastapi import create_cache_app
cache = SemanticCache.in_memory(namespace="api")
app = create_cache_app(cache, require_api_key=False)Run with: uvicorn app:app
| Method | Path | Description |
|---|---|---|
| POST | /v1/query |
Query the cache |
| GET | /health/live |
Liveness probe |
| GET | /health/ready |
Readiness probe |
| GET | /v1/cache/stats |
Cache statistics |
| POST | /v1/cache/invalidate |
Invalidate cache entries |
| POST | /v1/cache/warm |
Pre-warm cache with entries |
app = create_cache_app(cache, api_keys=["sk-123"], api_key_header="X-API-Key")| Config field | Default | Description |
|---|---|---|
namespace |
"default" |
Logical cache namespace |
ttl_seconds |
3600 |
Entry TTL in seconds |
max_entries |
10000 |
Max cache entries |
max_memory_estimate_mb |
512 |
Max estimated memory |
eviction_strategy |
"ttl_lru" |
ttl, lru, lfu, size, ttl_lru |
serve_stale |
False |
Serve stale entries when available |
deterministic_test_mode |
False |
Deterministic embeddings for testing |
request_timeout_seconds |
120 |
Per-request timeout |
max_concurrent_llm_requests |
5 |
Max concurrent LLM calls |
exact_cache.provider |
"memory" |
"memory" or "redis" |
semantic_cache.provider |
"memory" |
"memory" or "redis" |
semantic_cache.enabled |
True |
Enable semantic cache |
semantic_cache.similarity_threshold |
0.85 |
Min similarity for semantic hit |
semantic_cache.similarity_strategy |
"cosine" |
"cosine" or "euclidean" |
embeddings.provider |
"ollama" |
"ollama" or "deterministic" |
embeddings.model |
"nomic-embed-text" |
Embedding model name |
coalescing.enabled |
True |
Enable request coalescing |
coalescing.mode |
"memory" |
"memory" or "redis" |
failure_policy.on_cache_read_failure |
"bypass" |
raise, return_none, bypass |
failure_policy.on_embedding_failure |
"raise" |
raise, return_none |
validation.reject_empty |
True |
Reject empty responses |
metrics.enabled |
False |
Enable metrics recording |
| Method | Returns | Description |
|---|---|---|
in_memory(namespace, ttl_seconds) |
SemanticCache |
Create cache with in-memory stores |
lookup(query, metadata) |
CacheLookupResult |
Look up a query without generating |
store(query, value, metadata) |
bool |
Store a value in cache |
get_or_generate(query, generator, metadata, policy) |
CacheResult[T] |
Primary cache-through API |
get_stats() |
CacheStats |
Cache statistics |
invalidate(request) |
InvalidateResult |
Invalidate matching entries |
clear() |
int |
Clear all entries |
warm(query, value, metadata) |
bool |
Pre-warm a cache entry |
health() |
HealthStatus |
Component health status |
close() |
None |
Clean up resources |
| Attribute | Type | Description |
|---|---|---|
.value |
T |
The cached/generated value |
.status |
CacheStatus |
Shortcut for cache_status |
.cache_status |
CacheStatus |
Exact/semantic hit, miss... |
.similarity_score |
float or None |
Similarity score if hit |
.execution_info |
CacheExecutionInfo or None |
Timing and metadata |
| Attribute | Type | Description |
|---|---|---|
tenant |
str |
Tenant identifier |
user_scope |
str or None |
User-scoped namespace |
model |
str or None |
LLM model name |
agent |
str |
Logical agent name |
extra |
dict[str, Any] |
Arbitrary metadata |
SemacacheError
├── CacheConfigurationError
├── CacheStoreError
├── EmbeddingError
├── CacheMissError
├── SerializationError
├── HealthCheckError
├── CacheClosedError
├── InvalidKeyError
└── ValidationErrorSee the examples/ directory:
basic_memory.py— Minimal in-memory example (no deps required)redis_cache.py— Redis + Ollama backenddecorator_usage.py— Using@semantic_cachedcustom_generator.py— Custom LLM generator functioncustom_embedding_provider.py— Pluggable embedderfastapi_integration.py— FastAPI app (run withuvicorn)distributed_coalescing.py— Distributed request coalescing with Redis
This library follows Semantic Versioning 2.0.0 (MAJOR.MINOR.PATCH).
- Patch — bug fixes, performance improvements, docs
- Minor — backward-compatible new features
- Major — breaking API changes (announced at least one minor release in advance with deprecation warnings)
See MIGRATION.md for a complete guide with before/after examples for every breaking change.
See EXTENSION.md for implementing custom:
- Embedding providers
- Cache stores (exact and semantic)
- Serializers
- Response validators
- Eviction strategies
- Request coalescers
- Metrics recorders
See ARCHITECTURE.md for:
- Directory structure
- Request flow sequence diagram
- Store adapter class diagram
- Extension point class diagram
See CONTRIBUTING.md.
MIT