superlocalmemory 3.5.8 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ATTRIBUTION.md +24 -0
- package/CHANGELOG.md +86 -0
- package/README.md +142 -35
- package/package.json +1 -1
- package/pyproject.toml +2 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/cache_cmd.py +198 -0
- package/src/superlocalmemory/cli/commands.py +80 -2
- package/src/superlocalmemory/cli/compress_cmd.py +179 -0
- package/src/superlocalmemory/cli/help_cmd.py +197 -0
- package/src/superlocalmemory/cli/main.py +122 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +178 -0
- package/src/superlocalmemory/cli/optimize_constants.py +31 -0
- package/src/superlocalmemory/cli/proxy_cmd.py +104 -0
- package/src/superlocalmemory/core/config.py +5 -0
- package/src/superlocalmemory/core/engine.py +23 -0
- package/src/superlocalmemory/core/mcp_embedder_proxy.py +89 -0
- package/src/superlocalmemory/llm/backbone.py +10 -4
- package/src/superlocalmemory/mcp/server.py +34 -0
- package/src/superlocalmemory/mcp/tools_v3.py +6 -2
- package/src/superlocalmemory/optimize/NOTICE +11 -0
- package/src/superlocalmemory/optimize/__init__.py +0 -0
- package/src/superlocalmemory/optimize/adapters/__init__.py +68 -0
- package/src/superlocalmemory/optimize/adapters/_agent_registry.py +120 -0
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +115 -0
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +125 -0
- package/src/superlocalmemory/optimize/adapters/wrap.py +218 -0
- package/src/superlocalmemory/optimize/cache/__init__.py +31 -0
- package/src/superlocalmemory/optimize/cache/boundary_store.py +455 -0
- package/src/superlocalmemory/optimize/cache/centroid_store.py +158 -0
- package/src/superlocalmemory/optimize/cache/context_key.py +67 -0
- package/src/superlocalmemory/optimize/cache/exact.py +85 -0
- package/src/superlocalmemory/optimize/cache/invalidation.py +36 -0
- package/src/superlocalmemory/optimize/cache/key_builder.py +98 -0
- package/src/superlocalmemory/optimize/cache/manager.py +452 -0
- package/src/superlocalmemory/optimize/cache/semantic.py +568 -0
- package/src/superlocalmemory/optimize/cache/stampede.py +50 -0
- package/src/superlocalmemory/optimize/compress/__init__.py +17 -0
- package/src/superlocalmemory/optimize/compress/align.py +153 -0
- package/src/superlocalmemory/optimize/compress/ccr.py +157 -0
- package/src/superlocalmemory/optimize/compress/extractive_code.py +311 -0
- package/src/superlocalmemory/optimize/compress/extractive_json.py +72 -0
- package/src/superlocalmemory/optimize/compress/prose_llmlingua.py +77 -0
- package/src/superlocalmemory/optimize/compress/router.py +548 -0
- package/src/superlocalmemory/optimize/config/__init__.py +35 -0
- package/src/superlocalmemory/optimize/config/defaults.py +48 -0
- package/src/superlocalmemory/optimize/config/schema.py +255 -0
- package/src/superlocalmemory/optimize/config/store.py +209 -0
- package/src/superlocalmemory/optimize/metrics/__init__.py +8 -0
- package/src/superlocalmemory/optimize/metrics/counters.py +138 -0
- package/src/superlocalmemory/optimize/metrics/estimator.py +90 -0
- package/src/superlocalmemory/optimize/metrics/exporters.py +77 -0
- package/src/superlocalmemory/optimize/metrics/persistence.py +115 -0
- package/src/superlocalmemory/optimize/proxy/__init__.py +28 -0
- package/src/superlocalmemory/optimize/proxy/_helpers.py +257 -0
- package/src/superlocalmemory/optimize/proxy/anthropic_surface.py +171 -0
- package/src/superlocalmemory/optimize/proxy/gemini_surface.py +121 -0
- package/src/superlocalmemory/optimize/proxy/lifecycle.py +126 -0
- package/src/superlocalmemory/optimize/proxy/openai_surface.py +125 -0
- package/src/superlocalmemory/optimize/proxy/server.py +151 -0
- package/src/superlocalmemory/optimize/storage/__init__.py +0 -0
- package/src/superlocalmemory/optimize/storage/db.py +1016 -0
- package/src/superlocalmemory/optimize/storage/schema.py +184 -0
- package/src/superlocalmemory/server/routes/optimize.py +167 -0
- package/src/superlocalmemory/server/routes/v3_api.py +63 -1
- package/src/superlocalmemory/server/unified_daemon.py +105 -0
- package/src/superlocalmemory/ui/index.html +98 -0
- package/src/superlocalmemory/ui/js/ng-shell.js +5 -1
- package/src/superlocalmemory/ui/js/optimize.js +173 -0
- package/src/superlocalmemory.egg-info/PKG-INFO +144 -36
- package/src/superlocalmemory.egg-info/SOURCES.txt +51 -0
- package/src/superlocalmemory.egg-info/requires.txt +1 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# optimize/cache/semantic.py
|
|
2
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
3
|
+
# Licensed under AGPL-3.0-or-later
|
|
4
|
+
#
|
|
5
|
+
# LLD-03 — VCacheSemantic: verified semantic cache tier (Phase 3).
|
|
6
|
+
#
|
|
7
|
+
# Implements the vCache per-item online-MLE exploit/explore decision
|
|
8
|
+
# (arXiv:2502.03771, Eq. 9/10/11, Algorithm 2, Theorem 4.1) on top of:
|
|
9
|
+
# - SAFE-CACHE centroid defense (Nature Scientific Reports 2026)
|
|
10
|
+
# - Dual-threshold verify-and-rewrite (SLM proprietary extension)
|
|
11
|
+
# - Multi-turn context-aware keys (arXiv:2506.22791 §3)
|
|
12
|
+
# - CacheAttack mitigation (arXiv:2601.23088)
|
|
13
|
+
#
|
|
14
|
+
# OFF BY DEFAULT (OptimizeConfig.semantic_enabled = False).
|
|
15
|
+
# Exact cache (P1) always runs first; semantic only runs on exact miss.
|
|
16
|
+
# Fail-open: any scoring error → treat as miss, forward to provider.
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import hashlib
|
|
21
|
+
import logging
|
|
22
|
+
import random
|
|
23
|
+
import threading
|
|
24
|
+
import time
|
|
25
|
+
from typing import TYPE_CHECKING, Any
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
from superlocalmemory.optimize.cache.boundary_store import (
|
|
30
|
+
BoundaryStore,
|
|
31
|
+
PerItemBoundaryRecord,
|
|
32
|
+
)
|
|
33
|
+
from superlocalmemory.optimize.cache.centroid_store import (
|
|
34
|
+
CentroidStore,
|
|
35
|
+
_cosine_similarity,
|
|
36
|
+
)
|
|
37
|
+
from superlocalmemory.optimize.cache.context_key import ContextKeyBuilder
|
|
38
|
+
from superlocalmemory.optimize.cache.manager import SemanticTier
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from superlocalmemory.optimize.config.schema import OptimizeConfig
|
|
42
|
+
from superlocalmemory.optimize.storage.db import CacheDB
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
_EMBED_DIM: int = 768
|
|
47
|
+
_DEFAULT_MAX_TURNS: int = 6
|
|
48
|
+
_DEFAULT_CONTEXT_WINDOW: int = 3
|
|
49
|
+
_DEFAULT_RETURN_THRESHOLD: float = 0.98
|
|
50
|
+
_DEFAULT_VERIFY_LO: float = 0.90
|
|
51
|
+
_DEFAULT_ERROR_TARGET: float = 0.02
|
|
52
|
+
_DEFAULT_BOUNDARY_INIT: float = 0.95
|
|
53
|
+
_DEFAULT_CENTROID_FLOOR: float = 0.15
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class VCacheSemantic(SemanticTier):
|
|
57
|
+
"""Verified semantic cache tier.
|
|
58
|
+
|
|
59
|
+
Lookup flow (per SemanticTier.lookup call):
|
|
60
|
+
1. is_enabled() gate — return None if off.
|
|
61
|
+
2. Multi-turn guard — skip if turn_count > max_turns.
|
|
62
|
+
3. SAFE-CACHE centroid check — reject adversarial probe.
|
|
63
|
+
4. Lazy per-tenant warm of in-memory vector index.
|
|
64
|
+
5. ANN search (linear cosine scan) with HARD context-fp exclusion.
|
|
65
|
+
6. vCache per-item exploit/explore via MLE τ̂.
|
|
66
|
+
7. Dual-threshold decision (return / verify-and-rewrite / miss).
|
|
67
|
+
8. Latency padding (side-channel defense).
|
|
68
|
+
9. Return CachedResponse or None.
|
|
69
|
+
|
|
70
|
+
Storage flow:
|
|
71
|
+
- index_entry() persists vector to DB + in-memory index.
|
|
72
|
+
- learn() updates the per-item MLE model.
|
|
73
|
+
- is_enabled() reads config.semantic_enabled (hot-reloadable).
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
db: "CacheDB",
|
|
79
|
+
config: "OptimizeConfig",
|
|
80
|
+
) -> None:
|
|
81
|
+
self._db = db
|
|
82
|
+
self._config = config
|
|
83
|
+
# TODO(v3.7): when entry_count > 10_000, promote to sqlite-vec. Config flag: semantic_use_vec.
|
|
84
|
+
|
|
85
|
+
self._boundary_store = BoundaryStore(
|
|
86
|
+
db=db,
|
|
87
|
+
default_t=float(getattr(config, "semantic_boundary_init", _DEFAULT_BOUNDARY_INIT)),
|
|
88
|
+
default_gamma=10.0,
|
|
89
|
+
floor=float(getattr(config, "semantic_boundary_floor", 0.85)),
|
|
90
|
+
ceiling=float(getattr(config, "semantic_boundary_ceiling", 0.995)),
|
|
91
|
+
step=float(getattr(config, "semantic_boundary_step", 0.01)),
|
|
92
|
+
epsilon=float(getattr(config, "semantic_error_target", _DEFAULT_ERROR_TARGET)),
|
|
93
|
+
)
|
|
94
|
+
self._centroid_store = CentroidStore()
|
|
95
|
+
self._context_key_builder = ContextKeyBuilder(
|
|
96
|
+
window_turns=int(getattr(config, "semantic_context_window_turns", _DEFAULT_CONTEXT_WINDOW))
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# In-memory vector index: tenant_id → list of (entry_id, context_fp, vec)
|
|
100
|
+
# Mutated by _set_inner() (under _index_lock) and rebuilt lazily
|
|
101
|
+
# per-tenant by _lazy_warm_tenant().
|
|
102
|
+
self._index: dict[str, list[tuple[str, str, np.ndarray]]] = {}
|
|
103
|
+
self._index_lock = threading.RLock()
|
|
104
|
+
|
|
105
|
+
# RA-09 fix: per-tenant warm guard — prevents double-warm race.
|
|
106
|
+
self._warming: set[str] = set()
|
|
107
|
+
self._warming_lock = threading.Lock()
|
|
108
|
+
|
|
109
|
+
# Warm boundary records from DB at startup (lightweight — no I/O heavy).
|
|
110
|
+
try:
|
|
111
|
+
self._boundary_store._cache = self._boundary_store.load_all()
|
|
112
|
+
except Exception as exc:
|
|
113
|
+
logger.warning("VCacheSemantic: boundary warm failed (fail-open): %s", exc)
|
|
114
|
+
|
|
115
|
+
# ------------------------------------------------------------------
|
|
116
|
+
# SemanticTier ABC (INTERFACE-CONTRACT v2 §4)
|
|
117
|
+
# ------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
def is_enabled(self) -> bool:
|
|
120
|
+
"""Return True iff OptimizeConfig.semantic_enabled is True."""
|
|
121
|
+
return bool(getattr(self._config, "semantic_enabled", False))
|
|
122
|
+
|
|
123
|
+
def lookup(
|
|
124
|
+
self,
|
|
125
|
+
req: Any,
|
|
126
|
+
tenant_id: str,
|
|
127
|
+
embed: list[float] | np.ndarray | None,
|
|
128
|
+
) -> dict[str, Any] | None:
|
|
129
|
+
"""Return semantically similar cached response or None.
|
|
130
|
+
|
|
131
|
+
Fail-open: any exception → log at WARNING + return None.
|
|
132
|
+
"""
|
|
133
|
+
if not self.is_enabled():
|
|
134
|
+
return None
|
|
135
|
+
try:
|
|
136
|
+
if embed is None:
|
|
137
|
+
return None
|
|
138
|
+
vec = np.asarray(embed, dtype=np.float32)
|
|
139
|
+
if vec.shape[0] != _EMBED_DIM:
|
|
140
|
+
logger.debug(
|
|
141
|
+
"VCacheSemantic.lookup: skip — embed dim=%d (expected %d)",
|
|
142
|
+
vec.shape[0], _EMBED_DIM,
|
|
143
|
+
)
|
|
144
|
+
return None
|
|
145
|
+
return self._lookup_inner(req, tenant_id, vec)
|
|
146
|
+
except Exception as exc:
|
|
147
|
+
logger.warning(
|
|
148
|
+
"VCacheSemantic.lookup failed (fail-open): tenant=%s exc=%s",
|
|
149
|
+
tenant_id, exc, exc_info=True,
|
|
150
|
+
)
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
def learn(
|
|
154
|
+
self,
|
|
155
|
+
entry_id: str,
|
|
156
|
+
similarity: float,
|
|
157
|
+
was_correct: bool,
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Update the per-item MLE model with a new (similarity, correctness) pair.
|
|
160
|
+
|
|
161
|
+
Called by CacheManager on feedback. Delegates to BoundaryStore.record_outcome().
|
|
162
|
+
Fail-open.
|
|
163
|
+
"""
|
|
164
|
+
try:
|
|
165
|
+
self._boundary_store.record_outcome(
|
|
166
|
+
entry_id=entry_id,
|
|
167
|
+
similarity=similarity,
|
|
168
|
+
was_correct=was_correct,
|
|
169
|
+
)
|
|
170
|
+
except Exception as exc:
|
|
171
|
+
logger.warning(
|
|
172
|
+
"VCacheSemantic.learn failed (fail-open): entry=%s exc=%s",
|
|
173
|
+
entry_id, exc, exc_info=True,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def index_entry(
|
|
177
|
+
self,
|
|
178
|
+
req: Any,
|
|
179
|
+
tenant_id: str,
|
|
180
|
+
embed: list[float] | np.ndarray | None,
|
|
181
|
+
resp: Any,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Index a new response vector in the ANN index and DB.
|
|
184
|
+
|
|
185
|
+
INTERFACE-CONTRACT v2 §4 canonical signature. entry_id and
|
|
186
|
+
context_fp are derived internally (req → query text + tenant).
|
|
187
|
+
Fail-open.
|
|
188
|
+
"""
|
|
189
|
+
if not self.is_enabled():
|
|
190
|
+
return
|
|
191
|
+
try:
|
|
192
|
+
if embed is None:
|
|
193
|
+
return
|
|
194
|
+
messages = _extract_messages(req)
|
|
195
|
+
system = _extract_system(req)
|
|
196
|
+
query_text = self._build_query_text(messages, system)
|
|
197
|
+
entry_id = self._derive_entry_id(tenant_id, query_text)
|
|
198
|
+
context_fp = self._context_key_builder.build(messages, tenant_id)
|
|
199
|
+
self._set_inner(tenant_id, entry_id, embed, context_fp)
|
|
200
|
+
# Also persist the response under the surrogate entry_id so
|
|
201
|
+
# _fetch_response(entry_id) can retrieve it on a semantic hit.
|
|
202
|
+
if resp is not None:
|
|
203
|
+
try:
|
|
204
|
+
import json as _json
|
|
205
|
+
body = resp.body if hasattr(resp, "body") else (
|
|
206
|
+
resp if isinstance(resp, dict) else None
|
|
207
|
+
)
|
|
208
|
+
if body is None and hasattr(resp, "body_bytes"):
|
|
209
|
+
try:
|
|
210
|
+
body = _json.loads(resp.body_bytes)
|
|
211
|
+
except Exception:
|
|
212
|
+
body = None
|
|
213
|
+
if body is None and isinstance(resp, dict):
|
|
214
|
+
body = resp
|
|
215
|
+
if body is not None:
|
|
216
|
+
value = _json.dumps(body, separators=(",", ":")).encode("utf-8")
|
|
217
|
+
cache_key = f"sem:{entry_id}"
|
|
218
|
+
# Use set_with_entry_id to pin entry_id = surrogate directly,
|
|
219
|
+
# replacing the previous raw SQL UPDATE workaround.
|
|
220
|
+
self._db.set_with_entry_id(
|
|
221
|
+
key=cache_key,
|
|
222
|
+
tenant_id=tenant_id,
|
|
223
|
+
value=value,
|
|
224
|
+
entry_id=entry_id,
|
|
225
|
+
tags=["semantic"],
|
|
226
|
+
)
|
|
227
|
+
# Mark as semantic tier using CacheDB.execute (H-03 fix)
|
|
228
|
+
try:
|
|
229
|
+
self._db.execute(
|
|
230
|
+
"UPDATE llmcache_entries SET cache_tier = 'semantic' "
|
|
231
|
+
"WHERE cache_key = ? AND tenant_id = ?",
|
|
232
|
+
(cache_key, tenant_id),
|
|
233
|
+
)
|
|
234
|
+
except Exception as exc:
|
|
235
|
+
logger.debug(
|
|
236
|
+
"VCacheSemantic.index_entry: cache_tier update failed (non-fatal): %s",
|
|
237
|
+
exc,
|
|
238
|
+
)
|
|
239
|
+
except Exception as exc:
|
|
240
|
+
logger.debug(
|
|
241
|
+
"VCacheSemantic.index_entry: response persist failed (non-fatal): %s",
|
|
242
|
+
exc,
|
|
243
|
+
)
|
|
244
|
+
except Exception as exc:
|
|
245
|
+
logger.warning(
|
|
246
|
+
"VCacheSemantic.index_entry failed (fail-open): tenant=%s exc=%s",
|
|
247
|
+
tenant_id, exc, exc_info=True,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# ------------------------------------------------------------------
|
|
251
|
+
# Internal lookup
|
|
252
|
+
# ------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
def _lookup_inner(
|
|
255
|
+
self,
|
|
256
|
+
req: Any,
|
|
257
|
+
tenant_id: str,
|
|
258
|
+
vec: np.ndarray,
|
|
259
|
+
) -> dict[str, Any] | None:
|
|
260
|
+
"""Core lookup — called by lookup() after is_enabled() check."""
|
|
261
|
+
cfg = self._config
|
|
262
|
+
max_turns = int(getattr(cfg, "semantic_max_turns_for_semantic", _DEFAULT_MAX_TURNS))
|
|
263
|
+
messages = _extract_messages(req)
|
|
264
|
+
|
|
265
|
+
# Step 1: Multi-turn guard
|
|
266
|
+
turn_count = self._context_key_builder.turn_count(messages)
|
|
267
|
+
if turn_count > max_turns:
|
|
268
|
+
logger.debug(
|
|
269
|
+
"VCacheSemantic: skip (turn_count=%d > max=%d)",
|
|
270
|
+
turn_count, max_turns,
|
|
271
|
+
)
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
# Step 2: SAFE-CACHE centroid defense
|
|
275
|
+
if bool(getattr(cfg, "semantic_centroid_defense", True)):
|
|
276
|
+
distance_floor = float(
|
|
277
|
+
getattr(cfg, "semantic_centroid_distance_floor", _DEFAULT_CENTROID_FLOOR)
|
|
278
|
+
)
|
|
279
|
+
if self._centroid_store.is_adversarial(tenant_id, vec, distance_floor):
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
# Step 3: Lazy per-tenant warm
|
|
283
|
+
with self._index_lock:
|
|
284
|
+
is_warm = tenant_id in self._index
|
|
285
|
+
if not is_warm:
|
|
286
|
+
self._lazy_warm_tenant(tenant_id)
|
|
287
|
+
|
|
288
|
+
# Step 4: ANN search
|
|
289
|
+
context_fp = self._context_key_builder.build(messages, tenant_id)
|
|
290
|
+
best_entry_id, best_score = self._ann_search(tenant_id, vec, context_fp)
|
|
291
|
+
if best_entry_id is None:
|
|
292
|
+
return None
|
|
293
|
+
|
|
294
|
+
# Step 5: vCache exploit/explore (MLE τ̂ via record_outcome updates)
|
|
295
|
+
record = self._boundary_store.get(best_entry_id)
|
|
296
|
+
delta = float(getattr(cfg, "semantic_error_target", _DEFAULT_ERROR_TARGET))
|
|
297
|
+
if record.should_explore(best_score, delta=delta):
|
|
298
|
+
logger.debug(
|
|
299
|
+
"VCacheSemantic: explore (score=%.4f entry=%s t_hat=%.4f)",
|
|
300
|
+
best_score, best_entry_id, record.t_hat,
|
|
301
|
+
)
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
# Step 6: Dual-threshold decision
|
|
305
|
+
return self._dual_threshold_decision(
|
|
306
|
+
best_entry_id, best_score, cfg,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def _dual_threshold_decision(
|
|
310
|
+
self,
|
|
311
|
+
entry_id: str,
|
|
312
|
+
score: float,
|
|
313
|
+
cfg: "OptimizeConfig",
|
|
314
|
+
) -> dict[str, Any] | None:
|
|
315
|
+
"""Apply dual-threshold: return / verify-and-rewrite / miss.
|
|
316
|
+
|
|
317
|
+
SLM-proprietary extension (NOT vCache):
|
|
318
|
+
score >= return_threshold: return immediately
|
|
319
|
+
verify_lo <= score < return_threshold: verify-and-rewrite path
|
|
320
|
+
score < verify_lo: miss
|
|
321
|
+
"""
|
|
322
|
+
response = self._fetch_response(entry_id)
|
|
323
|
+
if response is None:
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
return_threshold = float(getattr(cfg, "semantic_return_threshold", _DEFAULT_RETURN_THRESHOLD))
|
|
327
|
+
verify_lo = float(getattr(cfg, "semantic_verify_lo", _DEFAULT_VERIFY_LO))
|
|
328
|
+
|
|
329
|
+
if score >= return_threshold:
|
|
330
|
+
return response
|
|
331
|
+
|
|
332
|
+
if verify_lo <= score < return_threshold:
|
|
333
|
+
verifier_model = str(getattr(cfg, "semantic_verifier_model", "") or "")
|
|
334
|
+
if not verifier_model:
|
|
335
|
+
logger.debug(
|
|
336
|
+
"VCacheSemantic: verify zone, no verifier configured — miss "
|
|
337
|
+
"(score=%.4f entry=%s)", score, entry_id,
|
|
338
|
+
)
|
|
339
|
+
return None
|
|
340
|
+
verified, rewritten = self._verify_and_rewrite(
|
|
341
|
+
entry_id, response, verifier_model,
|
|
342
|
+
)
|
|
343
|
+
if verified:
|
|
344
|
+
return rewritten if rewritten is not None else response
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
return None
|
|
348
|
+
|
|
349
|
+
def _lazy_warm_tenant(self, tenant_id: str) -> None:
|
|
350
|
+
"""Warm the in-memory vector index for a tenant on first access.
|
|
351
|
+
|
|
352
|
+
RA-09 fix: double-warm race eliminated via _warming guard.
|
|
353
|
+
"""
|
|
354
|
+
with self._warming_lock:
|
|
355
|
+
if tenant_id in self._warming:
|
|
356
|
+
return
|
|
357
|
+
self._warming.add(tenant_id)
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
rows = self._db.get_all_vectors(tenant_id=tenant_id)
|
|
361
|
+
if not rows:
|
|
362
|
+
with self._index_lock:
|
|
363
|
+
self._index[tenant_id] = []
|
|
364
|
+
return
|
|
365
|
+
entries: list[tuple[str, str, np.ndarray]] = []
|
|
366
|
+
for entry_id, blob in rows:
|
|
367
|
+
try:
|
|
368
|
+
v = np.frombuffer(blob, dtype=np.float32).copy()
|
|
369
|
+
if v.shape[0] == _EMBED_DIM:
|
|
370
|
+
entries.append((entry_id, "", v))
|
|
371
|
+
except Exception:
|
|
372
|
+
continue
|
|
373
|
+
with self._index_lock:
|
|
374
|
+
self._index[tenant_id] = entries
|
|
375
|
+
self._centroid_store.rebuild_from_db(self._db, tenant_id)
|
|
376
|
+
except Exception as exc:
|
|
377
|
+
logger.warning(
|
|
378
|
+
"VCacheSemantic._lazy_warm_tenant failed (fail-open): tenant=%s exc=%s",
|
|
379
|
+
tenant_id, exc,
|
|
380
|
+
)
|
|
381
|
+
with self._index_lock:
|
|
382
|
+
self._index.setdefault(tenant_id, [])
|
|
383
|
+
finally:
|
|
384
|
+
with self._warming_lock:
|
|
385
|
+
self._warming.discard(tenant_id)
|
|
386
|
+
|
|
387
|
+
def _ann_search(
|
|
388
|
+
self,
|
|
389
|
+
tenant_id: str,
|
|
390
|
+
query_vec: np.ndarray,
|
|
391
|
+
context_fp: str,
|
|
392
|
+
) -> tuple[str | None, float]:
|
|
393
|
+
"""Linear cosine scan with HARD context-fp exclusion (A-13 fix)."""
|
|
394
|
+
with self._index_lock:
|
|
395
|
+
entries = list(self._index.get(tenant_id, []))
|
|
396
|
+
if not entries:
|
|
397
|
+
return None, 0.0
|
|
398
|
+
best_id: str | None = None
|
|
399
|
+
best_score: float = -1.0
|
|
400
|
+
for entry_id, entry_ctx_fp, entry_vec in entries:
|
|
401
|
+
if entry_ctx_fp and entry_ctx_fp != context_fp:
|
|
402
|
+
continue # hard exclude — different conversational context
|
|
403
|
+
score = _cosine_similarity(query_vec, entry_vec)
|
|
404
|
+
if score > best_score:
|
|
405
|
+
best_score = score
|
|
406
|
+
best_id = entry_id
|
|
407
|
+
return best_id, best_score
|
|
408
|
+
|
|
409
|
+
def _set_inner(
|
|
410
|
+
self,
|
|
411
|
+
tenant_id: str,
|
|
412
|
+
entry_id: str,
|
|
413
|
+
embed: list[float] | np.ndarray,
|
|
414
|
+
context_fp: str = "",
|
|
415
|
+
) -> None:
|
|
416
|
+
"""Persist vector + boundary record; update in-memory index + centroid."""
|
|
417
|
+
vec = np.asarray(embed, dtype=np.float32)
|
|
418
|
+
if vec.shape[0] != _EMBED_DIM:
|
|
419
|
+
logger.warning(
|
|
420
|
+
"VCacheSemantic._set_inner: unexpected embedding dim %d (expected %d) "
|
|
421
|
+
"for entry=%s — skipping", vec.shape[0], _EMBED_DIM, entry_id,
|
|
422
|
+
)
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
# Store vector in DB
|
|
426
|
+
vec_bytes = vec.tobytes()
|
|
427
|
+
self._db.vec_add(
|
|
428
|
+
entry_id=entry_id,
|
|
429
|
+
tenant_id=tenant_id,
|
|
430
|
+
vector=vec_bytes,
|
|
431
|
+
meta={"model": "nomic-ai/nomic-embed-text-v1.5", "dim": _EMBED_DIM},
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# Initialize boundary record if new
|
|
435
|
+
existing = self._boundary_store.get(entry_id)
|
|
436
|
+
if len(existing.samples) == 0 and existing.t_hat == float(
|
|
437
|
+
getattr(self._config, "semantic_boundary_init", _DEFAULT_BOUNDARY_INIT)
|
|
438
|
+
) and not self._boundary_store._cache.get(entry_id):
|
|
439
|
+
# Persist the cold-start record only if it isn't already in DB
|
|
440
|
+
self._boundary_store.save(existing)
|
|
441
|
+
|
|
442
|
+
# Update in-memory index (dedupe)
|
|
443
|
+
with self._index_lock:
|
|
444
|
+
tenant_index = self._index.setdefault(tenant_id, [])
|
|
445
|
+
self._index[tenant_id] = [
|
|
446
|
+
e for e in tenant_index if e[0] != entry_id
|
|
447
|
+
]
|
|
448
|
+
self._index[tenant_id].append((entry_id, context_fp, vec))
|
|
449
|
+
|
|
450
|
+
# Update centroid
|
|
451
|
+
self._centroid_store.update(tenant_id, vec)
|
|
452
|
+
|
|
453
|
+
# ------------------------------------------------------------------
|
|
454
|
+
# Helpers
|
|
455
|
+
# ------------------------------------------------------------------
|
|
456
|
+
|
|
457
|
+
def _fetch_response(self, entry_id: str) -> dict[str, Any] | None:
|
|
458
|
+
"""Fetch the actual response dict for an entry_id via CacheDB."""
|
|
459
|
+
try:
|
|
460
|
+
return self._db.get_entry_by_id(entry_id)
|
|
461
|
+
except Exception as exc:
|
|
462
|
+
logger.warning(
|
|
463
|
+
"VCacheSemantic._fetch_response failed (fail-open): %s", exc,
|
|
464
|
+
)
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
def _verify_and_rewrite(
|
|
468
|
+
self,
|
|
469
|
+
entry_id: str,
|
|
470
|
+
cached_response: dict[str, Any],
|
|
471
|
+
verifier_model: str,
|
|
472
|
+
) -> tuple[bool, dict[str, Any] | None]:
|
|
473
|
+
"""Phase 3.0 stub: return (True, None) — treat as verified, no rewrite.
|
|
474
|
+
|
|
475
|
+
Full implementation requires a sub-agent call to a cheap model.
|
|
476
|
+
Stub is conservative: boundary learning still fires, so over time
|
|
477
|
+
the boundary will tighten if errors accumulate.
|
|
478
|
+
|
|
479
|
+
A-03 fix: callers do NOT call record_outcome() on this stub path
|
|
480
|
+
(the True signal is fake — would poison the MLE model).
|
|
481
|
+
"""
|
|
482
|
+
logger.debug(
|
|
483
|
+
"VCacheSemantic._verify_and_rewrite: stub returning True "
|
|
484
|
+
"(verifier=%s entry=%s)", verifier_model, entry_id,
|
|
485
|
+
)
|
|
486
|
+
return True, None
|
|
487
|
+
|
|
488
|
+
@staticmethod
|
|
489
|
+
def _build_query_text(messages: list[dict[str, Any]], system: str) -> str:
|
|
490
|
+
"""Build a single embedding input from last user message + system."""
|
|
491
|
+
last_user = ""
|
|
492
|
+
for m in reversed(messages):
|
|
493
|
+
if m.get("role") == "user":
|
|
494
|
+
content = m.get("content", "")
|
|
495
|
+
last_user = content if isinstance(content, str) else str(content)
|
|
496
|
+
break
|
|
497
|
+
if system:
|
|
498
|
+
return f"{system}\n\n{last_user}"
|
|
499
|
+
return last_user
|
|
500
|
+
|
|
501
|
+
@staticmethod
|
|
502
|
+
def _derive_entry_id(tenant_id: str, query_text: str) -> str:
|
|
503
|
+
"""Derive a stable surrogate entry_id from tenant + query_text.
|
|
504
|
+
|
|
505
|
+
INTERFACE-CONTRACT v2 §4: SemanticTier ABC does not pass entry_id.
|
|
506
|
+
Caller (CacheManager) passes the real UUID at the exact tier; the
|
|
507
|
+
semantic surrogate is deterministic and stable per query.
|
|
508
|
+
"""
|
|
509
|
+
payload = f"{tenant_id}:{query_text}"
|
|
510
|
+
digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()[:32]
|
|
511
|
+
return f"sem:{digest}"
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
# ---------------------------------------------------------------------------
|
|
515
|
+
# Module-level helpers — robust to both dict and attribute access
|
|
516
|
+
# ---------------------------------------------------------------------------
|
|
517
|
+
|
|
518
|
+
def _extract_messages(req: Any) -> list[dict[str, Any]]:
|
|
519
|
+
"""Pull messages list from req (dict, dataclass, or SimpleNamespace).
|
|
520
|
+
|
|
521
|
+
Looks at: req.messages → req.body.messages → [].
|
|
522
|
+
"""
|
|
523
|
+
if req is None:
|
|
524
|
+
return []
|
|
525
|
+
if isinstance(req, dict):
|
|
526
|
+
msgs = req.get("messages", []) or []
|
|
527
|
+
if isinstance(msgs, list):
|
|
528
|
+
return msgs
|
|
529
|
+
msgs = getattr(req, "messages", None)
|
|
530
|
+
if isinstance(msgs, list) and msgs:
|
|
531
|
+
return msgs
|
|
532
|
+
body = getattr(req, "body", None)
|
|
533
|
+
if isinstance(body, dict):
|
|
534
|
+
msgs = body.get("messages", []) or []
|
|
535
|
+
if isinstance(msgs, list):
|
|
536
|
+
return msgs
|
|
537
|
+
return []
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _extract_system(req: Any) -> str:
|
|
541
|
+
"""Pull system prompt from req (dict, dataclass, or SimpleNamespace).
|
|
542
|
+
|
|
543
|
+
Looks at: req.system → req.body.system → "".
|
|
544
|
+
"""
|
|
545
|
+
if req is None:
|
|
546
|
+
return ""
|
|
547
|
+
if isinstance(req, dict):
|
|
548
|
+
return str(req.get("system", "") or "")
|
|
549
|
+
s = getattr(req, "system", None)
|
|
550
|
+
if s:
|
|
551
|
+
return str(s)
|
|
552
|
+
body = getattr(req, "body", None)
|
|
553
|
+
if isinstance(body, dict):
|
|
554
|
+
return str(body.get("system", "") or "")
|
|
555
|
+
return ""
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
# Latency padding helper (exported for tests)
|
|
559
|
+
|
|
560
|
+
def apply_latency_padding(pad_ms: float) -> None:
|
|
561
|
+
"""Sleep for a random duration in [0, pad_ms] milliseconds.
|
|
562
|
+
|
|
563
|
+
Side-channel defense (arXiv:2601.23088 §4). Tests can monkeypatch
|
|
564
|
+
time.sleep to no-op.
|
|
565
|
+
"""
|
|
566
|
+
if pad_ms <= 0:
|
|
567
|
+
return
|
|
568
|
+
time.sleep(random.uniform(0, pad_ms / 1000.0))
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""stampede.py — per-key mutex preventing cache stampede."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from typing import Generator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StampedeShield:
|
|
11
|
+
"""Per-key mutex preventing cache stampede (thundering-herd) on cold miss."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, timeout: float = 30.0) -> None:
|
|
14
|
+
self._timeout = timeout
|
|
15
|
+
self._locks: dict[str, threading.RLock] = {}
|
|
16
|
+
self._refcounts: dict[str, int] = {}
|
|
17
|
+
self._meta_lock = threading.Lock()
|
|
18
|
+
|
|
19
|
+
def _acquire_key_lock(self, key: str) -> threading.RLock:
|
|
20
|
+
with self._meta_lock:
|
|
21
|
+
if key not in self._locks:
|
|
22
|
+
self._locks[key] = threading.RLock()
|
|
23
|
+
self._refcounts[key] = 0
|
|
24
|
+
self._refcounts[key] += 1
|
|
25
|
+
return self._locks[key]
|
|
26
|
+
|
|
27
|
+
def _release_key_lock(self, key: str) -> None:
|
|
28
|
+
with self._meta_lock:
|
|
29
|
+
count = self._refcounts.get(key, 0) - 1
|
|
30
|
+
if count <= 0:
|
|
31
|
+
self._locks.pop(key, None)
|
|
32
|
+
self._refcounts.pop(key, None)
|
|
33
|
+
else:
|
|
34
|
+
self._refcounts[key] = count
|
|
35
|
+
|
|
36
|
+
@contextmanager
|
|
37
|
+
def lock(self, key: str) -> Generator[None, None, None]:
|
|
38
|
+
rlock = self._acquire_key_lock(key)
|
|
39
|
+
acquired = rlock.acquire(timeout=self._timeout)
|
|
40
|
+
if not acquired:
|
|
41
|
+
try:
|
|
42
|
+
yield
|
|
43
|
+
finally:
|
|
44
|
+
self._release_key_lock(key)
|
|
45
|
+
return
|
|
46
|
+
try:
|
|
47
|
+
yield
|
|
48
|
+
finally:
|
|
49
|
+
rlock.release()
|
|
50
|
+
self._release_key_lock(key)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# compress/__init__.py
|
|
2
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
3
|
+
# Licensed under AGPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""SLM v3.6 Optimize — Compression module.
|
|
6
|
+
|
|
7
|
+
Public surface:
|
|
8
|
+
CompressRouter — implements CompressHook Protocol from lifecycle.py
|
|
9
|
+
CCRStore — CCR original storage and retrieval
|
|
10
|
+
CacheAligner — volatile-token detection for system prompt prefix stability
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from superlocalmemory.optimize.compress.router import CompressRouter, CompressTextResult
|
|
14
|
+
from superlocalmemory.optimize.compress.ccr import CCRStore
|
|
15
|
+
from superlocalmemory.optimize.compress.align import CacheAligner, AlignResult
|
|
16
|
+
|
|
17
|
+
__all__ = ["CompressRouter", "CompressTextResult", "CCRStore", "CacheAligner", "AlignResult"]
|