Shipped

ProofRAG

Open source · Python CLI & library · PyPI

A reproducible evaluation loop for RAG systems: generate golden sets, run predictions, score retrieval and generation, then gate regressions in CI.
PythonCLIGitHub ActionsPyPIpytestRAG EvaluationLLM-as-JudgeTypeSafe / JevDeepEvalRagas

At a glance

01Problem
RAG changes to a chunker, retriever, prompt or model are usually compared by impression, because there is no stable evaluation set grounded in the real corpus.
02What I did
Built and maintain ProofRAG: the CLI and Python package on PyPI, the GitHub Action and the agent skill, from golden-set generation to the CI gates.
03Evaluation
188 tests run in CI on Python 3.11 to 3.14. Three controlled case studies on 75 questions, each checked against exact evidence before scoring.
04Result
v0.9.0 is on PyPI. Runs gate regressions against a committed baseline and refuse to compare incompatible runs. The case studies found no universal rule: doubling context raised recall slightly but made answers, citations and cost worse.

ProofRAG started from a practical RAG problem: teams can change a chunker, retriever, reranker, prompt, model, or context-packing strategy, but without a stable evaluation set they are mostly comparing impressions. The hard part is not only running metrics; it is producing a useful golden set from the actual corpus and keeping the evaluation loop repeatable.

The tool packages that loop as both a Python CLI and an agent skill. It reads a corpus, generates and validates corpus-grounded test cases, calls the user's RAG system through HTTP or a Python callable, judges answers with a pinned judge or the optional typed Jev backend, computes retrieval metrics, and emits a self-contained HTML scorecard.

One dependency-light evaluation package.

ProofRAG runs on Python 3.11+ and keeps its core install dependency-free. The same workflow is available through three interfaces.

Command lineAgent skillGitHub Action
MIT licensedPackage: proofrag
Retrievalevidence coverage
Did the system find the evidence?
  • Recall at k
  • Precision at k
  • Normalized discounted gain
  • Mean reciprocal rank
Generationanswer quality
Did the answer use it well?
  • Groundedness
  • Correctness
  • Completeness
  • Citation quality
The evaluation keeps retrieval and generation separate so an answer cannot hide weak evidence behind fluent prose.

Engineering

ProofRAG grew out of the same evaluation problems turning up again and again in enterprise RAG work, so it's built as a tool other people can drop into their pipelines, not a notebook.

  • Swappable providers and scorers. Anthropic, OpenAI and any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio) sit behind one client, and the built-in judge can be swapped for DeepEval, Ragas or Jev. Retrieval metrics, scorecards, diff and the CI gates keep one output contract across all of them. Your RAG app is called over HTTP or as a Python callable, so changing its model or vector store doesn't touch the evaluation code.
  • Judge output is validated, not trusted. Judge JSON is parsed strictly: duplicate keys, malformed objects, non-finite numbers and boolean or string scores are rejected, and truncated or filtered provider responses are errors. A bad judgment fails the run instead of quietly becoming a zero or a tie. The Jev backend keeps its typed rubric probabilities in the results.
  • Fingerprints and baseline-drift protection. Each results file records the golden-set fingerprint, matcher, cutoff, backend, metric schema and judge identity. proofrag diff exits with an error when those differ between baseline and candidate, and a different judge needs an explicit --allow-judge-mismatch.
  • CI gates. An absolute floor (--fail-under) and a regression gate against a committed baseline. The composite GitHub Action runs both, writes a job summary, and uploads the scorecard and results from the current run, including when a gate fails.
  • Release hygiene. 188 pytest tests, run in CI on Python 3.11 to 3.14 alongside ruff, codespell and basedpyright. Versions come from git tags, and a GitHub release publishes to PyPI through trusted publishing after checking that the tag matches the changelog, the Action's default version and the plugin manifests. v0.9.0 shipped on 4 October 2026.

