"""路由决策缓存(T-X3,采纳 cortiq router_client 的决策哈希缓存思路)。 - key = sha256(scope + 0x00 + 归一文本);LRU + TTL(默认 60s / 4096 条, 对齐 auth._AuthCache 的进程内模式,D-P9 单进程前提)。 - 只缓存**纯决策负载**(如 probs/head_version/vec);调用方的观察与审计 落盘职责不因缓存命中而跳过。 - 时钟可注入(now(monotonic)),供测试确定性。 """ from __future__ import annotations import hashlib import threading import time from collections import OrderedDict from typing import Any, Callable, Optional _DEFAULT_TTL_S = 60.0 _DEFAULT_MAX = 4096 class DecisionCache: """进程内决策缓存:LRU + TTL,键为 (scope, 归一文本) 的 sha256。""" def __init__(self, maxsize: int = _DEFAULT_MAX, ttl_s: float = _DEFAULT_TTL_S, now: Optional[Callable[[], float]] = None): self._max = max(1, int(maxsize)) self._ttl = float(ttl_s) self._now = now or time.monotonic self._data: "OrderedDict[str, tuple[float, Any]]" = OrderedDict() self._lock = threading.Lock() self.hits = 0 self.misses = 0 @staticmethod def key(scope: str, text: str) -> str: """缓存键:scope 隔离消费方/模式,文本归一由调用方完成。""" return hashlib.sha256(f"{scope}\x00{text}".encode("utf-8")).hexdigest() def get(self, scope: str, text: str) -> Optional[Any]: now = self._now() k = self.key(scope, text) with self._lock: item = self._data.get(k) if item is None: self.misses += 1 return None ts, payload = item if now - ts > self._ttl: self._data.pop(k, None) self.misses += 1 return None self._data.move_to_end(k) self.hits += 1 return payload def put(self, scope: str, text: str, payload: Any) -> None: k = self.key(scope, text) with self._lock: self._data[k] = (self._now(), payload) self._data.move_to_end(k) while len(self._data) > self._max: self._data.popitem(last=False) def clear(self) -> None: """清空条目(阈值/工件版本切换时由 Grader.invalidate 调用)。""" with self._lock: self._data.clear() def stats(self) -> dict: with self._lock: return {"entries": len(self._data), "hits": self.hits, "misses": self.misses, "hit_rate": round(self.hits / (self.hits + self.misses), 4) if (self.hits + self.misses) else 0.0}