Spaces:
Running on Zero
Running on Zero
| """RAG Evaluation Benchmark Runner Script. | |
| Runs precision, recall, hit-rate, MRR, and faithfulness evaluations over | |
| benchmark queries using an isolated in-memory index to prevent polluting | |
| the main application vector store. | |
| Usage: | |
| python scripts/evaluate.py | |
| """ | |
| import json | |
| import logging | |
| from pathlib import Path | |
| import sys | |
| import tempfile | |
| # Ensure project root is in sys.path | |
| PROJECT_DIR = Path(__file__).resolve().parent.parent | |
| if str(PROJECT_DIR) not in sys.path: | |
| sys.path.insert(0, str(PROJECT_DIR)) | |
| import pymupdf as fitz | |
| from app.config import get_settings, setup_logging | |
| from app.embeddings.embedding_service import get_embedding_service | |
| from app.evaluation.dataset import BENCHMARK_DATASET | |
| from app.evaluation.evaluator import RAGEvaluator | |
| from app.generation.llm import LLMServiceFactory | |
| from app.ingestion.chunker import RecursiveChunker | |
| from app.ingestion.pdf_loader import PDFLoader | |
| from app.rag.pipeline import RAGPipeline | |
| from app.retrieval.retriever import DocumentRetriever | |
| from app.vectorstore.faiss_store import FAISSVectorStore | |
| def create_benchmark_pdf(target_path: Path) -> Path: | |
| """Creates the standard benchmark PDF corresponding to BENCHMARK_DATASET.""" | |
| target_path.parent.mkdir(parents=True, exist_ok=True) | |
| doc = fitz.open() | |
| # Page 1 | |
| page1 = doc.new_page() | |
| page1.insert_text( | |
| (50, 72), | |
| "Introduction to Retrieval-Augmented Generation\n\n" | |
| "RAG combines parametric knowledge in neural models with non-parametric external retrieval.\n" | |
| "The primary benefit of external retrieval in RAG is that it prevents hallucinations " | |
| "and provides direct citations for human verification.", | |
| ) | |
| # Page 2 | |
| page2 = doc.new_page() | |
| page2.insert_text( | |
| (50, 72), | |
| "Methodology and Experimental Setup\n\n" | |
| "The research evaluated FAISS vector indices with sentence-transformer embeddings.\n" | |
| "Evaluation demonstrated a 94% hit rate on domain queries when using recursive character chunking.", | |
| ) | |
| doc.save(str(target_path)) | |
| doc.close() | |
| return target_path | |
| def main(): | |
| settings = get_settings() | |
| setup_logging(settings.log_level) | |
| logger = logging.getLogger("eval_runner") | |
| logger.info("Initializing isolated RAG evaluation benchmark...") | |
| # Initialize isolated components to protect the active user's vector store | |
| embedder = get_embedding_service(settings.embedding_model) | |
| eval_store = FAISSVectorStore(dimension=embedder.dimension) | |
| loader = PDFLoader() | |
| chunker = RecursiveChunker(chunk_size=settings.chunk_size, chunk_overlap=settings.chunk_overlap) | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| bench_pdf_path = Path(temp_dir) / "sample_research.pdf" | |
| create_benchmark_pdf(bench_pdf_path) | |
| # Ingest benchmark PDF into the isolated evaluation store | |
| pages = loader.load(bench_pdf_path) | |
| chunks = chunker.chunk_documents(pages) | |
| embeddings = embedder.embed_documents([c.text for c in chunks]) | |
| eval_store.add_chunks(chunks, embeddings) | |
| eval_retriever = DocumentRetriever( | |
| vector_store=eval_store, | |
| embedding_service=embedder, | |
| similarity_threshold=settings.similarity_threshold, | |
| default_top_k=settings.top_k, | |
| ) | |
| llm_service = LLMServiceFactory.create( | |
| provider=settings.llm_provider, | |
| gemini_api_key=settings.gemini_api_key, | |
| openrouter_api_key=settings.openrouter_api_key, | |
| model_name=settings.llm_model, | |
| ) | |
| eval_pipeline = RAGPipeline( | |
| retriever=eval_retriever, | |
| llm_service=llm_service, | |
| ) | |
| # Run evaluator | |
| evaluator = RAGEvaluator(retriever=eval_retriever, pipeline=eval_pipeline) | |
| report = evaluator.evaluate(BENCHMARK_DATASET, top_k=5) | |
| # Print markdown report | |
| print("\n" + "=" * 60) | |
| print(" ๐ RAG BENCHMARK EVALUATION RESULTS") | |
| print("=" * 60 + "\n") | |
| print(report.summary_table()) | |
| print("\nQuery Breakdown:") | |
| for d in report.query_details: | |
| status = "โ HIT" if d["hit"] else "โ MISS" | |
| rank_str = f"Rank: #{d['first_rank']}" if d["first_rank"] else "Not in Top-K" | |
| print(f"- [{status}] '{d['question']}' ({rank_str}, Prec: {d['precision']}, Faith: {d['faithfulness']})") | |
| # Save results to data/processed/eval_results.json | |
| output_file = settings.get_processed_path() / "eval_results.json" | |
| with open(output_file, "w", encoding="utf-8") as f: | |
| json.dump( | |
| { | |
| "total_queries": report.total_queries, | |
| "hit_rate": report.hit_rate, | |
| "mrr": report.mrr, | |
| "avg_precision": report.avg_precision, | |
| "avg_faithfulness": report.avg_faithfulness, | |
| "details": report.query_details, | |
| }, | |
| f, | |
| indent=2, | |
| ) | |
| logger.info("Saved evaluation report to '%s'", output_file) | |
| if __name__ == "__main__": | |
| main() | |