manayporwal07 commited on
Commit
97d6a1e
·
verified ·
1 Parent(s): 300cb07

Update evaluator.py

Browse files
Files changed (1) hide show
  1. evaluator.py +188 -157
evaluator.py CHANGED
@@ -205,204 +205,235 @@
205
  ###############################################################################################################################
206
 
207
  # evaluator.py
208
- """
209
- Evaluator for Agentic Evaluation Framework
210
- """
211
-
 
212
  import numpy as np
213
  import pandas as pd
214
  import matplotlib.pyplot as plt
215
  import seaborn as sns
216
- import math, uuid, re
217
-
218
- from sentence_transformers import SentenceTransformer, util
219
- from transformers import AutoTokenizer, AutoModelForSequenceClassification
220
 
221
- # ------------------------
222
- # Models (lightweight)
223
- # ------------------------
224
- EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
225
  NLI_MODEL = "textattack/roberta-base-MNLI"
 
226
 
227
- _embed_model = SentenceTransformer(EMBED_MODEL)
228
- _nli_tokenizer = AutoTokenizer.from_pretrained(NLI_MODEL)
229
- _nli_model = AutoModelForSequenceClassification.from_pretrained(NLI_MODEL)
230
- _id2label = {int(k): v.upper() for k, v in _nli_model.config.id2label.items()}
231
-
232
- # ------------------------
233
- # Metrics
234
- # ------------------------
235
-
236
- def check_instruction_following(prompt, response):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  if not prompt or not response:
238
  return 0.0
239
- p_emb = _embed_model.encode(prompt, convert_to_tensor=True)
240
- r_emb = _embed_model.encode(response, convert_to_tensor=True)
 
241
  sim = float(util.cos_sim(p_emb, r_emb).item())
242
  return round(max(0.0, min(1.0, sim)), 3)
243
 
244
- def check_hallucination(prompt, response):
245
  if not prompt or not response:
246
  return 0.0
247
- inputs = _nli_tokenizer.encode_plus(prompt, response, return_tensors="pt", truncation=True)
248
- outputs = _nli_model(**inputs)
 
249
  probs = outputs.logits.softmax(dim=1).detach().cpu().numpy()[0]
250
- labels = [ _id2label[i] for i in range(len(probs)) ]
251
- entailment_prob = float(probs[labels.index("ENTAILMENT")]) if "ENTAILMENT" in labels else float(probs.max())
252
- return round(max(0.0, min(1.0, entailment_prob)), 3)
253
-
254
- def check_accuracy(reference, response):
255
- if not reference or not response:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  return 0.0
 
 
 
257
 
258
- # Compute BERTScore
259
- try:
260
- bert_results = bertscore.compute(predictions=[response], references=[reference], lang="en")
261
- bert_f1 = bert_results["f1"][0]
262
- except Exception:
263
- bert_f1 = 0.0
264
-
265
- # Compute BLEU
266
- try:
267
- bleu_results = bleu.compute(predictions=[response], references=[[reference.split()]])
268
- bleu_score = bleu_results["bleu"]
269
- except Exception:
270
- bleu_score = 0.0
271
-
272
- # Compute ROUGE-L
273
- try:
274
- rouge_results = rouge.compute(predictions=[response], references=[reference])
275
- rouge_l = rouge_results["rougeL"]
276
- except Exception:
277
- rouge_l = 0.0
278
 
