feat(proxy): T-P6 语义缓存(L1/L2 倒排+singleflight+SSE 回放,M2 核心)

- semcache.py:SemanticCache——L1 精确(LRU max_entries=30万)+ L2 字符 2/3-gram
  倒排索引(启动自 sqlite q_norm 重建)+ 加权 Jaccard(3-gram 权 2)+
  共享 gram>=3 候选门限 + 阈值 0.92 + TTL 滑动过期 + L2 命中 5 次晋升 L1
  (别名键写回表);SingleFlight(dict[hash->Future] 上限 256/60s 超时降级);
  synth_sse_chunks 命中回放(分块 delta+finish+[DONE] 合法 SSE)
- ledger:semcache_rows/put_semcache/promote_semcache/purge_expired
- 测试 +10:gram/精确/语义上下阈值/TTL 假时钟/LRU/重建/晋升/singleflight/SSE 合法性,
  全量 422 passed
- 待接线:routes 缓存分支(T-P7 顺带接入,M2 完整闭环在压测前完成)
This commit is contained in:
tzt
2026-09-05 15:39:48 +08:00
parent 06545b150d
commit e41471c39c
4 changed files with 435 additions and 14 deletions
+45
View File
@@ -187,6 +187,51 @@ class Ledger(BillingMixin):
(used + 1, today, key_id))
return True
# ---------- 语义缓存持久化(T-P6,供 semcache.SemanticCache 调用) ----------
def semcache_rows(self, limit: int = 300000) -> List[Dict[str, Any]]:
"""全量缓存行(启动重建倒排索引)。"""
with self._lock, self._connect() as conn:
rows = conn.execute(
"SELECT cache_key, q_norm, answer, model, created_ts, ttl_ts,"
" doc_version, hits FROM semcache LIMIT ?", (limit,)).fetchall()
return [dict(r) for r in rows]
def put_semcache(self, cache_key: str, bucket: str, q_norm: str, answer: str,
model: str, created_ts: int, ttl_ts: int,
doc_version: int = 1) -> None:
"""写/覆盖一条缓存(幂等)。"""
with self._lock, self._connect() as conn:
conn.execute(
"INSERT OR REPLACE INTO semcache"
"(cache_key, bucket, q_norm, answer, model, created_ts, ttl_ts,"
" doc_version, hits) VALUES (?,?,?,?,?,?,?,?,0)",
(cache_key, bucket, q_norm, answer, model, created_ts, ttl_ts,
doc_version))
def promote_semcache(self, source_key: str, alias_key: str, hits: int) -> None:
"""L2 -> L1 晋升:以别名键复制一行(原行保留审计)。"""
with self._lock, self._connect() as conn:
row = conn.execute(
"SELECT * FROM semcache WHERE cache_key = ?", (source_key,)).fetchone()
if row is None:
return
conn.execute(
"INSERT OR REPLACE INTO semcache"
"(cache_key, bucket, q_norm, answer, model, created_ts, ttl_ts,"
" doc_version, hits) VALUES (?,?,?,?,?,?,?,?,?)",
(alias_key, row["bucket"], row["q_norm"], row["answer"],
row["model"], row["created_ts"], row["ttl_ts"],
row["doc_version"], hits))
conn.execute("UPDATE semcache SET hits = ? WHERE cache_key = ?",
(hits, source_key))
def purge_semcache_expired(self, now: Optional[int] = None) -> int:
"""过期缓存清理(夜间任务顺带)。"""
now = int(now if now is not None else time.time())
with self._lock, self._connect() as conn:
cur = conn.execute("DELETE FROM semcache WHERE ttl_ts < ?", (now,))
return cur.rowcount
# ---------- 自省(测试/验收用) ----------
def table_names(self) -> List[str]:
"""列出已建表名(测试验收)。"""