rag-ci
Stop shipping RAG regressions.
rag-ci measures every change to your RAG pipeline against a baseline and fails the pull request when retrieval actually got worse — not when it merely looks worse.
See it catch one, in one command and under a second — no API key, nothing to download:
$ uvx rag-ci demo
Built a synthetic corpus: 144 documents, 144 questions anchored to character offsets.
1. Baseline — chunk_size=240
┏━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ metric ┃ mean ┃ 95% CI ┃
┡━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ recall@3 * │ 0.951 │ [0.917, 0.986] │
└────────────┴───────┴────────────────┘
2. Someone halves the chunk size to fit more of them in context — chunk_size=120
┏━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ metric ┃ mean ┃ 95% CI ┃
┡━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ recall@3 * │ 0.736 │ [0.667, 0.806] │
└────────────┴───────┴────────────────┘
3. The gate compares them over the same questions
REGRESSION — the gate fails — recall@3 dropped by 0.215
(95% CI [-0.292, -0.139], p=0.0000) over 144 paired cases.
Exit code 1. In CI, that is a blocked pull request.
Nothing there is a recording. The corpus is generated on your machine and measured on
the spot, through the same code paths run and gate use — a canned transcript would
contradict the one thing this project argues for. Reproduce it yourself; the numbers will
match, because they are deterministic, not because they were typed in.
Why the interval is the point
The demo above blocks because 144 questions are enough to be sure. The opposite case
matters just as much. In examples/reference/ — six questions —
cutting top_k to 1 drops recall@3 from 0.833 to 0.667, and rag-ci does not block:
Gate: Pass — recall@3 moved by -0.167 (95% CI [-0.500, 0.000]) over 6 paired cases.
A drop that large is probably real, and rag-ci still refuses to call it — because across
six paired cases the paired bootstrap cannot separate it from noise. The interval, spanning
-0.500 to 0.000, is the tool telling you how little evidence six questions buy. Widen
the golden set and that interval tightens until the same change trips the gate. A tool
that fails builds on noise gets switched off within a week, so this one errs toward
silence and shows its evidence either way.
The same logic runs in reverse, which is where the money is: Beyond the Reranker finds that many retrieval enhancements stop contributing anything once a strong reranker is present. Teams are stacking techniques that do nothing and paying for them in latency and tokens, because a bare score moving from 0.71 to 0.74 reads like progress.
The academic side settled this years ago — paired bootstrap tests with 10,000 resamples and 95% confidence intervals are standard in T2-RAGBench and HetDocQA. That rigor had not reached the tools practitioners actually run. rag-ci is that rigor behind a CLI and a GitHub Action.
To be precise about the gap: promptfoo evaluates RAG and runs in CI, and ragas and DeepEval both compute retrieval metrics. What none of them do is condition the build verdict on a significance test. They compare a score to a threshold; rag-ci compares two runs over the same questions and reports how confident the difference is.
Commands
Every command below works today; see the design document for how they fit together.
uvx rag-ci demo # watch a regression get caught, in one second ✅
uvx rag-ci init # scaffold an adapter for your pipeline ✅
uvx rag-ci golden anchor # anchor a Q/A set you already have ✅
uvx rag-ci golden gen # generate candidate questions ✅
uvx rag-ci golden review # accept / edit / reject, then commit ✅
uvx rag-ci run # measure, with confidence intervals ✅
uvx rag-ci gate # fail the PR only on a real regression ✅
uvx rag-ci sweep --report # a shareable HTML page of the result ✅
uvx rag-ci sweep --cache # find the configuration that provably wins ✅
uvx rag-ci run --judge # score answer grounding, not just recall ✅
uvx rag-ci judge calibrate # check the judge before trusting it ✅
Generating questions needs the optional extra and an ANTHROPIC_API_KEY
(uvx "rag-ci[generate]" golden gen …). Everything else installs and runs with no model
dependency at all. See docs/golden-sets.md and
docs/sweeps.md, and docs/judging.md.
Use it in CI
name: rag-ci
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
retrieval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: Nokimalos/rag-ci@v0.7.1
with:
adapter: ragci_adapter.py
golden: tests/golden.jsonl
The gate exits 1 on a regression and 2 when it cannot make a trustworthy comparison —
an invalid run, or a baseline recorded against a different golden set. See
docs/github-action.md for recording your first baseline and tuning
min-effect.
Not on GitHub? The gate is a CLI, so any runner works. GitLab gets a one-job setup in
docs/gitlab-ci.md, where allow_failure: exit_codes maps the 1 / 2
split onto the pipeline itself — a distinction GitHub Actions cannot express.
Two worked examples. examples/reference/ is dependency-free and
gates every pull request to this repository.
examples/langchain-chroma/ is the real thing — LangChain
chunking, Chroma storage, ONNX embeddings — and shows the one flag
(add_start_index=True) that decides whether rag-ci matches passages exactly or falls
back to token overlap.
What makes it different
- Ground truth anchored to document passages, never to chunks. Change the chunk size and your golden set still works. This is what makes sweeping possible at all.
- A winner has to beat the field.
sweepdoes not just rank means: it carries the finalists to the last rung, tests the winner against each with a paired bootstrap, and says plainly when the field is indistinguishable instead of naming a coin toss. - A two-condition gate. A regression blocks the build only when it is both statistically
significant and larger than
min-effect. Significance alone would block on trivia; effect size alone would block on noise. - Built for large corpora. Question generation streams the corpus and holds only
identifiers while it decides what to sample, never the text. Sweeps use successive halving
instead of grid search, separate index-time from query-time parameters so indexes are
rebuilt as rarely as possible, and
--extrapolate-toprojects a sub-corpus result to full scale — refusing to project when the measured points do not support it. - Agnostic by design. No RAG framework as a dependency. You write one adapter file; your stack stays yours. The adapter contract captures multi-step retrieval trajectories, so agentic and multi-hop pipelines are first-class.
License
MIT
Metadata
Release files for rag-ci 0.7.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| rag_ci-0.7.1.tar.gz | 113.0 kB | With provenance | Details for rag_ci-0.7.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| rag_ci-0.7.1-py3-none-any.whl | Python 3 | none | any | Details for rag_ci-0.7.1-py3-none-any.whl |
Total release size: 170.3 kB
Release files / rag_ci-0.7.1.tar.gz
| Download URL | rag_ci-0.7.1.tar.gz |
|---|---|
| Size | 113.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
19cd5a2af816ad20ea83c36a1cd559694918f15b7b9f216adb963f166b921ba4
|
|
BLAKE2b-256 checksum How to use checksums |
dc78a269d7da0a5d950d4c6c378abdadeed5fee0ef8454396c46a63525f3afa1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.
Transparency logRelease files / rag_ci-0.7.1-py3-none-any.whl
| Download URL | rag_ci-0.7.1-py3-none-any.whl |
|---|---|
| Size | 57.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
640c9daf39bc7e6c5fb80e806b7927fa8176cf151fc288651d78cc01a94cccee
|
|
BLAKE2b-256 checksum How to use checksums |
d15efed18318fe953c6d55c0b69308b2dc4d4765a842c6efd92eabbd2b75d974
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.
Transparency logSecurity for rag-ci 0.7.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for rag_ci-0.7.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 11, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.