279
- return float((bert_f1 + bleu_score + rouge_l) / 3)
 
 
280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
- # def check_coherence(response):
283
- # if not response:
284
- # return 0.0
285
- # sents = [s.strip() for s in re.split(r"[.!?]+", response) if s.strip()]
286
- # if len(sents) <= 1:
287
- # return 1.0
288
- # embs = _embed_model.encode(sents, convert_to_tensor=True)
289
- # sims = []
290
- # for i in range(len(embs)):
291
- # for j in range(i+1, len(embs)):
292
- # sims.append(float(util.cos_sim(embs[i], embs[j]).item()))
293
- # avg = np.mean(sims)
294
- # return round((avg + 1) / 2, 3) # normalize to [0,1]
295
- def check_coherence(response, reference=None):
296
- try:
297
- model = get_embed_model()
298
- emb = model.encode(response, convert_to_tensor=True, normalize_embeddings=True)
299
- if reference:
300
- ref_emb = model.encode(reference, convert_to_tensor=True, normalize_embeddings=True)
301
- return float(torch.nn.functional.cosine_similarity(emb, ref_emb, dim=0))
302
- else:
303
- return float(torch.norm(emb)) # or another meaningful scalar
304
- except Exception:
305
- return 0.0
306
 
307
 
308
- def check_fluency(response):
309
- if not response:
310
- return 0.0
311
- letters = sum(ch.isalpha() for ch in response)
312
- total = len(response)
313
- return round(letters / max(1, total), 3)
314
 
315
- # ------------------------
316
- # Evaluation
317
- # ------------------------
318
 
319
- def evaluate_dataframe(df: pd.DataFrame):
320
  scores = []
321
- for _, row in df.iterrows():
 
 
 
 
322
  s = {}
323
- s["instruction_following"] = check_instruction_following(str(row.get("prompt", "")), str(row.get("response", "")))
324
- s["hallucination"] = check_hallucination(str(row.get("prompt", "")), str(row.get("response", "")))
325
- s["accuracy"] = check_accuracy(str(row.get("reference", "")), str(row.get("response", "")))
326
- s["coherence"] = check_coherence(str(row.get("response", "")))
327
- s["fluency"] = check_fluency(str(row.get("response", "")))
328
- # clamp
 
329
  for k in s:
330
- s[k] = max(0.0, min(1.0, s[k]))
331
- s["final_score"] = round(float(np.mean(list(s.values()))), 3)
 
 
 
 
 
 
 
332
  scores.append(s)
333
 
334
  metrics_df = pd.concat([df.reset_index(drop=True), pd.DataFrame(scores)], axis=1)
335
- metric_cols = ["instruction_following", "hallucination", "accuracy", "coherence", "fluency", "final_score"]
336
 
 
337
  leaderboard = (
338
  metrics_df.groupby(["agent", "task_type"])[metric_cols]
339
  .mean()
340
  .reset_index()
341
  )
342
- return metrics_df, [], leaderboard
343
-
344
- # ------------------------
345
- # Visualizations
346
- # ------------------------
347
-
348
- def plot_radar_chart(leaderboard, metric_cols):
349
- categories = metric_cols
350
- angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False).tolist()
351
- angles += angles[:1]
352
- fig = plt.figure(figsize=(6,6))
353
- ax = plt.subplot(111, polar=True)
354
- for agent in leaderboard["agent"].unique():
355
- vals = leaderboard[leaderboard["agent"]==agent][metric_cols].mean().tolist()
356
- vals += vals[:1]
357
- ax.plot(angles, vals, label=agent)
358
- ax.fill(angles, vals, alpha=0.1)
359
- ax.set_xticks(angles[:-1])
360
- ax.set_xticklabels(categories)
361
- ax.set_ylim(0,1)
362
- ax.legend(loc="upper right")
363
- return fig
364
-
365
- def plot_heatmap(metrics_df, metric_cols):
366
- fig, ax = plt.subplots(figsize=(7,5))
367
- sns.heatmap(metrics_df[metric_cols].corr(), annot=True, fmt=".2f", cmap="coolwarm", ax=ax)
368
- return fig
369
 
370
- # def plot_boxplot(metrics_df, metric_cols):
371
- # fig, ax = plt.subplots(figsize=(7,5))
372
- # sns.boxplot(data=metrics_df[metric_cols], ax=ax)
373
- # return fig
374
-
375
- def plot_bar(leaderboard, metric_cols):
376
- fig, ax = plt.subplots(figsize=(8,5))
377
- leaderboard.plot(x="agent", y="final_score", kind="bar", ax=ax, legend=False)
378
- ax.set_ylabel("Final Score")
379
- return fig
380
 
