feat(guardrail): similarità pre-scrittura su POST /v1/memories
Guardrail deterministico FUORI dall'LLM (stessa architettura di egeos-copilot): - Strato 1: text_hash SHA-256 normalizzato -> BLOCK 409 (EXACT_DUPLICATE) - Strato 2: similarità semantica top-3 BGE-M3 cosine -> BLOCK/WARN/ALLOW (soglie configurabili: GUARDRAIL_BLOCK_THRESHOLD 0.85, WARN 0.70) - Supersede esplicito bypassa il guardrail (correzione intenzionale) - text_hash e flag guardrail nel payload; audit create_blocked - Indice payload su text_hash - Test: 6 nuovi (duplicato esatto, similarità alta/moderata, nessun candidato, supersede bypass, disabilitato, text_hash) — 28/28 passano
This commit is contained in:
@@ -12,6 +12,8 @@ from pathlib import Path
|
||||
# Disabilita il push metriche nei test
|
||||
os.environ["METRICS_ENABLED"] = "false"
|
||||
os.environ["API_KEYS"] = "test-key"
|
||||
# Guardrail disabilitato di default nei test esistenti (abilitato nei test del guardrail)
|
||||
os.environ["GUARDRAIL_ENABLED"] = "false"
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
@@ -33,6 +35,7 @@ class FakeQdrant:
|
||||
self.points: dict[str, FakePoint] = {}
|
||||
self.collection_exists = False
|
||||
self.upsert_calls = 0
|
||||
self.query_score = 0.9 # score di default per query_points (configurabile nei test)
|
||||
|
||||
def get_collections(self):
|
||||
class _C:
|
||||
@@ -94,7 +97,7 @@ class FakeQdrant:
|
||||
ok = False
|
||||
if not ok:
|
||||
continue
|
||||
results.append(type("H", (), {"id": p.id, "score": 0.9, "payload": pl})())
|
||||
results.append(type("H", (), {"id": p.id, "score": self.query_score, "payload": pl})())
|
||||
return type("R", (), {"points": results[:limit]})()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user