Design Decisions

  • Fail rather than score partial data. Missing, duplicate or unexpected prediction ids, provider failures and invalid judgments end the run with an error. A flaky provider can fail a CI job as a result; a scorecard built on half the cases would be worse.
  • A dependency-free core. The base install has no dependencies. Anthropic, OpenAI, DeepEval, Ragas and PDF support are extras, and Jev is called over its HTTP API without adding one.
  • Static reports. Scorecards and A/B reports are self-contained HTML with no JavaScript or external assets, so they work as CI artifacts and open anywhere. Artifact data is escaped before it is written.
  • No retriever of its own. ProofRAG scores what your system returns. Adapters accept text answers and text contexts only and reject ambiguous objects instead of coercing them to strings.

From Corpus to Gate

The workflow is intentionally explicit. Every stage leaves behind an artifact that can be reviewed, committed, compared, or uploaded by CI.

  1. 01
    Generategoldenset.jsonl

    Build corpus-grounded questions and expected contexts.

  2. 02
    Validatevalidation.json

    Check coverage, duplicates, sources, and corpus drift.

  3. 03
    Runpredictions.jsonl

    Call the RAG app through HTTP or a Python callable.

  4. 04
    Evaluateresults.json

    Score retrieval and generation with a pinned judge.

  5. 05
    Reportscorecard.html

    Package the run into a static, reviewable scorecard.

proofrag@local: evaluation-loop
$ proofrag generate --corpus ./docs --out goldenset.jsonl --n 20
20 grounded cases · 14 sources · corpus fingerprint saved
$ proofrag validate --goldenset goldenset.jsonl --corpus ./docs
validation passed · coverage and source references are consistent
$ proofrag run --goldenset goldenset.jsonl --endpoint http://localhost:8000/ask
predictions written to predictions.jsonl
$ proofrag evaluate --goldenset goldenset.jsonl --predictions predictions.jsonl
retrieval and generation scores written to results.json
$ proofrag report --results results.json --out scorecard.html
scorecard.html is ready to review
One continuous CLI run turns a source corpus into a reviewable evaluation report; each command can also run independently.

Golden Set Design

A benchmark is only useful when its cases stay grounded in the material the RAG system is meant to serve. ProofRAG therefore treats the golden set as a versioned evaluation asset, not temporary prompt output.

Corpus-grounded

Questions come from the evaluated corpus, with source and chunk metadata kept alongside every expected context.

Coverage-aware

Single-document, multi-document, and unanswerable cases expose different failure modes instead of collapsing quality into one prompt shape.

Drift-detecting

Validation catches duplicate cases, missing contexts, weak source coverage, and a corpus fingerprint that no longer matches.

Reports You Can Review

Reports are static HTML files with no external assets. They can be opened locally after an agent run, sent to a teammate, or retained as a CI artifact without a hosted dashboard.

ProofRAG HTML scorecard showing RAG evaluation metrics
The same evaluation data can be read as a complete scorecard or as a blind side-by-side comparison.

CI Gates

The GitHub Action turns the same evaluation into a merge gate. An absolute mode enforces a score floor; a regression mode compares compatible candidate and baseline runs against the configured tolerance. Gates use full-precision scores even when reports round the displayed values. Invalid judgments and incompatible runs fail the check.

Gate Modes

Absolute
--fail-under
Regression
proofrag diff
Artifacts
html + json
Summary
markdown
Absolute gateProtect a minimum quality bar.

Useful when the team already knows the lowest acceptable score for a release.

Regression gateProtect the current baseline.

Useful while changing the pipeline, when the direction of a metric matters more than a universal threshold.

github@actions: regression-gate
› uses: unshDee/[email protected]
goldenset: eval/goldenset.jsonl
predictions: predictions.jsonl
baseline: eval/baseline.json
fail-under: 0.7
› proofrag diff --baseline eval/baseline.json --candidate results.json
checking score floors and tolerated metric deltas
gate passed · no metric regressed beyond tolerance
The GitHub Action runs the same diff and threshold logic as the local CLI, then publishes the report and machine-readable result.

Three Ways In

ProofRAG exposes one evaluation model through three entry points. The interface changes, but the data contracts, metrics, reports, and gates do not.

Direct control
Use each stage independently.

