ProofRAG

Python CLI · Agent skill · GitHub Action

A reproducible evaluation loop for RAG systems: generate golden sets, run predictions, score retrieval and generation, then gate regressions in CI.
RAG EvaluationLLM-as-JudgePython CLIAgent SkillsGitHub ActionsDeepEvalRagas

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 LLM-as-judge, 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.

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 the candidate run with a committed baseline and fails only when a metric moves beyond the configured tolerance.

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 judgeDeepEvalRagas
Generation scoring
Answer scorer Native Adapter Adapter
Shared evaluation surface
Retrieval metrics Included Included Included
Static scorecard Included Included Included
Diff and CI gates Included Included Included
Generation backends change the answer-scoring adapter, not the surrounding evaluation workflow.

Case Studies

Release 0.8.0 applies that evaluation loop to three controlled retrieval questions. 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.

Case-study suite

3
controlled case studies
75
audited questions
21
official source documents
v0.8.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