ProofRAG
Python CLI · Agent skill · GitHub Action
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.
ProofRAG runs on Python 3.11+ and keeps its core install dependency-free. The same workflow is available through three interfaces.
- Recall at k
- Precision at k
- Normalized discounted gain
- Mean reciprocal rank
- Groundedness
- Correctness
- Completeness
- Citation quality
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.
- 01Generategoldenset.jsonl
Build corpus-grounded questions and expected contexts.
- 02Validatevalidation.json
Check coverage, duplicates, sources, and corpus drift.
- 03Runpredictions.jsonl
Call the RAG app through HTTP or a Python callable.
- 04Evaluateresults.json
Score retrieval and generation with a pinned judge.
- 05Reportscorecard.html
Package the run into a static, reviewable scorecard.
$ 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
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.

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
Useful when the team already knows the lowest acceptable score for a release.
Useful while changing the pipeline, when the direction of a metric matters more than a universal threshold.
› 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
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.
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 judge | DeepEval | Ragas | |
|---|---|---|---|
| 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 |
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.
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.
| Token overlapbaseline | SQLite 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 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
On q008, FTS5 returned terminate_workers() instead of the gold kill_workers() passage. Jaccard counted it; exact matching correctly recorded a miss.
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.
Thirty questions, six multi-document cases, lexical systems, and one judge pass support this corpus-specific result—not “FTS5 always wins.”
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.
| Body onlyraw chunks | Section 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 |
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
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.
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.
A positive delta did not become a success story after the fact. The predeclared decision remains “threshold not met.”
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%.
| Top 3compact context | Top 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 |
| Required | Observed | Decision | |
|---|---|---|---|
| Completeness improvement | ≥ +0.050 | −0.021 | No |
| Top-6 decided win rate | ≥ 60% | 41.7% | No |
| Groundedness change | ≥ −0.030 | −0.042 | No |
Expanded context won 41.7% of the 12 decided pairs, below the pre-registered 60% threshold.
Judge sensitivity
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.
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.
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.
FTS5's clearest Python advantage lived in multi-document retrieval, while token overlap ranked single-document evidence better.
RFC metadata helped structured questions but missed the predeclared aggregate threshold and reduced lexical-control ranking.
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