The command line is the clearest surface for local experiments, scripts, and inspecting intermediate JSON or JSONL artifacts.

Scoring Backends

The default path uses ProofRAG's pinned LLM judge. DeepEval and Ragas can replace generation scoring while retrieval metrics, reporting, diffing, and CI behavior stay on the same shared surface.

Built-in judgeJevTypeSafe APIDeepEvalRagas
Generation scoring
Answer scorer Native Typed rubrics Adapter Adapter
Shared evaluation surface
Retrieval metrics Included Included Included Included
Static scorecard Included Included Included Included
Diff and CI gates Included Included Included Included
Generation backends change the answer-scoring adapter, not the surrounding evaluation workflow.

Typed Jev Judging

Version 0.9.0 adds evaluate --backend jev through the official TypeSafe API. It needs a TYPESAFE_API_KEY and no extra Python dependency. The pinned jev-1.13.0 model scores groundedness, correctness, completeness, and citation quality using four ordered five-level rubrics in one request per case. Probability-weighted scores are normalized from 0–4 to ProofRAG's 0–1 scale.

Results retain raw probabilities, confidence, the resolved model, token usage, and a rubric fingerprint. Invalid distributions, provider failures, or model drift invalidate the run. Confidence describes rubric uncertainty; hosted RAG accuracy and dataset calibration have not been benchmarked by ProofRAG. Thresholds still need review against representative human labels.

Jev is available for evaluation only. Golden-set generation and blind A/B comparison continue to use Anthropic or an OpenAI-compatible provider.

Upgrading Baselines

Native judging now includes every retrieved context and JSON-encodes the evaluated text as data. Native evaluation and comparison fingerprints move to v3; DeepEval and Ragas fingerprints move to v2. Rerun older baselines with the same package version, backend, and judge before using them to gate new results. Keep baselines separate across scoring backends, even when their metric names match.

Case Studies

Introduced in v0.8.0, these three controlled studies apply the evaluation loop to retriever choice, index enrichment, and context depth. Each study freezes the rest of the stack and retains official hash-checked sources, reviewed golden data, predictions, scorecards, costs, failure analysis, judge disagreements, and reproduction steps. These are the retained v0.8.0 results, not new v0.9.0 or Jev benchmarks.

Case-study suite

3
controlled case studies
75
audited questions
21
official source documents
v0.9.0
published release

All 75 questions were reviewed against exact evidence before scoring; claims remain bounded to each pinned corpus and setup.

Study Index

The three experiments cover retriever choice, index enrichment, and context depth. None produced a universal optimization rule; together they show why retrieval and generation need separate measurements.

Case study 01

SQLite FTS5 vs Token Overlap

Does SQLite FTS5/BM25 retrieve Python concurrency documentation more effectively than a unique-token-overlap baseline?

30
questions
333
corpus chunks
13–6
FTS5 vs overlap blind wins
11
blind ties

Corpus and control

The corpus pins eight official Python 3.14.7 concurrency documentation files to one CPython commit. A paragraph-aware 700-character target produced 333 chunks from 195,561 parsed characters. The final benchmark contains 21 single-document, 6 multi-document, and 3 corpus-unanswerable questions; review changed 17 of 30 generated records.

Changed
Shared-token count → SQLite FTS5/BM25
Held fixed
Corpus, chunking, top five, prompt, model, golden set
Answer model and judge
Claude Haiku 4.5, temperature 0
Audit integrity
8/8 sources · 30/30 unique IDs · 0 warnings

Result

ProofRAG's pre-specified Jaccard matcher favored FTS5 by 0.037 Recall@5 and 0.032 NDCG@5. Exact chunk identity—added as a post-hoc sensitivity because both systems return from the same chunk universe—tied overall recall at 0.852 and narrowed the NDCG gap to 0.007. The material separation remained in multi-document ranking.

