@@ -552,6 +552,70 @@ async def arena_run(req: ArenaRequest):
552552 eval_docs = random .sample (documents , min (req .n_queries , len (documents )))
553553 logger .info (f"Evaluating { len (eval_docs )} queries from { req .dataset } " )
554554
555+ # ── Per-pipeline evaluation ─────────────────────────────────────────
556+ def evaluate_pipeline (pipeline_cfg : ArenaPipeline , docs ):
557+ docs_copy = copy .deepcopy (docs )
558+ rr_latency = 0.0
559+ ret_results = docs_copy
560+
561+ # Reranking (retrieval already done — BEIR datasets come pre-retrieved)
562+ reranker = get_reranker (pipeline_cfg .rerankerCategory , pipeline_cfg .rerankerModel )
563+ if reranker :
564+ t1 = time .time ()
565+ ret_results = reranker .rank (ret_results )
566+ rr_latency = (time .time () - t1 ) * 1000 / max (1 , len (docs_copy ))
567+
568+ use_rr = reranker is not None
569+
570+ # ── Try Rankify's calculate_trec_metrics with downloaded QREL file ──
571+ ndcg_10 , mrr_10 = 0.0 , 0.0
572+ if qrel_path :
573+ try :
574+ metrics_obj = Metrics (ret_results )
575+ trec = metrics_obj .calculate_trec_metrics (
576+ ndcg_cuts = [10 ],
577+ map_cuts = [10 ],
578+ mrr_cuts = [10 ],
579+ qrel = qrel_path ,
580+ use_reordered = use_rr ,
581+ )
582+ ndcg_10 = trec .get ("ndcg@10" , 0.0 ) * 100
583+ mrr_10 = trec .get ("mrr@10" , 0.0 ) * 100
584+ logger .info (f"TREC eval: NDCG@10={ ndcg_10 :.2f} % MRR@10={ mrr_10 :.2f} %" )
585+ except Exception as e :
586+ logger .warning (f"calculate_trec_metrics failed ({ e } ), using binary fallback" )
587+
588+ # ── Pure-Python binary fallback using has_answer ─────────────────
589+ if ndcg_10 == 0.0 and mrr_10 == 0.0 :
590+ mrr_sum , ndcg_sum = 0.0 , 0.0
591+ for doc in ret_results :
592+ ctxs = (
593+ doc .reorder_contexts
594+ if (use_rr and getattr (doc , "reorder_contexts" , None ))
595+ else doc .contexts
596+ )
597+ if not ctxs :
598+ continue
599+ for i , ctx in enumerate (ctxs [:10 ]):
600+ if getattr (ctx , "has_answer" , False ):
601+ mrr_sum += 1.0 / (i + 1 )
602+ break
603+ dcg , rels = 0.0 , []
604+ for i , ctx in enumerate (ctxs [:10 ]):
605+ rel = 1 if getattr (ctx , "has_answer" , False ) else 0
606+ rels .append (rel )
607+ if rel :
608+ dcg += 1.0 / math .log2 (i + 2 )
609+ idcg = sum (r / math .log2 (i + 2 ) for i , r in enumerate (sorted (rels , reverse = True )) if r )
610+ if idcg > 0 :
611+ ndcg_sum += dcg / idcg
612+ n = len (ret_results )
613+ mrr_10 = (mrr_sum / n ) * 100 if n > 0 else 0.0
614+ ndcg_10 = (ndcg_sum / n ) * 100 if n > 0 else 0.0
615+ logger .info (f"Binary fallback: NDCG@10={ ndcg_10 :.2f} % MRR@10={ mrr_10 :.2f} %" )
616+
617+ return {"mrr_10" : mrr_10 , "ndcg_10" : ndcg_10 , "latency_ms" : rr_latency }
618+
555619 res_a = evaluate_pipeline (req .pipeline_a , eval_docs )
556620 res_b = evaluate_pipeline (req .pipeline_b , eval_docs )
557621
0 commit comments