- A: gate store con cross-encoder — guardrail.decide async, conferma/scarta quasi-duplicati (CROSS_DUP_CONFIRMED/WEAK/LOW_COSINE), suggerimento supersedes in WARN, degrada a cosine-only se il reranker è giù - B: verifica supersede — cross-score (nuovo,vecchio) sotto soglia → supersede_warning non bloccante + audit - C: score composito in search — rerank + importance (nuovo campo payload) + recency decay (180gg) + authority, pesi SCORE_W_* da env - E: multi-query — SearchIn.queries (max 3), pool unito con dedup, rerank unico; endpoint POST /v1/score come primitiva cross-encoder (F-lite) - extension search.ts: param queries + rerank_score/composite in output - D: scripts/consolidate.py — dedup periodico a coppie via cross-encoder con report ntfy e --apply via gateway - test: 69 pass (+11 strategie); guardrail_version similarity-v2
79 lines
4.1 KiB
Python
79 lines
4.1 KiB
Python
"""Configurazione statica del Memory Gateway letta dall'ambiente."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from collections import Counter
|
|
from typing import Any
|
|
|
|
QDRANT_URL = os.environ.get("QDRANT_URL", "http://127.0.0.1:6333")
|
|
QDRANT_API_KEY = os.environ.get("QDRANT_API_KEY", "")
|
|
EMBED_API = os.environ.get("EMBED_API", "ollama")
|
|
EMBED_URL = os.environ.get("EMBED_URL", os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434"))
|
|
EMBED_MODEL = os.environ.get("EMBED_MODEL", "bge-m3")
|
|
EMBED_API_KEY = os.environ.get("EMBED_API_KEY", "")
|
|
EMBED_DIM = int(os.environ.get("EMBED_DIM", "1024"))
|
|
# Catena di fallback per gli embedding (JSON, formato RERANK_CHAIN + campo "api").
|
|
# Vuota → comportamento legacy: endpoint singolo da EMBED_API/EMBED_URL/EMBED_API_KEY.
|
|
EMBED_CHAIN = os.environ.get("EMBED_CHAIN", "")
|
|
EMBED_TIMEOUT_MS = int(os.environ.get("EMBED_TIMEOUT_MS", "30000"))
|
|
EMBED_RETRY_COOLDOWN_S = int(os.environ.get("EMBED_RETRY_COOLDOWN_S", "60"))
|
|
# Retry transiente per le chiamate Qdrant (store/search inclusi)
|
|
QDRANT_RETRIES = int(os.environ.get("QDRANT_RETRIES", "3"))
|
|
COLLECTION = os.environ.get("COLLECTION", "memories")
|
|
API_KEYS: set[str] = {k.strip() for k in os.environ.get("API_KEYS", "").split(",") if k.strip()}
|
|
RATE_LIMIT_PER_MIN = int(os.environ.get("RATE_LIMIT_PER_MIN", "120"))
|
|
MAX_TEXT_LEN = int(os.environ.get("MAX_TEXT_LEN", "8000"))
|
|
|
|
GUARDRAIL_ENABLED = os.environ.get("GUARDRAIL_ENABLED", "true").lower() == "true"
|
|
GUARDRAIL_BLOCK_THRESHOLD = float(os.environ.get("GUARDRAIL_BLOCK_THRESHOLD", "0.85"))
|
|
GUARDRAIL_WARN_THRESHOLD = float(os.environ.get("GUARDRAIL_WARN_THRESHOLD", "0.70"))
|
|
GUARDRAIL_VERSION = "similarity-v2"
|
|
# Strato 3 del guardrail: cross-encoder (richiede catena rerank attiva)
|
|
GUARDRAIL_RERANK = os.environ.get("GUARDRAIL_RERANK", "false").lower() == "true"
|
|
GUARDRAIL_RERANK_BLOCK = float(os.environ.get("GUARDRAIL_RERANK_BLOCK", "0.88"))
|
|
GUARDRAIL_RERANK_SUGGEST = float(os.environ.get("GUARDRAIL_RERANK_SUGGEST", "0.80"))
|
|
# Verifica supersede: cross-score (nuovo, vecchio) sotto soglia → warning non bloccante
|
|
GUARDRAIL_SUPERSEDE_CHECK = os.environ.get("GUARDRAIL_SUPERSEDE_CHECK", "false").lower() == "true"
|
|
GUARDRAIL_SUPERSEDE_MIN = float(os.environ.get("GUARDRAIL_SUPERSEDE_MIN", "0.50"))
|
|
# Score composito: rerank + importance + recency + authority (post-rerank)
|
|
SCORE_W_RELEVANCE = float(os.environ.get("SCORE_W_RELEVANCE", "0.55"))
|
|
SCORE_W_IMPORTANCE = float(os.environ.get("SCORE_W_IMPORTANCE", "0.20"))
|
|
SCORE_W_RECENCY = float(os.environ.get("SCORE_W_RECENCY", "0.15"))
|
|
SCORE_W_AUTHORITY = float(os.environ.get("SCORE_W_AUTHORITY", "0.10"))
|
|
SCORE_DECAY_HALF_LIFE_DAYS = float(os.environ.get("SCORE_DECAY_HALF_LIFE_DAYS", "180"))
|
|
GIT_COMMIT = os.environ.get("GIT_COMMIT", "unknown").strip()
|
|
GATEWAY_VERSION = os.environ.get("GATEWAY_VERSION", "2.11.0").strip()
|
|
|
|
VM_PUSH_URL = os.environ.get("VM_PUSH_URL", "http://host.docker.internal:8428/api/v1/import/prometheus")
|
|
VM_PUSH_INTERVAL = int(os.environ.get("VM_PUSH_INTERVAL", "30"))
|
|
METRICS_ENABLED = os.environ.get("METRICS_ENABLED", "true").lower() == "true"
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
|
log = logging.getLogger("memory-gateway")
|
|
|
|
SPARSE_VECTOR_NAME = "bm25"
|
|
|
|
# Re-ranking: catena di fallback resiliente (frigate → brain locale).
|
|
# Il default nel codice è OFF; il deploy imposta RERANK_ENABLED=true e la catena.
|
|
RERANK_ENABLED = os.environ.get("RERANK_ENABLED", "false").lower() == "true"
|
|
RERANK_MODEL = os.environ.get("RERANK_MODEL", "bge-reranker-v2-m3")
|
|
RERANK_CANDIDATES = int(os.environ.get("RERANK_CANDIDATES", "16"))
|
|
RERANK_MAX_DOC_CHARS = int(os.environ.get("RERANK_MAX_DOC_CHARS", "800"))
|
|
RERANK_TIMEOUT_MS = int(os.environ.get("RERANK_TIMEOUT_MS", "10000"))
|
|
RERANK_RETRY_COOLDOWN_S = int(os.environ.get("RERANK_RETRY_COOLDOWN_S", "60"))
|
|
RERANK_CHAIN = os.environ.get("RERANK_CHAIN", "")
|
|
_metrics: dict[str, Any] = {
|
|
"requests": Counter(),
|
|
"duration_sum": Counter(),
|
|
"duration_count": Counter(),
|
|
"errors": Counter(),
|
|
"search_queries": 0,
|
|
"search_hits": 0,
|
|
"rerank_calls": Counter(),
|
|
"rerank_duration_sum": Counter(),
|
|
"embed_calls": Counter(),
|
|
"embed_duration_sum": Counter(),
|
|
"qdrant_retries": 0,
|
|
}
|