381
- def generate_visualizations(metrics_df, leaderboard):
382
- metric_cols = ["instruction_following", "hallucination", "accuracy", "coherence", "fluency", "final_score"]
383
- figs = []
384
  try:
385
- figs.append(plot_radar_chart(leaderboard, metric_cols))
 
 
 
 
 
 
 
 
386
  except Exception as e:
387
- print("Radar failed:", e)
 
388
  try:
389
- figs.append(plot_heatmap(metrics_df, metric_cols))
 
 
 
 
390
  except Exception as e:
391
  print("Heatmap failed:", e)
392
- # try:
393
- # figs.append(plot_boxplot(metrics_df, metric_cols))
394
- # except Exception as e:
395
- # print("Boxplot failed:", e)
396
- try:
397
- figs.append(plot_bar(leaderboard, metric_cols))
398
- except Exception as e:
399
- print("Bar failed:", e)
400
 
401
- # Save to temp and return as gallery list
402
- images = []
403
- for fig in figs:
404
- path = f"/tmp/viz_{uuid.uuid4().hex}.png"
405
- fig.savefig(path, bbox_inches="tight")
406
- plt.close(fig)
407
- images.append(path)
408
- return images
 
205
  ###############################################################################################################################
206
 
207
  # evaluator.py
208
+ import math
209
+ import uuid
210
+ import json
211
+ import re
212
+ from typing import List, Dict, Tuple
213
  import numpy as np
214
  import pandas as pd
215
  import matplotlib.pyplot as plt
216
  import seaborn as sns
 
 
 
 
217
 
218
+ # -----------------------------
219
+ # Lazy model loading
220
+ # -----------------------------
 
221
  NLI_MODEL = "textattack/roberta-base-MNLI"
222
+ EMBED_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
223
 
224
+ _nli_tokenizer = None
225
+ _nli_model = None
226
+ _embed_model = None
227
+ _id2label = None
228
+
229
+ def ensure_models_loaded():
230
+ global _nli_tokenizer, _nli_model, _embed_model, _id2label, util
231
+ if _embed_model is None:
232
+ from sentence_transformers import SentenceTransformer, util
233
+ _embed_model = SentenceTransformer(EMBED_MODEL)
234
+ globals()["util"] = util
235
+ if _nli_model is None:
236
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
237
+ _nli_tokenizer = AutoTokenizer.from_pretrained(NLI_MODEL)
238
+ _nli_model = AutoModelForSequenceClassification.from_pretrained(NLI_MODEL)
239
+ _nli_model.to("cpu")
240
+ _nli_model.eval()
241
+ _id2label = {int(k): v.upper() for k, v in _nli_model.config.id2label.items()}
242
+
243
+ def get_embed_model():
244
+ ensure_models_loaded()
245
+ return _embed_model
246
+
247
+ def get_nli_tokenizer_and_model():
248
+ ensure_models_loaded()
249
+ return _nli_tokenizer, _nli_model, _id2label
250
+
251
+
252
+ # -----------------------------
253
+ # Metric functions
254
+ # -----------------------------
255
+
256
+ def check_instruction_following(prompt: str, response: str) -> float:
257
  if not prompt or not response:
258
  return 0.0
259
+ embed_model = get_embed_model()
260
+ p_emb = embed_model.encode(prompt, convert_to_tensor=True)
261
+ r_emb = embed_model.encode(response, convert_to_tensor=True)
262
  sim = float(util.cos_sim(p_emb, r_emb).item())
263
  return round(max(0.0, min(1.0, sim)), 3)
264
 
265
+ def check_hallucination(prompt: str, response: str) -> float:
266
  if not prompt or not response:
267
  return 0.0
268
+ tokenizer, model, id2label = get_nli_tokenizer_and_model()
269
+ inputs = tokenizer.encode_plus(prompt, response, return_tensors="pt", truncation=True)
270
+ outputs = model(**inputs)
271
  probs = outputs.logits.softmax(dim=1).detach().cpu().numpy()[0]
