Spaces:
Sleeping
Sleeping
File size: 6,618 Bytes
27bbd47 3ee62c8 27bbd47 3ee62c8 27bbd47 6290fdd 27bbd47 3ee62c8 27bbd47 3a00bdd 27bbd47 3ee62c8 27bbd47 3ee62c8 27bbd47 3ee62c8 27bbd47 3ee62c8 27bbd47 0ba41e3 c6390d0 0ba41e3 3ee62c8 0ba41e3 3ee62c8 0ba41e3 3ee62c8 0ba41e3 c6390d0 27bbd47 c6390d0 27bbd47 c6390d0 27bbd47 c6390d0 27bbd47 3ee62c8 27bbd47 c6390d0 27bbd47 c6390d0 0ba41e3 c6390d0 0ba41e3 c6390d0 0ba41e3 c6390d0 0ba41e3 c6390d0 0ba41e3 3ee62c8 0ba41e3 3ee62c8 0ba41e3 27bbd47 c6390d0 3ee62c8 27bbd47 0ba41e3 3ee62c8 c6390d0 27bbd47 3ee62c8 27bbd47 3ee62c8 27bbd47 c6390d0 27bbd47 c6390d0 3ee62c8 a68d3b5 27bbd47 c6390d0 3ee62c8 27bbd47 3ee62c8 27bbd47 c6390d0 27bbd47 c6390d0 3ee62c8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
"""
Cache Manager - Gère Hit/Miss et distillation locale
"""
import numpy as np
from typing import Dict, List, Any
import uuid
from datetime import datetime
from config import DISTANCE_THRESHOLD, TOP_K_RESULTS, CONFIDENCE_THRESHOLD_WARNING
class CacheManager:
def __init__(self, chroma_collection, encoder_fn, threshold=None):
"""
Args:
chroma_collection: Collection ChromaDB
encoder_fn: Fonction pour encoder du texte en embedding
threshold: Custom similarity threshold
"""
self.collection = chroma_collection
self.encoder_fn = encoder_fn
self.threshold = threshold if threshold is not None else DISTANCE_THRESHOLD
def calculate_confidence(self, distances: List[float]) -> float:
"""Convertit la distance Chroma (Cosine) en score de confiance [0, 1]."""
if not distances:
return 0.0
# Avec hnsw:space="cosine", distance = 1 - similarity.
avg_distance = np.mean(distances)
return max(0.0, 1.0 - avg_distance)
def query_cache(self, code: str, context: Dict[str, Any]) -> Dict[str, Any]:
"""
Recherche dans le cache (Pipeline Hybride: Exact Match -> Vector Search -> Code Comparison)
"""
# --- NIVEAU 1 : CHECK RAPIDE (String Exact Match) ---
try:
if len(code) < 5000:
exact_matches = self.collection.get(where={"code": code}, limit=1)
if exact_matches and len(exact_matches['ids']) > 0:
return {
"status": "perfect_match",
"results": [{
"feedback": exact_matches['documents'][0],
"code": code,
"distance": 0.0,
"rank": 1,
"metadata": exact_matches['metadatas'][0]
}],
"confidence": 1.0,
"needs_warning": False,
"closest_distance": 0.0
}
except Exception as e:
print(f"Warning exact match check: {e}")
# --- NIVEAU 2 : RETRIEVAL (Vectorielle) ---
query_embedding = self.encoder_fn(code)
# On récupère les candidats (basé sur la proximité Code Input -> Feedback Embedding)
query_results = self.collection.query(
query_embeddings=[query_embedding],
n_results=TOP_K_RESULTS
)
distances = query_results['distances'][0] if query_results['distances'] else []
documents = query_results['documents'][0] if query_results['documents'] else []
metadatas = query_results['metadatas'][0] if query_results['metadatas'] else []
# --- NIVEAU 3 : ANALYSE FINE (Code vs Code) ---
is_code_hit = False
code_distance = 1.0 # Pire cas par défaut
# On vérifie si le code du meilleur candidat est sémantiquement proche du code utilisateur
if metadatas and metadatas[0].get('code'):
ref_code = metadatas[0].get('code')
if ref_code and ref_code != 'N/A':
# On encode le code de référence pour comparer avec le code d'entrée
ref_code_embedding = self.encoder_fn(ref_code)
# Distance Cosine entre les deux codes
# Note: np.dot sur vecteurs normalisés = Cosine Similarity. Distance = 1 - Sim.
similarity = float(np.dot(query_embedding, ref_code_embedding))
code_distance = max(0.0, 1.0 - similarity)
# Seuil très strict pour dire "C'est le même code" (mais écrit différemment)
if code_distance < 0.1: # Correspond à > 90% de similarité
is_code_hit = True
# --- DÉCISION FINALE ---
is_hit = False
hit_type = "miss"
# Priorité 1 : Code quasi-identique vectoriellement
if is_code_hit:
is_hit = True
hit_type = "code_hit"
# Priorité 2 : Feedback pertinent (Standard RAG) selon le slider
elif distances and distances[0] < self.threshold:
is_hit = True
hit_type = "feedback_hit"
# Formatage des résultats pour l'affichage
formatted_results = []
for i, (feedback, metadata, dist) in enumerate(zip(documents, metadatas, distances)):
formatted_results.append({
"rank": i + 1,
"feedback": feedback,
"code": metadata.get('code', 'N/A'),
"distance": round(dist, 4),
"metadata": metadata
})
if is_hit:
confidence = self.calculate_confidence(distances)
# Boost de confiance si c'est un code hit
if hit_type == "code_hit":
confidence = max(confidence, 0.95)
return {
"status": hit_type,
"results": formatted_results,
"confidence": round(confidence, 3),
"needs_warning": False if hit_type == "code_hit" else (confidence < CONFIDENCE_THRESHOLD_WARNING),
"closest_distance": round(distances[0], 4)
}
else:
return {
"status": "miss",
"results": formatted_results,
"confidence": 0.0,
"needs_warning": False,
"closest_distance": round(distances[0], 4) if distances else 1.0
}
def add_to_cache(self, code: str, feedback: str, metadata: Dict[str, Any], embedding: List[float]) -> bool:
"""
Ajoute au cache local pour la session courante (Active Learning).
"""
try:
doc_id = f"learned_{uuid.uuid4().hex[:8]}"
safe_metadata = {
"code": code[:10000],
"timestamp": datetime.now().isoformat(),
"source": "active_learning",
"theme": str(metadata.get("theme", "")),
"difficulty": str(metadata.get("difficulty", ""))
}
self.collection.add(
embeddings=[embedding],
documents=[feedback],
metadatas=[safe_metadata],
ids=[doc_id]
)
print(f"✅ Learned new feedback: {doc_id}")
return True
except Exception as e:
print(f"❌ Error adding to cache: {e}")
return False |