Pixedar's picture
Deploy TraceScope API
ce11d27
Raw
History Blame Contribute Delete
5.02 kB
"""
LLM response cache backed by SQLite.
Avoids redundant API calls by caching responses keyed on
SHA-256(model_name || system_prompt || user_prompt).
"""
from __future__ import annotations
import sqlite3
import time
from typing import Optional
from tracescope.utils.hashing import sha256_key
class LLMResponseCache:
"""SQLite-backed LLM response cache.
Args:
db_path: Path to the SQLite database file.
enabled: If False, all lookups miss and stores are skipped.
"""
def __init__(self, db_path: str, enabled: bool = True):
self.db_path = db_path
self.enabled = enabled
if enabled:
self._conn = sqlite3.connect(db_path, check_same_thread=False)
self._init_table()
else:
self._conn = None
def _init_table(self):
self._conn.execute(
"""
CREATE TABLE IF NOT EXISTS llm_cache (
hash TEXT PRIMARY KEY,
model_name TEXT NOT NULL,
response TEXT NOT NULL,
created_at REAL NOT NULL
)
"""
)
self._conn.commit()
def get(self, model_name: str, system_prompt: str, user_prompt: str) -> Optional[str]:
"""Look up a cached response. Returns None on miss."""
if not self.enabled:
return None
key = sha256_key(model_name, system_prompt, user_prompt)
row = self._conn.execute(
"SELECT response FROM llm_cache WHERE hash = ?", (key,)
).fetchone()
return row[0] if row else None
def put(self, model_name: str, system_prompt: str, user_prompt: str, response: str):
"""Store a response in the cache."""
if not self.enabled:
return
key = sha256_key(model_name, system_prompt, user_prompt)
self._conn.execute(
"""
INSERT OR REPLACE INTO llm_cache (hash, model_name, response, created_at)
VALUES (?, ?, ?, ?)
""",
(key, model_name, response, time.time()),
)
self._conn.commit()
def clear(self):
"""Clear all cached responses."""
if self._conn:
self._conn.execute("DELETE FROM llm_cache")
self._conn.commit()
def close(self):
if self._conn:
self._conn.close()
class ResultCache:
"""SQLite-backed cache for expensive ML computation results.
Caches clustering, dimension reduction, and velocity grid results
keyed on embeddings fingerprint (matching Android's DimensionReducerAdapter).
Uses the same SQLite database as LLMResponseCache but a separate table.
Args:
db_path: Path to the SQLite database file.
enabled: If False, all lookups miss and stores are skipped.
"""
def __init__(self, db_path: str, enabled: bool = True):
self.db_path = db_path
self.enabled = enabled
if enabled:
self._conn = sqlite3.connect(db_path, check_same_thread=False)
self._init_table()
else:
self._conn = None
def _init_table(self):
self._conn.execute(
"""
CREATE TABLE IF NOT EXISTS result_cache (
key TEXT PRIMARY KEY,
step TEXT NOT NULL,
data BLOB NOT NULL,
created_at REAL NOT NULL
)
"""
)
self._conn.commit()
def get_result(self, step: str, fingerprint: str) -> Optional[bytes]:
"""Look up a cached computation result.
Args:
step: Pipeline step name (e.g. "clustering", "dim_reduction", "velocity_grid").
fingerprint: Embeddings fingerprint (SHA-256 hex digest).
Returns:
Cached data as bytes, or None on miss.
"""
if not self.enabled:
return None
key = sha256_key(step, fingerprint)
row = self._conn.execute(
"SELECT data FROM result_cache WHERE key = ?", (key,)
).fetchone()
return row[0] if row else None
def put_result(self, step: str, fingerprint: str, data: bytes):
"""Store a computation result in the cache.
Args:
step: Pipeline step name.
fingerprint: Embeddings fingerprint.
data: Result data as bytes (JSON-encoded or raw numpy).
"""
if not self.enabled:
return
key = sha256_key(step, fingerprint)
self._conn.execute(
"""
INSERT OR REPLACE INTO result_cache (key, step, data, created_at)
VALUES (?, ?, ?, ?)
""",
(key, step, data, time.time()),
)
self._conn.commit()
def clear(self):
"""Clear all cached results."""
if self._conn:
self._conn.execute("DELETE FROM result_cache")
self._conn.commit()
def close(self):
if self._conn:
self._conn.close()