Spaces:
Sleeping
Sleeping
| import os | |
| import sys | |
| import json | |
| import time | |
| import numpy as np | |
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| from app.dataset import load_documents | |
| from app.hybrid_search import BM25Index, HybridSearcher | |
| from app.vector_store import build_index | |
| from sentence_transformers import SentenceTransformer | |
| from app.llm import generate_answer, GeminiProvider | |
| from experiments.benchmark_data import benchmark_queries | |
| try: | |
| from app.reranker import get_reranker | |
| except ImportError: | |
| get_reranker = None | |
| # LLM-as-a-judge prompt templates | |
| FAITHFULNESS_PROMPT = """You are an impartial judge evaluating the faithfulness of an AI-generated answer. | |
| Your task is to determine if all claims made in the ANSWER are directly supported by the CONTEXT. | |
| CONTEXT: | |
| {context} | |
| ANSWER: | |
| {answer} | |
| Does the ANSWER contain any claims or information not present in the CONTEXT? | |
| Score 1.0 if the answer is completely faithful to the context (no hallucinations). | |
| Score 0.5 if it is partially faithful but includes some unsupported claims. | |
| Score 0.0 if the answer is entirely hallucinated or contradicts the context. | |
| Respond ONLY with the numerical score (e.g., 1.0, 0.5, 0.0). | |
| """ | |
| RELEVANCE_PROMPT = """You are an impartial judge evaluating how well an AI-generated answer addresses a user's query. | |
| QUERY: | |
| {query} | |
| ANSWER: | |
| {answer} | |
| Does the ANSWER directly address the QUERY? | |
| Score 1.0 if the answer fully and directly addresses the query. | |
| Score 0.5 if the answer is tangential or only partially addresses the query. | |
| Score 0.0 if the answer is completely irrelevant. | |
| Respond ONLY with the numerical score (e.g., 1.0, 0.5, 0.0). | |
| """ | |
| def extract_score(llm_response: str) -> float: | |
| try: | |
| # Try to extract the first float found in the response | |
| import re | |
| matches = re.findall(r'\d+\.\d+', llm_response) | |
| if matches: | |
| return float(matches[0]) | |
| # Try integers | |
| matches = re.findall(r'\d+', llm_response) | |
| if matches: | |
| return float(matches[0]) | |
| except: | |
| pass | |
| return 0.0 | |
| def run_rag_evaluation(): | |
| print("Loading ArXiv ML Papers Dataset...") | |
| docs, labels, _ = load_documents() | |
| docs = docs[:2000] | |
| labels = labels[:2000] | |
| print("Loading Embedding Model...") | |
| model = SentenceTransformer('all-MiniLM-L6-v2') | |
| print("Encoding Documents...") | |
| embeddings = model.encode(docs, show_progress_bar=True, convert_to_numpy=True, normalize_embeddings=True) | |
| faiss_index = build_index(embeddings, labels) | |
| print("Building BM25 Index...") | |
| bm25 = BM25Index() | |
| bm25.fit(docs) | |
| hybrid = HybridSearcher(bm25) | |
| reranker = get_reranker() if get_reranker else None | |
| llm_provider = GeminiProvider() | |
| if not llm_provider.model: | |
| print("GEMINI_API_KEY not found. Skipping RAG Evaluation.") | |
| return | |
| # To avoid rate limits, evaluate on the first 10 queries | |
| eval_queries = benchmark_queries[:10] | |
| print(f"\nEvaluating RAG Pipeline on {len(eval_queries)} queries...") | |
| results = [] | |
| faithfulness_scores = [] | |
| relevance_scores = [] | |
| for i, item in enumerate(eval_queries): | |
| query = item["query"] | |
| print(f"\n[{i+1}/{len(eval_queries)}] Query: {query}") | |
| # 1. Retrieve | |
| q_emb = model.encode([query], convert_to_numpy=True, normalize_embeddings=True)[0] | |
| cand_indices, _, _ = hybrid.search(query, q_emb, faiss_index, docs, top_k=30) | |
| cand_docs = [docs[idx] for idx in cand_indices] | |
| if reranker: | |
| final_indices, _ = reranker.rerank(query, cand_docs, cand_indices, top_k=3) | |
| else: | |
| final_indices = cand_indices[:3] | |
| retrieved_docs = [docs[idx] for idx in final_indices] | |
| # 2. Generate Answer | |
| print(" -> Generating Answer...") | |
| answer = generate_answer(query, retrieved_docs, provider=llm_provider) | |
| if not answer: | |
| print(" -> LLM Generation failed.") | |
| continue | |
| context_text = "\n\n".join(retrieved_docs) | |
| # 3. Evaluate Faithfulness | |
| f_prompt = FAITHFULNESS_PROMPT.format(context=context_text, answer=answer) | |
| f_response = llm_provider.generate(f_prompt) | |
| f_score = extract_score(f_response) if f_response else 0.0 | |
| # 4. Evaluate Relevance | |
| r_prompt = RELEVANCE_PROMPT.format(query=query, answer=answer) | |
| r_response = llm_provider.generate(r_prompt) | |
| r_score = extract_score(r_response) if r_response else 0.0 | |
| print(f" -> Faithfulness: {f_score} | Relevance: {r_score}") | |
| faithfulness_scores.append(f_score) | |
| relevance_scores.append(r_score) | |
| results.append({ | |
| "query": query, | |
| "answer": answer, | |
| "faithfulness": f_score, | |
| "relevance": r_score | |
| }) | |
| # Sleep to avoid rate limits | |
| time.sleep(2) | |
| print("\n" + "="*50) | |
| print("RAG EVALUATION METRICS (LLM-AS-A-JUDGE)") | |
| print("="*50) | |
| print(f"Faithfulness: {np.mean(faithfulness_scores):.4f}") | |
| print(f"Answer Relevance: {np.mean(relevance_scores):.4f}") | |
| print("="*50) | |
| os.makedirs("experiments/results", exist_ok=True) | |
| with open("experiments/results/rag_evaluation.json", "w") as f: | |
| json.dump(results, f, indent=2) | |
| if __name__ == "__main__": | |
| run_rag_evaluation() | |