272
+ labels = [id2label[i] for i in range(len(probs))]
273
+ if "ENTAILMENT" in labels:
274
+ entailment_prob = float(probs[labels.index("ENTAILMENT")])
275
+ else:
276
+ entailment_prob = float(probs.max())
277
+ return round(entailment_prob, 3)
278
+
279
+ def check_accuracy(prompt: str, response: str, metadata: str) -> float:
280
+ """
281
+ Accuracy:
282
+ - if metadata contains an 'answer', compare response vs answer using embeddings
283
+ - else fall back to entailment probability (NLI)
284
+ """
285
+ answer = None
286
+ if isinstance(metadata, str) and metadata.strip().startswith("{"):
287
+ try:
288
+ meta = json.loads(metadata)
289
+ for k in ("answer", "reference", "ground_truth"):
290
+ if k in meta:
291
+ answer = meta[k]
292
+ break
293
+ except Exception:
294
+ pass
295
+ elif isinstance(metadata, dict):
296
+ for k in ("answer", "reference", "ground_truth"):
297
+ if k in metadata:
298
+ answer = metadata[k]
299
+ break
300
+
301
+ if answer:
302
+ embed_model = get_embed_model()
303
+ ref_emb = embed_model.encode(str(answer), convert_to_tensor=True)
304
+ r_emb = embed_model.encode(response, convert_to_tensor=True)
305
+ sim = float(util.cos_sim(ref_emb, r_emb).item())
306
+ return round(max(0.0, min(1.0, sim)), 3)
307
+ else:
308
+ return check_hallucination(prompt, response)
309
+
310
+ def check_coherence(response: str) -> float:
311
+ """
312
+ Coherence: average pairwise sentence similarity if >1 sentence,
313
+ otherwise fallback heuristic.
314
+ """
315
+ if not response or not isinstance(response, str) or response.strip() == "":
316
+ return 0.25
317
+ sents = [s.strip() for s in re.split(r"[.!?]+", response) if s.strip()]
318
+ embed_model = get_embed_model()
319
+ if len(sents) > 1:
320
+ try:
321
+ embs = embed_model.encode(sents, convert_to_tensor=True)
322
+ sims = []
323
+ for i in range(len(embs)):
324
+ for j in range(i + 1, len(embs)):
325
+ sims.append(float(util.cos_sim(embs[i], embs[j]).item()))
326
+ if sims:
327
+ score = (np.mean(sims) + 1.0) / 2.0
328
+ return round(max(0.0, min(1.0, score)), 3)
329
+ except Exception:
330
+ pass
331
+ words = len(re.findall(r"\w+", response))
332
+ return round(min(0.8, max(0.3, words / 50.0)), 3)
333
+
334
+ def check_fluency(response: str) -> float:
335
+ if not response or not isinstance(response, str):
336
  return 0.0
337
+ letters = sum(ch.isalpha() for ch in response)
338
+ total = len(response)
339
+ return round(max(0.0, min(1.0, letters / max(1, total))), 3)
340
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
+ # -----------------------------
343
+ # Visualization helpers
344
+ # -----------------------------
345
 
346
+ def spider_net_multi(labels: List[str], rows: List[Dict], title: str, fill_alpha: float = 0.12):
347
+ N = len(labels)
348
+ angles = [n / float(N) * 2 * math.pi for n in range(N)]
349
+ angles += angles[:1]
350
+ fig = plt.figure(figsize=(6.5, 6.5))
351
+ ax = plt.subplot(111, polar=True)
352
+ ax.set_xticks(angles[:-1])
353
+ ax.set_xticklabels(labels, fontsize=9)
354
+ ax.set_ylim(0, 1)
355
+ ax.set_yticks([0, 0.25, 0.5, 0.75, 1.0])
356
+ for r in rows:
357
+ values = r["values"]
358
+ values_closed = values + values[:1]
359
+ ax.plot(angles, values_closed, linewidth=1.5, label=r["name"])
360
+ ax.fill(angles, values_closed, alpha=fill_alpha)
361
+ ax.set_title(title, y=1.08, fontsize=12)
362
+ ax.legend(loc="upper right", bbox_to_anchor=(1.25, 1.1))
363
+ return fig
364
 