Exact NDCG@5 by question difficulty
Exact matching removes the headline overall recall advantage but preserves a 0.323 FTS5 NDCG lead on the six multi-document questions.
Token overlapbaselineSQLite FTS5BM25
Pre-specified Jaccard matcher
Recall@5 0.870 0.907
NDCG@5 0.876 0.908
Post-hoc exact-chunk sensitivity
Exact Recall@5 0.852 0.852
Exact NDCG@5 0.837 0.844
Generated answers
Overall 0.870 0.893
Correctness 0.892 0.870
Completeness 0.793 0.857
FTS5 improved completeness and citation quality, but correctness moved toward token overlap; one aggregate score is not the full result.
Blind A/B · 30 answersFTS5 won 13; overlap won 6; 11 tied.

FTS5 won 5 of 6 multi-document comparisons. Its 68.4% decided win rate applies only after excluding ties—not to all 30 questions.

What the audit exposed

01
Near-duplicate evidence can create false retrieval credit.

On q008, FTS5 returned terminate_workers() instead of the gold kill_workers() passage. Jaccard counted it; exact matching correctly recorded a miss.

02
Safe refusal can hide a broken retriever.

Reversing BM25 order drove retrieval metrics to zero while groundedness rose to 1.0 because the model refused. ProofRAG's multi-metric diff still failed CI on six regressions.

03
The strongest claim stays narrow.

Thirty questions, six multi-document cases, lexical systems, and one judge pass support this corpus-specific result—not “FTS5 always wins.”

Case study 02

HTTP RFC Section Metadata

Does adding RFC identity, document title, and nearest section heading to a BM25 index materially improve retrieval across HTTP standards?

21
questions
1,859
corpus chunks
+0.018
metadata NDCG@5 gain
+0.050
required material gain

Corpus and decision rule

Seven immutable plaintext RFC publications—HTTP Semantics, Caching, HTTP/1.1, HTTP/2, HTTP/3, QPACK, and RFC 9931—produce 1,859 section-bound chunks from 1,165,186 bytes. The 21-case design includes 8 structure-dependent, 7 lexical-control, 4 multi-document, and 2 corpus-unanswerable questions.

Changed
Indexed body → RFC + title + heading + body
Returned evidence
Same raw chunk bodies; metadata never reaches answer model
Adoption threshold
NDCG@5 gain ≥ 0.050; Recall@5 loss no worse than 0.020
Audit integrity
7/7 sources · 21/21 records · 0 errors · 0 warnings

Result

Metadata lifted exact Recall@5 by 0.026 and NDCG@5 by 0.018. The latter missed the predeclared 0.050 materiality threshold. Benefits clustered in structure-oriented and multi-document questions; lexical-control NDCG fell by 0.053.

Exact NDCG@5 by predeclared question group
Section enrichment helped where document structure carried signal and hurt the lexical-control slice, producing a small aggregate gain.
Body onlyraw chunksSection metadataindex only
Exact retrieval
Recall@5 0.816 0.842
Precision@5 0.189 0.200
NDCG@5 0.711 0.729
MRR 0.689 0.711
Generated answers
Overall 0.850 0.827
Citation quality 0.860 0.805
Safe refusals 2 / 2 2 / 2
Metadata improved every aggregate retrieval metric, but body-only answers received the higher absolute generation score.
Blind A/B · 21 answersMetadata won 4; body won 1; 16 tied.

Only five pairs were decided. Project audit agreed with two of those verdicts and disagreed with three, limiting the strength of the apparent 4–1 edge.

Failure analysis

01
Metadata solved part of one retrieval gap.

For q018 it found the QPACK capacity instruction at rank four, but missed the paired HTTP/3 rationale. Both variants still refused rather than answering half the question.

02
More retrieval signal did not improve generated answers.

Body-only scored 0.850 overall versus metadata's 0.827. Four multi-document records drove the largest separation, too small a slice for a general harm claim.

03
The threshold did its job.

A positive delta did not become a success story after the fact. The predeclared decision remains “threshold not met.”

Case study 03

OWASP Context Depth

Does doubling retrieved context from three to six chunks improve account-security answers enough to justify the extra context and cost?

24
questions
414
corpus chunks
+0.024
top-6 recall gain
7–5
top-3 vs top-6 blind wins

