ragfresh
Detect stale, drifted, and ghost documents in RAG vector indexes.
Embeddings get created once. Source documents keep changing. Nothing tells you
your index has drifted from reality until a user gets an answer grounded in a
document that was edited — or deleted — weeks ago. ragfresh closes that gap
with a lightweight sidecar ledger and a CI-gateable check.
Staleness classes
- unchanged — source content hash matches what was indexed
- changed, not reindexed — the source doc was edited since it was indexed
- deleted, still indexed — the source doc is gone but the vector store still serves it (ghost citation risk — the worst class)
- not yet indexed — present in the source, absent from the ledger/store
Quickstart
pip install ragfresh
ragfresh init ./docs --db ragfresh.db
# ... time passes, docs change ...
ragfresh check ./docs --db ragfresh.db
Exits non-zero when changed, not reindexed or deleted, still indexed is
non-empty, so it's usable as a CI gate.
check only detects staleness. To resolve it -- re-embed changed/missing
docs and remove ghost entries from both the store and the ledger -- run:
ragfresh reindex ./docs --db ragfresh.db --chroma-collection my-index
reindex uses a local embedding model (no API calls) and requires a
writable store -- Chroma or pgvector; the default ledger-only store is
read-only. Preview what it would do without writing anything:
ragfresh reindex ./docs --db ragfresh.db --chroma-collection my-index --dry-run
Sources & stores
- Sources: filesystem, git (
ragfresh.sources.GitSource) - Stores:
- Chroma —
pip install ragfresh[chroma], then--chroma-collection NAME [--chroma-path DIR] - pgvector (any Postgres table with a doc_id column and a
vector-typed embedding column) —pip install ragfresh[pgvector], then--pg-dsn DSN [--pg-table documents] [--pg-id-column id] [--pg-embedding-column embedding] [--pg-document-column TEXT_COL](the embedding/document columns are only needed forreindex, notcheck) - Neither flag given → the ledger itself stands in as the index (read-only)
- Chroma —
Semantic drift scoring
Hash-based staleness treats any byte-level change as "changed" — a typo fix
and a rewritten paragraph look identical. --score-drift classifies each
changed_not_reindexed doc as cosmetic or meaningful using cosine
distance between embeddings of the old and new text:
ragfresh init ./docs --db ragfresh.db # snapshots content by default
ragfresh check ./docs --db ragfresh.db --score-drift
# [CHANGED] report.md (meaningful, score=0.812)
Requires ragfresh[chroma] (reuses its bundled local ONNX MiniLM model —
downloaded once, then fully offline, no API calls) and content snapshots
from init (pass --no-store-content at init time to opt out and disable
this later). Tune sensitivity with --drift-threshold (default 0.15).
By default drift scoring uses chromadb's bundled MiniLM model. If your
production embeddings diverge enough from that model that drift scores
wouldn't track them well, point --embedding-model (on check and
reindex) at any sentence-transformers model name instead (requires the
sentence-transformers package).
Chunked indexes
Most production RAG pipelines chunk a doc into several vectors (ids like
report.md#0, report.md#1, ...), not one vector per doc. By default
ragfresh assumes indexed ids equal doc_ids -- pass --chunk-separator on
check to fold chunk ids back to their source doc:
ragfresh check ./docs --db ragfresh.db --chroma-collection my-index --chunk-separator "#"
A doc counts as indexed if any of its chunks are present. ragfresh
doesn't chunk on your behalf -- it only needs to know how to reverse
whatever chunking scheme you already use, via a separator (CLI) or a
chunk_id_to_doc_id callable (library API, ragfresh.check.check).
Detection-only: reindex doesn't support chunked stores yet -- it
writes one vector per doc_id, so re-embedding into a chunked index would
require ragfresh to own the chunking strategy, which it deliberately
doesn't.
Development
pip install -e ".[dev]"
pytest
ruff check .
mypy
Status
v0.1.0. Filesystem + git sources; SQLite ledger; Chroma + pgvector stores
(both readable and writable, for check and reindex); CLI with drift
scoring (pluggable embedding model), chunk-aware detection, and reindex
(flat indexes only, with --dry-run); staleness-injection benchmark
(benchmarks/staleness_injection/); CI across 3 OSes × 3 Python versions
plus a lint/type-check job (ruff + mypy). 44 tests passing.
Known gap: reindex doesn't support chunked indexes (writes one vector
per doc_id; check handles chunking, reindex deliberately doesn't own a
chunking strategy).
License
MIT
Metadata
Release files for ragfresh 0.1.0
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 |
|---|---|---|---|---|
| ragfresh-0.1.0.tar.gz | 21.2 kB | Without provenance | Details for ragfresh-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ragfresh-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for ragfresh-0.1.0-py3-none-any.whl |
Total release size: 40.1 kB
Release files / ragfresh-0.1.0.tar.gz
| Download URL | ragfresh-0.1.0.tar.gz |
|---|---|
| Size | 21.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
373b38146915e8e683f3def09445fd3ccc7b52e95b2530022b52cb01375b85e9
|
|
BLAKE2b-256 checksum How to use checksums |
356614c4d591c3be461a7fbfb151c88481eeddcfcd9577f7d8529098f9a0bcd1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / ragfresh-0.1.0-py3-none-any.whl
| Download URL | ragfresh-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
26b1f0810d26f13734a434ce810f43e71f8389662ceb81dc1d99007d86446ca9
|
|
BLAKE2b-256 checksum How to use checksums |
958c301eba27545cb5e769837bf953f435ea6fd96aadb05c09b58019ed6eba58
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for ragfresh 0.1.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A 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.