A production-grade tool for benchmarking and testing Retrieval-Augmented Generation (RAG) pipelines. Comes with a React dashboard, document Q&A with Supabase vector search, a CLI, and a REST API.
- Benchmark built-in pipelines against curated QA datasets
- Upload your own documents (PDF, DOCX, TXT, MD, YAML, XML, JSON, HTML, RTF) and ask questions
- Evaluate any answer with 7 metrics — using Groq (Llama 3) as the LLM judge
- React dashboard with real-time job polling and per-sample score breakdowns
- REST API for programmatic access
rag_eval/
├── core/
│ ├── models.py # Data models (RAGSample, BenchmarkConfig, BenchmarkReport)
│ ├── base_metric.py # Abstract base class for all metrics
│ └── evaluator.py # Central RAGEvaluator orchestration engine
│
├── metrics/
│ ├── __init__.py # Metrics registry + build_metrics() factory
│ ├── fact_accuracy.py # Fact accuracy (anti-hallucination)
│ ├── answer_quality.py # Answers the question (reverse question generation)
│ ├── context_coverage.py # Info coverage + source quality
│ ├── document_grounding.py # Stays in document (LLM-as-judge)
│ └── semantic_similarity.py # Meaning match + word overlap
│
├── pipelines/
│ ├── rag_pipelines.py # Naive (TF-IDF), Semantic (embeddings), Mock
│ └── supabase_pipeline.py # Production pipeline: hybrid retrieval from Supabase
│
├── datasets/
│ └── benchmark_data.py # Built-in QA datasets (science, tech, hallucination)
│
├── dashboard/ # React + Vite frontend
│ ├── src/
│ └── dist/ # Built output (generated — not committed)
│
├── tests/
│ └── test_framework.py # Test suite (no LLM required)
│
├── server.py # FastAPI REST server
├── benchmark.py # CLI tool
├── document_processor.py # Extract → chunk → embed → Supabase
├── groq_client.py # Groq (Llama 3) LLM wrapper
├── examples.py # Programmatic usage examples
└── requirements.txt
| Metric | Display Name | Description | LLM Required |
|---|---|---|---|
fact_accuracy |
Fact Accuracy | Fraction of answer claims supported by retrieved context | Optional |
answer_quality |
Answers the Question | Does the answer address what was actually asked? | Optional |
context_recall |
Info Coverage | How much of the ground truth appears in retrieved context? | Optional |
context_precision |
Source Quality | What fraction of retrieved chunks are truly relevant? | Optional |
document_grounding |
Stays in Document | Holistic LLM-judge check that answer doesn't go beyond context | Optional |
semantic_similarity |
Meaning Match | Cosine similarity between answer and ground truth embeddings | No |
rouge_l |
Word Overlap | Longest common subsequence F1 vs ground truth | No |
git clone https://github.com/your-username/rag-eval.git
cd rag-eval
pip install -r requirements.txtCreate a .env file (never commit this):
GROQ_API_KEY=gsk_... # https://console.groq.com
SUPABASE_URL=https://....supabase.co
SUPABASE_KEY=eyJ... # anon/service role keyLoad them before starting the server:
export $(cat .env | xargs)Run this SQL in your Supabase SQL Editor to create the vector table and search function:
-- Enable pgvector
create extension if not exists vector;
-- Documents table
create table documents (
id bigint generated always as identity primary key,
filename text not null,
chunk_index int not null,
content text not null,
embedding vector(384),
metadata jsonb default '{}'
);
-- Vector similarity search function
create or replace function match_documents(
query_embedding vector(384),
match_threshold float,
match_count int
)
returns table (
id bigint,
filename text,
content text,
similarity float
)
language sql stable
as $$
select id, filename, content,
1 - (embedding <=> query_embedding) as similarity
from documents
where 1 - (embedding <=> query_embedding) > match_threshold
order by similarity desc
limit match_count;
$$;cd dashboard
npm install
npm run build
cd ..python server.py
# Open http://localhost:8000Open http://localhost:8000 after starting the server. Three tabs:
- Benchmark — Run evaluations on built-in pipelines and datasets
- Evaluate — Test a single custom question/answer/context set
- Documents — Upload files, ask questions, and get scored answers
# Run a benchmark
python benchmark.py run --pipeline naive --dataset science --max-samples 3
# Compare two pipelines
python benchmark.py compare --pipelines naive,semantic --dataset tech --max-samples 2
# Skip LLM metrics (fast, no API cost)
python benchmark.py run --pipeline naive --dataset all --no-llm
# List available metrics and datasets
python benchmark.py list-metrics
python benchmark.py list-datasetsfrom core.models import RAGSample, BenchmarkConfig, MetricName
from core.evaluator import RAGEvaluator
samples = [
RAGSample(
question="What is photosynthesis?",
ground_truth="Photosynthesis converts light energy to chemical energy in plants.",
contexts=["Plants use sunlight, CO2, and water to produce glucose via photosynthesis."],
answer="Photosynthesis is how plants convert sunlight into energy as glucose."
)
]
config = BenchmarkConfig(
name="my_eval",
metrics=[MetricName.FAITHFULNESS, MetricName.GROUNDEDNESS, MetricName.ROUGE_L],
use_llm_judge=True,
)
evaluator = RAGEvaluator(config)
report = evaluator.evaluate_dataset(samples, verbose=True)
evaluator.print_summary(report)See examples.py for 5 detailed usage patterns (pipeline comparison, custom thresholds, single-sample analysis, and more).
Upload any of these to the Documents tab for Q&A:
| Type | Extensions |
|---|---|
.pdf |
|
| Word | .docx, .doc |
| Plain text | .txt, .md |
| YAML | .yaml, .yml |
| XML | .xml |
| JSON | .json |
| HTML | .html, .htm |
| Rich Text | .rtf |
| Endpoint | Method | Description |
|---|---|---|
/ |
GET | Serve React dashboard |
/api/benchmark |
POST | Start a benchmark job |
/api/jobs |
GET | List all jobs |
/api/jobs/{id} |
GET | Poll job status |
/api/results/{id} |
GET | Get completed results |
/api/evaluate-sample |
POST | Evaluate a single sample |
/api/metrics |
GET | List all available metrics |
/api/datasets |
GET | List all built-in datasets |
/api/upload |
POST | Upload a document to Supabase |
/api/documents |
GET | List uploaded documents |
/api/documents/{filename} |
DELETE | Delete a document |
/api/ask |
POST | Ask a question against uploaded documents |
curl -X POST http://localhost:8000/api/ask \
-H "Content-Type: application/json" \
-d '{
"question": "What is the ESS algorithm?",
"filename": "my_paper.pdf",
"ground_truth": "optional — include to get evaluation scores"
}'| Dataset | Samples | Domains |
|---|---|---|
science |
3 | biology, physics, geology |
tech |
3 | ML, security, databases |
hallucination |
2 | astronomy, history (tests hallucination resistance) |
all |
8 | All of the above |
- Create
metrics/your_metric.pysubclassingBaseMetric - Add
MetricName.YOUR_METRICtocore/models.py - Register it in
metrics/__init__.py
from core.base_metric import BaseMetric
from core.models import RAGSample, MetricResult, MetricName
class YourMetric(BaseMetric):
@property
def name(self) -> MetricName:
return MetricName.YOUR_METRIC
def compute(self, sample: RAGSample) -> MetricResult:
score = ... # your logic
return MetricResult(
metric_name=self.name,
score=score,
explanation="...",
threshold=self.threshold
)The .gitignore already excludes:
.env— API keys (Groq, Supabase)venv/— Python virtual environmentdashboard/node_modules/— Node packagesdashboard/dist/— Built React app (regenerate withnpm run build)uploaded_files/— User-uploaded documentsreports/— Generated evaluation JSON reports__pycache__/— Python bytecode.DS_Store— macOS metadata
- Hybrid retrieval: Keyword scan (BM25-style) + pgvector similarity — robust even when embeddings are imperfect
- Graceful degradation: Every LLM metric falls back to a heuristic if the API fails
- Pluggable pipelines: Bring your own RAG pipeline via
BasePipeline - Free to run: Uses Groq's free tier (Llama 3.1 8B) for both generation and evaluation