Corpus and pre-registration

Six official OWASP Cheat Sheet Markdown files cover authentication, sessions, password storage, password recovery, MFA, and credential stuffing. A hard 550-character chunk limit produced 414 chunks. The audited 24-case set contains 16 single-document, 5 multi-document, and 3 corpus-unanswerable questions.

Changed
Top three → top six FTS5/BM25 chunks
Held fixed
Ranker, query, prompt, models, corpus, golden set
Answer / judge split
Anthropic Haiku 4.5 / OpenAI GPT-4o mini
Audit integrity
6/6 sources · 24/24 IDs · 0 errors · 0 warnings

Result

Top six recovered slightly more exact evidence but did not increase the number of full-evidence cases. Groundedness, completeness, and citation quality fell. Aggregate formatted context doubled, answer input tokens rose 78%, and answer-phase estimated cost rose 49%.

Answer quality and exact retrieval recall
Expanded context improved exact recall by 0.024 while all four judged answer dimensions moved downward in the retained raw evaluation.
Top 3compact contextTop 6expanded context
Generated answers
Groundedness 0.917 0.875
Correctness 0.896 0.854
Completeness 0.875 0.854
Citation quality 0.917 0.792
Exact retrieval
Recall@6 0.881 0.905
NDCG@6 0.897 0.908
Full-evidence cases 17 / 21 17 / 21
Answer phase
Input tokens 10,460 18,634
Estimated cost $0.0207 $0.0309
The extra three chunks eliminated one zero-recall case but did not create another full-evidence answer; compact context remained cheaper and scored better.
2.04×formatted context
1.78×answer input tokens
1.49×answer estimated cost
RequiredObservedDecision
Completeness improvement ≥ +0.050 −0.021 No
Top-6 decided win rate ≥ 60% 41.7% No
Groundedness change ≥ −0.030 −0.042 No
All three pre-registered adoption criteria failed, so the study retains top three for this configuration.
Blind A/B · 24 answersTop 3 won 7; top 6 won 5; 12 tied.

Expanded context won 41.7% of the 12 decided pairs, below the pre-registered 60% threshold.

Judge sensitivity

01
One equivalent refusal received opposite absolute scores.

On q013, top three received four 1.0 scores; the equivalent top-six refusal received four zeros. The blind judge preferred top six. Raw outputs remain unchanged in the report.

02
Sensitivity analysis did not change the decision.

Replacing only q013's top-six zeros with ones makes groundedness tie and lifts completeness by 0.021—still short of the required 0.050, with the blind win rate unchanged.

03
“More context” remained a corpus-specific loss.

Twenty-four questions, one lexical ranker, one chunk size, and one judge cannot establish a universal context-window rule.

What the Three Studies Say Together

Better retrieval metrics do not guarantee better answers. Positive deltas do not automatically clear a materiality threshold. A blind judge can disagree with an absolute judge—and a project audit can disagree with both. ProofRAG keeps those disagreements visible instead of compressing them into a single leaderboard number.

01 · Retriever choice
Slice results before declaring a winner.

FTS5's clearest Python advantage lived in multi-document retrieval, while token overlap ranked single-document evidence better.

02 · Index enrichment
Small positive movement can still mean “no.”

RFC metadata helped structured questions but missed the predeclared aggregate threshold and reduced lexical-control ranking.

03 · Context depth
Retrieved evidence must survive generation.

OWASP top six found slightly more evidence while using more tokens and producing lower judged answer quality.

Related RAG Utilities

I also maintain rag-utils[1], an intentionally loose collection of scripts I reach for while building RAG systems rather than a second end-to-end product.

Corpus preparation

  • DOCX chunks with media metadata
  • PDF to Markdown conversion
  • Chunk quality scoring
  • Overlap merging and semantic deduplication
  • SQLite embedding cache

Retrieval analysis

  • Offline retrieval evaluation
  • Context window packing
  • Query expansion and HyDE
  • Span-based RAG tracing
  • Hybrid retrieval with reciprocal rank fusion