Skip to main content

ragfresh

CI

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 for reindex, not check)
    • Neither flag given → the ledger itself stands in as the index (read-only)

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)

Source distribution for ragfresh 0.1.0
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)

Table of built distributions (wheels) for ragfresh 0.1.0
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page