365
+ def heatmap_plot(df: pd.DataFrame, metric_cols: List[str], title: str = "Metric Correlations"):
366
+ fig, ax = plt.subplots(figsize=(7, 5))
367
+ sns.heatmap(df[metric_cols].corr(), annot=True, fmt=".2f", cmap="coolwarm", ax=ax)
368
+ ax.set_title(title)
369
+ return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
 
372
+ # -----------------------------
373
+ # Main evaluation entrypoint
374
+ # -----------------------------
 
 
 
375
 
376
+ def evaluate_dataframe(df: pd.DataFrame) -> Tuple[pd.DataFrame, List[Tuple[str,str]], pd.DataFrame]:
377
+ df = df.copy()
 
378
 
 
379
  scores = []
380
+ for _, r in df.iterrows():
381
+ prompt = str(r.get("prompt", "") or "")
382
+ response = str(r.get("response", "") or "")
383
+ metadata = r.get("metadata", "")
384
+
385
  s = {}
386
+ s["instruction_following"] = check_instruction_following(prompt, response)
387
+ s["hallucination"] = check_hallucination(prompt, response)
388
+ s["accuracy"] = check_accuracy(prompt, response, metadata)
389
+ s["coherence"] = check_coherence(response)
390
+ s["fluency"] = check_fluency(response)
391
+
392
+ # clamp and nanmean
393
  for k in s:
394
+ try:
395
+ s[k] = float(s[k])
396
+ except Exception:
397
+ s[k] = np.nan
398
+ if not np.isnan(s[k]):
399
+ s[k] = max(0.0, min(1.0, s[k]))
400
+
401
+ vals = [v for v in s.values() if not np.isnan(v)]
402
+ s["FinalScore"] = round(float(np.nanmean(vals)) if vals else 0.0, 3)
403
  scores.append(s)
404
 
405
  metrics_df = pd.concat([df.reset_index(drop=True), pd.DataFrame(scores)], axis=1)
 
406
 
407
+ metric_cols = ["instruction_following", "hallucination", "accuracy", "coherence", "fluency", "FinalScore"]
408
  leaderboard = (
409
  metrics_df.groupby(["agent", "task_type"])[metric_cols]
410
  .mean()
411
  .reset_index()
412
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
+ images = []
 
 
 
 
 
 
 
 
 
415
 
 
 
 
416
  try:
417
+ rows = []
418
+ for agent in leaderboard["agent"].unique():
419
+ vals = leaderboard[leaderboard["agent"] == agent][metric_cols[:-1]].mean().tolist()
420
+ rows.append({"name": agent, "values": vals})
421
+ fig1 = spider_net_multi(metric_cols[:-1], rows, "Agent Performance Radar")
422
+ path1 = f"/tmp/radar_{uuid.uuid4().hex}.png"
423
+ fig1.savefig(path1, bbox_inches="tight")
424
+ plt.close(fig1)
425
+ images.append((path1, "Radar Plot"))
426
  except Exception as e:
427
+ print("Radar plot failed:", e)
428
+
429
  try:
430
+ fig2 = heatmap_plot(metrics_df, metric_cols[:-1], title="Metric Correlation Heatmap")
431
+ path2 = f"/tmp/heatmap_{uuid.uuid4().hex}.png"
432
+ fig2.savefig(path2, bbox_inches="tight")
433
+ plt.close(fig2)
434
+ images.append((path2, "Correlation Heatmap"))
435
  except Exception as e:
436
  print("Heatmap failed:", e)
 
 
 
 
 
 
 
 
437
 
438
+ return metrics_df, images, leaderboard
439
+