Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,24 +1,9 @@
|
|
| 1 |
import os
|
|
|
|
| 2 |
import gradio as gr
|
| 3 |
from huggingface_hub import InferenceClient
|
| 4 |
from difflib import SequenceMatcher
|
| 5 |
|
| 6 |
-
# =======================
|
| 7 |
-
# METRICHE (evaluate)
|
| 8 |
-
# =======================
|
| 9 |
-
|
| 10 |
-
try:
|
| 11 |
-
import evaluate
|
| 12 |
-
ROUGE = evaluate.load("rouge")
|
| 13 |
-
BLEU = evaluate.load("bleu")
|
| 14 |
-
CHRF = evaluate.load("chrf")
|
| 15 |
-
METRICS_AVAILABLE = True
|
| 16 |
-
print("✅ Metriche ROUGE / BLEU / chrF caricate correttamente.")
|
| 17 |
-
except Exception as e:
|
| 18 |
-
print("⚠️ Impossibile caricare le metriche da evaluate:", repr(e))
|
| 19 |
-
ROUGE = BLEU = CHRF = None
|
| 20 |
-
METRICS_AVAILABLE = False
|
| 21 |
-
|
| 22 |
# =======================
|
| 23 |
# MODELLI DISPONIBILI
|
| 24 |
# =======================
|
|
@@ -190,45 +175,99 @@ def get_system_prompt(mostra_ragionamento: bool):
|
|
| 190 |
)
|
| 191 |
|
| 192 |
# =======================
|
| 193 |
-
#
|
| 194 |
# =======================
|
| 195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
def log_quality_metrics(question: str, voci_glossario, answer_text: str, modello_scelto: str):
|
| 197 |
"""
|
| 198 |
-
Calcola
|
| 199 |
-
con le definizioni del glossario (
|
| 200 |
-
I risultati vengono SOLO stampati nei log del server (non mostrati all'utente).
|
| 201 |
"""
|
| 202 |
-
if not METRICS_AVAILABLE:
|
| 203 |
-
print("⚠️ Metriche non disponibili (evaluate non caricato). Salto la valutazione.")
|
| 204 |
-
return
|
| 205 |
-
|
| 206 |
if not voci_glossario:
|
| 207 |
print("ℹ️ Nessuna voce di glossario per questa domanda. Salto il calcolo delle metriche.")
|
| 208 |
return
|
| 209 |
|
| 210 |
-
# Riferimento: concatenazione delle definizioni delle voci selezionate
|
| 211 |
reference = " ".join(e["definition"] for e in voci_glossario)
|
| 212 |
prediction = answer_text.strip()
|
| 213 |
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
chrf_res = CHRF.compute(predictions=[prediction], references=[reference])
|
| 218 |
-
except Exception as e:
|
| 219 |
-
print("⚠️ Errore nel calcolo delle metriche:", repr(e))
|
| 220 |
-
return
|
| 221 |
|
| 222 |
-
print("\n====== VALUTAZIONE QUALITÀ RISPOSTA ======")
|
| 223 |
print(f"Modello: {modello_scelto}")
|
| 224 |
print(f"Domanda: {question}")
|
| 225 |
print(f"Glossario usato per riferimento: {[e['term'] for e in voci_glossario]}")
|
| 226 |
-
print("--- Metriche ---")
|
| 227 |
-
print(f"ROUGE-1 F1: {
|
| 228 |
-
print(f"
|
| 229 |
-
print(f"
|
| 230 |
-
print(
|
| 231 |
-
print("=========================================\n")
|
| 232 |
|
| 233 |
# =======================
|
| 234 |
# FUNZIONE DI RISPOSTA (STREAMING + SELF-REFLECTION IMPLICITA)
|
|
@@ -241,7 +280,7 @@ def answer_with_self_reflection(question: str, modello_scelto: str, mostra_ragio
|
|
| 241 |
- usa il glossario come fonte principale
|
| 242 |
- chiede al modello di correggersi mentalmente prima di rispondere
|
| 243 |
|
| 244 |
-
Alla fine, in background, calcola anche
|
| 245 |
e le scrive nei log (solo per amministratore).
|
| 246 |
"""
|
| 247 |
question = question.strip()
|
|
@@ -249,19 +288,17 @@ def answer_with_self_reflection(question: str, modello_scelto: str, mostra_ragio
|
|
| 249 |
yield "Scrivi un termine o concetto di informatica che vuoi capire meglio 😊"
|
| 250 |
return
|
| 251 |
|
| 252 |
-
# Ricava il vero MODEL_ID a partire dall'etichetta scelta nel menu
|
| 253 |
model_id = AVAILABLE_MODELS.get(modello_scelto)
|
| 254 |
if model_id is None:
|
| 255 |
yield "Si è verificato un errore: modello non valido."
|
| 256 |
return
|
| 257 |
|
| 258 |
-
# Client specifico per il modello scelto
|
| 259 |
client = InferenceClient(model=model_id, token=HF_TOKEN)
|
| 260 |
|
| 261 |
# 🔍 MINI-RAG: trova voci rilevanti nel glossario TOON
|
| 262 |
voci = trova_voci_rilevanti(question, k=3)
|
| 263 |
|
| 264 |
-
#
|
| 265 |
base_prompt = get_system_prompt(mostra_ragionamento)
|
| 266 |
|
| 267 |
if voci:
|
|
@@ -298,11 +335,10 @@ def answer_with_self_reflection(question: str, modello_scelto: str, mostra_ragio
|
|
| 298 |
]
|
| 299 |
|
| 300 |
if mostra_ragionamento:
|
| 301 |
-
max_tokens = 800
|
| 302 |
else:
|
| 303 |
-
max_tokens = 400
|
| 304 |
|
| 305 |
-
# Badge con numero di voci trovate
|
| 306 |
if n_voci > 0:
|
| 307 |
prefix = f"🔎 Ho trovato **{n_voci}** voci nel glossario rilevanti per la tua domanda.\n\n"
|
| 308 |
else:
|
|
@@ -329,14 +365,12 @@ def answer_with_self_reflection(question: str, modello_scelto: str, mostra_ragio
|
|
| 329 |
yield f"Si è verificato un errore: {e}"
|
| 330 |
return
|
| 331 |
|
| 332 |
-
#
|
| 333 |
-
# Per le metriche togliamo il prefisso "🔎 ..." se presente
|
| 334 |
if n_voci > 0:
|
| 335 |
answer_for_metrics = partial[len(prefix):]
|
| 336 |
else:
|
| 337 |
answer_for_metrics = partial
|
| 338 |
|
| 339 |
-
# 🔐 Valutazione "nascosta": solo log admin
|
| 340 |
log_quality_metrics(question, voci, answer_for_metrics, modello_scelto)
|
| 341 |
|
| 342 |
# =======================
|
|
|
|
| 1 |
import os
|
| 2 |
+
import re
|
| 3 |
import gradio as gr
|
| 4 |
from huggingface_hub import InferenceClient
|
| 5 |
from difflib import SequenceMatcher
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
# =======================
|
| 8 |
# MODELLI DISPONIBILI
|
| 9 |
# =======================
|
|
|
|
| 175 |
)
|
| 176 |
|
| 177 |
# =======================
|
| 178 |
+
# METRICHE CUSTOM (approx ROUGE / BLEU / chrF)
|
| 179 |
# =======================
|
| 180 |
|
| 181 |
+
def _tokenize(text: str):
|
| 182 |
+
# tokenizzazione molto semplice: parole alfanumeriche
|
| 183 |
+
return [t for t in re.findall(r"\w+", text.lower()) if t]
|
| 184 |
+
|
| 185 |
+
def _char_list(text: str):
|
| 186 |
+
return [c for c in text.lower() if not c.isspace()]
|
| 187 |
+
|
| 188 |
+
def rouge1_f1(pred: str, ref: str) -> float:
|
| 189 |
+
pred_tokens = _tokenize(pred)
|
| 190 |
+
ref_tokens = _tokenize(ref)
|
| 191 |
+
if not pred_tokens or not ref_tokens:
|
| 192 |
+
return 0.0
|
| 193 |
+
pred_set = set(pred_tokens)
|
| 194 |
+
ref_set = set(ref_tokens)
|
| 195 |
+
overlap = len(pred_set & ref_set)
|
| 196 |
+
if overlap == 0:
|
| 197 |
+
return 0.0
|
| 198 |
+
precision = overlap / len(pred_set)
|
| 199 |
+
recall = overlap / len(ref_set)
|
| 200 |
+
if precision + recall == 0:
|
| 201 |
+
return 0.0
|
| 202 |
+
return 2 * precision * recall / (precision + recall)
|
| 203 |
+
|
| 204 |
+
def bleu1(pred: str, ref: str) -> float:
|
| 205 |
+
pred_tokens = _tokenize(pred)
|
| 206 |
+
ref_tokens = _tokenize(ref)
|
| 207 |
+
if not pred_tokens or not ref_tokens:
|
| 208 |
+
return 0.0
|
| 209 |
+
overlap = 0
|
| 210 |
+
ref_counts = {}
|
| 211 |
+
for t in ref_tokens:
|
| 212 |
+
ref_counts[t] = ref_counts.get(t, 0) + 1
|
| 213 |
+
for t in pred_tokens:
|
| 214 |
+
if ref_counts.get(t, 0) > 0:
|
| 215 |
+
overlap += 1
|
| 216 |
+
ref_counts[t] -= 1
|
| 217 |
+
precision = overlap / len(pred_tokens)
|
| 218 |
+
# brevity penalty
|
| 219 |
+
from math import exp
|
| 220 |
+
len_p = len(pred_tokens)
|
| 221 |
+
len_r = len(ref_tokens)
|
| 222 |
+
if len_p == 0:
|
| 223 |
+
return 0.0
|
| 224 |
+
if len_p > len_r:
|
| 225 |
+
bp = 1.0
|
| 226 |
+
else:
|
| 227 |
+
bp = exp(1 - len_r / len_p)
|
| 228 |
+
return bp * precision
|
| 229 |
+
|
| 230 |
+
def chrf_simple(pred: str, ref: str) -> float:
|
| 231 |
+
pred_chars = _char_list(pred)
|
| 232 |
+
ref_chars = _char_list(ref)
|
| 233 |
+
if not pred_chars or not ref_chars:
|
| 234 |
+
return 0.0
|
| 235 |
+
pred_set = set(pred_chars)
|
| 236 |
+
ref_set = set(ref_chars)
|
| 237 |
+
overlap = len(pred_set & ref_set)
|
| 238 |
+
if overlap == 0:
|
| 239 |
+
return 0.0
|
| 240 |
+
precision = overlap / len(pred_set)
|
| 241 |
+
recall = overlap / len(ref_set)
|
| 242 |
+
if precision + recall == 0:
|
| 243 |
+
return 0.0
|
| 244 |
+
return 2 * precision * recall / (precision + recall)
|
| 245 |
+
|
| 246 |
def log_quality_metrics(question: str, voci_glossario, answer_text: str, modello_scelto: str):
|
| 247 |
"""
|
| 248 |
+
Calcola metriche naive (ROUGE1-F1, BLEU1, chrF-like) confrontando la risposta del modello
|
| 249 |
+
con le definizioni del glossario. Solo log su console (admin).
|
|
|
|
| 250 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
if not voci_glossario:
|
| 252 |
print("ℹ️ Nessuna voce di glossario per questa domanda. Salto il calcolo delle metriche.")
|
| 253 |
return
|
| 254 |
|
|
|
|
| 255 |
reference = " ".join(e["definition"] for e in voci_glossario)
|
| 256 |
prediction = answer_text.strip()
|
| 257 |
|
| 258 |
+
r1 = rouge1_f1(prediction, reference)
|
| 259 |
+
b1 = bleu1(prediction, reference)
|
| 260 |
+
cf = chrf_simple(prediction, reference)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
+
print("\n====== VALUTAZIONE QUALITÀ RISPOSTA (NAIVE METRICS) ======")
|
| 263 |
print(f"Modello: {modello_scelto}")
|
| 264 |
print(f"Domanda: {question}")
|
| 265 |
print(f"Glossario usato per riferimento: {[e['term'] for e in voci_glossario]}")
|
| 266 |
+
print("--- Metriche approx ---")
|
| 267 |
+
print(f"ROUGE-1 F1 (uno-grammi): {r1:.4f}")
|
| 268 |
+
print(f"BLEU-1 (uno-grammi): {b1:.4f}")
|
| 269 |
+
print(f"chrF semplice (char F1): {cf:.4f}")
|
| 270 |
+
print("=========================================================\n")
|
|
|
|
| 271 |
|
| 272 |
# =======================
|
| 273 |
# FUNZIONE DI RISPOSTA (STREAMING + SELF-REFLECTION IMPLICITA)
|
|
|
|
| 280 |
- usa il glossario come fonte principale
|
| 281 |
- chiede al modello di correggersi mentalmente prima di rispondere
|
| 282 |
|
| 283 |
+
Alla fine, in background, calcola anche metriche di qualità rispetto al glossario
|
| 284 |
e le scrive nei log (solo per amministratore).
|
| 285 |
"""
|
| 286 |
question = question.strip()
|
|
|
|
| 288 |
yield "Scrivi un termine o concetto di informatica che vuoi capire meglio 😊"
|
| 289 |
return
|
| 290 |
|
|
|
|
| 291 |
model_id = AVAILABLE_MODELS.get(modello_scelto)
|
| 292 |
if model_id is None:
|
| 293 |
yield "Si è verificato un errore: modello non valido."
|
| 294 |
return
|
| 295 |
|
|
|
|
| 296 |
client = InferenceClient(model=model_id, token=HF_TOKEN)
|
| 297 |
|
| 298 |
# 🔍 MINI-RAG: trova voci rilevanti nel glossario TOON
|
| 299 |
voci = trova_voci_rilevanti(question, k=3)
|
| 300 |
|
| 301 |
+
# Prompt di sistema
|
| 302 |
base_prompt = get_system_prompt(mostra_ragionamento)
|
| 303 |
|
| 304 |
if voci:
|
|
|
|
| 335 |
]
|
| 336 |
|
| 337 |
if mostra_ragionamento:
|
| 338 |
+
max_tokens = 800
|
| 339 |
else:
|
| 340 |
+
max_tokens = 400
|
| 341 |
|
|
|
|
| 342 |
if n_voci > 0:
|
| 343 |
prefix = f"🔎 Ho trovato **{n_voci}** voci nel glossario rilevanti per la tua domanda.\n\n"
|
| 344 |
else:
|
|
|
|
| 365 |
yield f"Si è verificato un errore: {e}"
|
| 366 |
return
|
| 367 |
|
| 368 |
+
# Risposta completa (senza prefisso) per le metriche admin
|
|
|
|
| 369 |
if n_voci > 0:
|
| 370 |
answer_for_metrics = partial[len(prefix):]
|
| 371 |
else:
|
| 372 |
answer_for_metrics = partial
|
| 373 |
|
|
|
|
| 374 |
log_quality_metrics(question, voci, answer_for_metrics, modello_scelto)
|
| 375 |
|
| 376 |
# =======================
|