cache.models
On this page
Core data models for the semantic-cache pipeline: thread-agnostic (no locks of their own — concurrency is managed by their owning components), serializable to/from plain dicts for storage backends, and self-describing.
BanditArm
@dataclass
class BanditArm:
alpha: float = 1.0
beta: float = 1.0
Overview
A Beta-distribution arm for Thompson Sampling. alpha tracks successes (plus prior),
beta tracks failures (plus prior). This is the statistical core that
CachePolicyLearner uses to rank and evict
IntelligentCacheEntry instances.
Methods
Example
from zerda.cache.models import BanditArm
arm = BanditArm()
arm.update_positive() # a cache hit was judged good
arm.update_negative(0.5) # a partial negative signal
print(arm.expected_reward) # -> 2.0 / (2.0 + 1.5) = 0.571...
print(arm.sample_reward()) # stochastic draw for exploration
CostRecord
@dataclass
class CostRecord:
embedding_cost_usd: float = 0.0
llm_input_cost_usd: float = 0.0
llm_output_cost_usd: float = 0.0
retrieval_cost_usd: float = 0.0
A per-entry breakdown of estimated USD costs, attached to every
IntelligentCacheEntry.cost_record. The total_usd property sums
all four fields. to_dict() returns a dict with keys embedding_usd, llm_input_usd,
llm_output_usd, retrieval_usd, and total_usd. Produced by
CostModel and set on entries by
CacheManager.put().
IntelligentCacheEntry
@dataclass
class IntelligentCacheEntry:
entry_id: str
tenant_id: str = "default"
namespace: str = "global"
raw_query: str = ""
normalized_query: str = ""
response: Any = None
response_tokens: int = 0
query_tokens: int = 0
embedding: Optional[np.ndarray] = None
embedding_model: str = ""
confidence_score: float = 1.0
similarity_score: float = 1.0
quality_score: float = 1.0
bandit_arm: BanditArm
usage_count: int = 0
success_hits: int = 0
failure_hits: int = 0
cost_record: CostRecord
created_at: float
last_access: float
ttl_s: Optional[float] = None
size_bytes: int = 0
is_shared: bool = False
content_hash: str = ""
creation_route: RoutingDecision = RoutingDecision.LLM_FALLBACK
Overview
The extended cache entry used throughout the semantic-cache pipeline. Beyond a plain
key/value pair, it carries the RL bandit state, cost accounting, multi-tenant metadata, and
a content hash for integrity verification — everything
CacheManager, CachePolicyLearner,
SecurityGuard, TenantManager, and
CostModel need to make decisions about a cached answer.
entry_id has no default (it's the one required positional field), but in practice
CacheManager.put() always sets it equal to the
entry's exact cache key for deduplication purposes.
__post_init__
Two side effects run automatically after construction:
- If
content_hashis falsy, compute it via_compute_hash(). - If
size_bytes == 0, compute it via_estimate_size().
Private helpers
Access & lifecycle methods
eviction_score(reward_w=0.50, usage_w=0.30, recency_w=0.20, decay=0.995) -> float
Composite score where lower means higher eviction priority. Combines:
reward_score—bandit_arm.expected_rewardusage_norm—min(1.0, usage_count / 100.0)recency_norm—max(0.0, 1.0 - idle_s() / 86400.0)(a 24-hour recency window)
score = (reward_w*reward_score + usage_w*usage_norm + recency_w*recency_norm)
* decay ** max(0, usage_count - 1)
The multiplicative decay ** (usage_count - 1) term means, counter-intuitively, that
more-used entries have their composite score shrunk more by this formula (since the
exponent grows with usage_count), all else being equal — an aspect worth understanding
before tuning the weights; see Known Issues for discussion.
Called by CachePolicyLearner.eviction_ranking()
with decay=cfg.usage_decay, leaving reward_w/usage_w/recency_w at their defaults
(the analogous EvictionConfig.reward_weight/usage_weight/recency_weight fields are not
threaded through to this call in the analyzed source).
Serialization
to_storage_dict(self) -> Dict[str, Any]
Returns a JSON-serializable dict with every field, converting embedding to a plain list
(.tolist()) or None, bandit_arm/cost_record to their own to_dict() forms, and
creation_route to its .value string.
from_storage_dict(cls, d: Dict[str, Any]) -> IntelligentCacheEntry (classmethod)
The inverse operation, reconstructing numpy arrays, BanditArm, CostRecord, and
RoutingDecision from the plain dict. Used by every storage backend
(SQLiteStorage, RedisStorage) on read.
[!WARNING]
from_storage_dictuses direct dict indexing, not.get(), for most fields Nearly every field access infrom_storage_dict(e.g.d["entry_id"],d["tenant_id"],d["usage_count"]) uses[]rather than.get(...)with a fallback. A payload written by a different (e.g. older or newer) version of this dataclass that is missing any of these keys will raiseKeyErrorrather than degrading gracefully. The exceptions are the nestedcost_recordsub-fields, which do use.get(..., 0.0).
Examples
from zerda.cache.models import IntelligentCacheEntry
import numpy as np
entry = IntelligentCacheEntry(
entry_id="abc123",
tenant_id="acme",
raw_query="What is RAG?",
normalized_query="what is rag",
response="Retrieval-Augmented Generation combines retrieval with generation.",
embedding=np.random.rand(128).astype(np.float32),
ttl_s=3600,
)
entry.touch()
entry.record_hit(positive=True)
print(entry.eviction_score())
payload = entry.to_storage_dict()
restored = IntelligentCacheEntry.from_storage_dict(payload)
assert restored.content_hash == entry.content_hash
Related APIs
CacheManager— creates, stores, and retrieves these entriesCachePolicyLearner— reads/writesbandit_arm, callseviction_scoreSecurityGuard.verify_entry_integrity— recomputes and comparescontent_hashBaseStorage— persists viato_storage_dict/from_storage_dict
CacheLookupResult
@dataclass
class CacheLookupResult:
hit: bool = False
decision: RoutingDecision = RoutingDecision.LLM_FALLBACK
entry: Optional[IntelligentCacheEntry] = None
similarity: float = 0.0
latency_ms: float = 0.0
cost_saved_usd: float = 0.0
The return type of CacheManager.get() /
get_sync(). Exposes a convenience response property:
@property
def response(self) -> Any:
return self.entry.response if self.entry else None
so callers can write result.response instead of result.entry.response if result.entry else None.
Example
result = await manager.get("hello")
if result.hit:
print(result.decision, result.similarity, result.response)
else:
print("miss — call your LLM")
TenantQuotaState
@dataclass
class TenantQuotaState:
tenant_id: str
memory_used_mb: float = 0.0
entry_count: int = 0
requests_this_min: int = 0
last_rpm_reset: float
memory_quota_mb: float = 512.0
max_entries: int = 10_000
request_quota_rpm: int = 1000
Mutable, in-memory quota-tracking state for one tenant, owned and updated by
TenantManager.
Related APIs
TenantManager— the sole owner/mutator of this stateQuotaExceeded— raised when these checks fail