feat(govdoc): 新增内部公文模块全链路(后端58+前端11文件)
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""LLM 响应缓存(SQLite)。
|
||||
|
||||
缓存键 = sha256(model + canonical_json(messages, temperature, top_p, max_tokens))。
|
||||
仅缓存成功返回的文本;JSON 解析失败、API 错误、超时一律不入库。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import sqlite3
|
||||
import time
|
||||
from pathlib import Path
|
||||
from threading import Lock
|
||||
from typing import Any
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
_SCHEMA = """
|
||||
CREATE TABLE IF NOT EXISTS llm_cache (
|
||||
cache_key TEXT PRIMARY KEY,
|
||||
model TEXT NOT NULL,
|
||||
response_text TEXT NOT NULL,
|
||||
created_at REAL NOT NULL,
|
||||
hit_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_hit_at REAL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_llm_cache_created ON llm_cache(created_at);
|
||||
"""
|
||||
|
||||
# 影响响应的关键参数。其他 kwargs 不入 hash(如 stream/timeout)。
|
||||
_KEY_PARAMS = ("temperature", "top_p", "max_tokens", "response_format")
|
||||
|
||||
|
||||
def _canonical(messages: list[dict[str, str]], **kwargs: Any) -> str:
|
||||
payload = {
|
||||
"messages": messages,
|
||||
"params": {k: kwargs.get(k) for k in _KEY_PARAMS},
|
||||
}
|
||||
return json.dumps(payload, sort_keys=True, ensure_ascii=False)
|
||||
|
||||
|
||||
def make_key(model: str, messages: list[dict[str, str]], **kwargs: Any) -> str:
|
||||
raw = model + "\x00" + _canonical(messages, **kwargs)
|
||||
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
class LlmCache:
|
||||
def __init__(self, path: str | Path):
|
||||
self.path = Path(path)
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self._lock = Lock()
|
||||
self._conn = sqlite3.connect(str(self.path), check_same_thread=False)
|
||||
self._conn.executescript(_SCHEMA)
|
||||
self._conn.commit()
|
||||
|
||||
def get(self, key: str) -> str | None:
|
||||
with self._lock:
|
||||
row = self._conn.execute(
|
||||
"SELECT response_text FROM llm_cache WHERE cache_key = ?",
|
||||
(key,),
|
||||
).fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
self._conn.execute(
|
||||
"UPDATE llm_cache "
|
||||
"SET hit_count = hit_count + 1, last_hit_at = ? "
|
||||
"WHERE cache_key = ?",
|
||||
(time.time(), key),
|
||||
)
|
||||
self._conn.commit()
|
||||
return row[0]
|
||||
|
||||
def put(self, key: str, model: str, response_text: str) -> None:
|
||||
if not response_text:
|
||||
return
|
||||
with self._lock:
|
||||
self._conn.execute(
|
||||
"INSERT OR IGNORE INTO llm_cache "
|
||||
"(cache_key, model, response_text, created_at) "
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
(key, model, response_text, time.time()),
|
||||
)
|
||||
self._conn.commit()
|
||||
|
||||
def stats(self) -> dict[str, int]:
|
||||
with self._lock:
|
||||
row = self._conn.execute(
|
||||
"SELECT COUNT(*), COALESCE(SUM(hit_count), 0) FROM llm_cache"
|
||||
).fetchone()
|
||||
return {"entries": int(row[0] or 0), "total_hits": int(row[1] or 0)}
|
||||
|
||||
def clear(self) -> int:
|
||||
with self._lock:
|
||||
cur = self._conn.execute("DELETE FROM llm_cache")
|
||||
self._conn.commit()
|
||||
return cur.rowcount
|
||||
|
||||
def close(self) -> None:
|
||||
with self._lock:
|
||||
self._conn.close()
|
||||
Reference in New Issue
Block a user