- gateway/sense/decision_cache.py:DecisionCache(sha256(scope+文本) 键、 LRU + TTL 60s、4096 条上限、时钟可注入),对齐 auth._AuthCache 进程内模式 - Grader:仅 live 模式缓存纯决策负载(probs/head_version/vec); 命中跳过 embed + 线性头预测;tier 仍按当前 conformal 阈值即时重算; 观察落盘(observer.log)不因缓存命中跳过——校准数据完整性不受影响 - collect/shadow 模式不走缓存(校准必须全量);invalidate() 同步清空缓存 (阈值/工件切换即时生效,TTL 仅兜底陈旧) pytest 455 passed(T-X2 后 447 + 8)
75 lines
2.7 KiB
Python
75 lines
2.7 KiB
Python
"""路由决策缓存(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}
|