Spaces:
Sleeping
Sleeping
| """JSONL response cache for LLM calls. | |
| Per research/experiment_matrix.md §0: cache key = SHA-256 of (prompt, model, seed, | |
| temperature). Cache is shipped with the submission so reviewers reproduce numbers | |
| without API access. | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import json | |
| from dataclasses import dataclass, asdict | |
| from pathlib import Path | |
| from typing import Iterator | |
| class CacheKey: | |
| """The canonical cache key. Hash determinism matters for reproducibility.""" | |
| prompt: str | |
| model: str | |
| seed: int | |
| temperature: float | |
| def to_hash(self) -> str: | |
| payload = json.dumps(asdict(self), sort_keys=True).encode("utf-8") | |
| return hashlib.sha256(payload).hexdigest() | |
| class CacheEntry: | |
| """A single cached LLM call. Shape mirrors the schema in experiment_matrix.md §2.""" | |
| key_hash: str | |
| prompt: str | |
| model: str | |
| seed: int | |
| temperature: float | |
| response_text: str | |
| input_tokens: int | |
| output_tokens: int | |
| latency_ms: int | |
| timestamp: str | |
| def to_jsonl(self) -> str: | |
| return json.dumps(asdict(self), ensure_ascii=False) + "\n" | |
| def from_jsonl(cls, line: str) -> "CacheEntry": | |
| return cls(**json.loads(line)) | |
| class JsonlCache: | |
| """Append-only JSONL cache. Reads load entire file into a hash-indexed dict. | |
| For a hackathon-scale cache (~30k entries, ~30MB), in-memory indexing is fine. | |
| """ | |
| def __init__(self, path: Path | str): | |
| self.path = Path(path) | |
| self.path.parent.mkdir(parents=True, exist_ok=True) | |
| self._index: dict[str, CacheEntry] = {} | |
| if self.path.exists(): | |
| self._load() | |
| def _load(self) -> None: | |
| with self.path.open("r", encoding="utf-8") as f: | |
| for line in f: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| entry = CacheEntry.from_jsonl(line) | |
| self._index[entry.key_hash] = entry | |
| def get(self, key: CacheKey) -> CacheEntry | None: | |
| return self._index.get(key.to_hash()) | |
| def put(self, key: CacheKey, entry: CacheEntry) -> None: | |
| self._index[key.to_hash()] = entry | |
| with self.path.open("a", encoding="utf-8") as f: | |
| f.write(entry.to_jsonl()) | |
| def __contains__(self, key: CacheKey) -> bool: | |
| return key.to_hash() in self._index | |
| def __len__(self) -> int: | |
| return len(self._index) | |
| def iter_entries(self) -> Iterator[CacheEntry]: | |
| yield from self._index.values() | |
| def deterministic_seed(*parts: str | int) -> int: | |
| """Per experiment_matrix.md §0: seed deterministic per cell tuple. | |
| Used to derive the seed from (persona_id, item_id, condition, architecture). | |
| """ | |
| payload = "|".join(str(p) for p in parts).encode("utf-8") | |
| return int.from_bytes(hashlib.sha256(payload).digest()[:4], "big") | |