Exposure audit for vector search / RAG: coverage, dark matter, Gini, hub capture, CI gate
Project description
retrieval-fairness
Exposure audit for vector search / RAG. Shows what share of your vector corpus is actually reachable by queries and what share is never retrieved (dark matter — the antihub inventory of your index); measures exposure concentration (Gini), hub capture, and regression diff when you change the embedder or chunking. Think of it as a health/coverage report for your index, not your pipeline.
Status: early development. This is packaging novelty (the Gini / retrievability metrics are honestly borrowed from IR-fairness / T-Retrievability research), not a from-scratch invention.
Why your index has dark matter (it's not a bug in your code)
Hubs and never-retrieved chunks are a structural property of high-dimensional nearest-neighbor search, not a symptom of a bad embedder. Radovanović, Nanopoulos & Ivanović (JMLR 2010, “Hubs in Space”) showed that as dimensionality grows, the distribution of k-occurrences becomes strongly right-skewed: a few points (hubs) appear in a disproportionate share of neighbor lists, while others (antihubs) appear in almost none. Modern ANN indexes (HNSW) amplify the effect — hub nodes are the highway entry points that make the graph navigable. Left unmeasured, this silently collapses the effective size of your corpus. This tool makes it measurable — and gateable in CI.
Installation
pip install retrieval-fairness
Optional store adapters are extras (installed only if you use them):
pip install 'retrieval-fairness[faiss]' # FAISS
pip install 'retrieval-fairness[pgvector]' # pgvector (PostgreSQL)
pip install 'retrieval-fairness[qdrant]' # Qdrant
pip install 'retrieval-fairness[models]' # sentence-transformers embedder
pip install 'retrieval-fairness[fastembed]' # fastembed (BGE) embedder
From source (development):
pip install -e '.[dev]'
Quick start
retrieval-fairness demo --top-k 5 # demo on a synthetic corpus
retrieval-fairness demo-diff --top-k 5 # regression diff for an embedder migration
Usage
Run against real queries
# corpus.jsonl: {"id": "...", "text": "...", "vector": [...]}
# queries.jsonl: {"id": "...", "vector": [...]}
retrieval-fairness probe --corpus corpus.jsonl --queries queries.jsonl \
--top-k 10 --json report.json --html dashboard.html
No query logs: antihub self-query audit (synthetic queries)
For each chunk, generate the query that should retrieve it (its own top TF-IDF terms) and check whether it actually surfaces. A chunk that cannot be found even by a query aimed at it is invisible from any reasonable query direction — dark matter from day one:
retrieval-fairness synth --corpus corpus.jsonl --top-k 10 --html dashboard.html
Regression diff (embedder/chunking change)
retrieval-fairness diff --baseline before.json --candidate after.json
# For an intentional rechunking migration (different corpus fingerprint):
retrieval-fairness diff --baseline before.json --candidate after.json \
--corpus-policy allow-change
Baselines use schema v2 and contain ordered query IDs plus deterministic corpus and workload SHA-256 fingerprints. On load, reports are rebuilt from raw hits and frequencies; saved metric values are never trusted as source data. Legacy 0.1.0 baselines remain readable, using positional query alignment only when explicitly allowed by strict overlap gates.
CI gate
retrieval-fairness gate --baseline v1.json --candidate new.json --strict \
--max-coverage-drop 0.05 --max-dark-matter-rise 0.05
# exit 1 in strict mode if coverage dropped > 5 pp -> CI blocks the deploy
Cross-check dark matter against qrels ("lost gold")
If you have relevance judgments (qrels), cross-check which dark-matter chunks are actually relevant — the corpus contains material the retriever never surfaces. No competitor exposure tool ships this:
retrieval-fairness qrels --probe report.json --qrels qrels.json \
--min-relevance-grade 1 --json qrels_report.json
# --queries is only required for a legacy schema-v1 probe
A qrels pair is relevant only when grade >= --min-relevance-grade (default
1), so zero and negative judgments are ignored. The output reports micro
recall@k over all relevant query/document pairs and macro recall@k over
queries that have at least one relevant in-corpus document. recall_at_k is
retained as a compatibility alias for micro recall for the 0.1.x cycle.
Metrics
| Metric | What it shows |
|---|---|
| Coverage % | share of the corpus retrieved at least once |
| Of reachable ceiling % | coverage as a share of what the workload can physically reach (n_queries × top_k); distinguishes a bad retriever from a small workload |
| Dark matter % | share NEVER retrieved |
| Gini | exposure concentration (0 = uniform, 1 = all in one) |
| Hub capture top5/10 | share of exposure captured by top-N hubs |
| Lorenz curve | inequality visualization |
| Per-query overlap | result stability across a migration |
| Lost gold / micro & macro Recall@k | positive-relevance dark-matter chunks and qrels retrieval quality |
How it works
retrieval_fairness/types.py— theVectorStorecontract (Protocol). Any store is bridged to it via an adapter (InMemory, FAISS, pgvector, Qdrant today; Pinecone/Weaviate on the roadmap).adapters/inmemory.py—InMemoryVectorStore(cosine, numpy) for dev/tests/demos;adapters/{faiss,pgvector,qdrant}.py— store adapters.metrics.py— coverage, gini, lorenz, hub_capture, FairnessReport.probe.py— run a workload → retrieval frequency → report.diff.py— regression diff between two runs.gate.py— CI gate with configurable rules.synth.py— antihub self-query audit (synthetic queries from the corpus).qrels.py— dark-matter vs qrels cross-check ("lost gold").dashboard.py— self-contained HTML report (Lorenz, histogram, PCA map).embedders.py— Embedder contract (TF-IDF / sentence-transformers / fastembed).
Real-scale case study (BEIR NQ, ~50% dark matter, lexical→dense
regression diff): docs/case_study_nq.md. Store adapters: docs/adapters.md.
Comparison with related work: docs/comparison.md.
Tests
pytest tests/ -q
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file retrieval_fairness-0.1.1.tar.gz.
File metadata
- Download URL: retrieval_fairness-0.1.1.tar.gz
- Upload date:
- Size: 61.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86b6a3106faf2bc04a131adda03f0dcf5e188e19ea8836a092b623808ba3907b
|
|
| MD5 |
4cb4ffdeaa7cef95b72054223945023d
|
|
| BLAKE2b-256 |
af49af0243f826add09289c871fd2bc5f9b5156582449226211d1bc4b23b5d8a
|
Provenance
The following attestation bundles were made for retrieval_fairness-0.1.1.tar.gz:
Publisher:
publish.yml on derishabl/retrieval-fairness
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
retrieval_fairness-0.1.1.tar.gz -
Subject digest:
86b6a3106faf2bc04a131adda03f0dcf5e188e19ea8836a092b623808ba3907b - Sigstore transparency entry: 2162876820
- Sigstore integration time:
-
Permalink:
derishabl/retrieval-fairness@55264eac6f93b2a85e75b2e51dd22bd40babeacb -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/derishabl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@55264eac6f93b2a85e75b2e51dd22bd40babeacb -
Trigger Event:
release
-
Statement type:
File details
Details for the file retrieval_fairness-0.1.1-py3-none-any.whl.
File metadata
- Download URL: retrieval_fairness-0.1.1-py3-none-any.whl
- Upload date:
- Size: 54.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5580e5cdb1561b6d7b2bc063f7ca3b38d7a44eaa3f3de92428c905377201b792
|
|
| MD5 |
41a2c6bf8ecaa8f4dbf8af64818cd1b2
|
|
| BLAKE2b-256 |
c028309324b110e1da8934af1da3f9f98c8318d8c76bbf899fa1282d298d53f2
|
Provenance
The following attestation bundles were made for retrieval_fairness-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on derishabl/retrieval-fairness
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
retrieval_fairness-0.1.1-py3-none-any.whl -
Subject digest:
5580e5cdb1561b6d7b2bc063f7ca3b38d7a44eaa3f3de92428c905377201b792 - Sigstore transparency entry: 2162876833
- Sigstore integration time:
-
Permalink:
derishabl/retrieval-fairness@55264eac6f93b2a85e75b2e51dd22bd40babeacb -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/derishabl
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@55264eac6f93b2a85e75b2e51dd22bd40babeacb -
Trigger Event:
release
-
Statement type: