semantic-search-api / experiments /evaluate_rag.py
TarunikaHF's picture
chore: setup multi-stage docker and render.yaml for deployment
85aae51
Raw
History Blame Contribute Delete
5.48 kB
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()