rag-consistency-scorer
MMD-based semantic consistency scoring for RAG pipeline outputs.
Measures whether a RAG pipeline produces semantically stable answers when asked the same question multiple times. Uses Maximum Mean Discrepancy (MMD) over atomic-fact embeddings — a statistically principled distributional test — rather than naive string similarity.
Installation
pip install rag-consistency-scorer
Optional extras:
pip install "rag-consistency-scorer[pca]" # enables PCA dimensionality reduction
pip install "rag-consistency-scorer[dev]" # testing dependencies
Quick start
from rag_consistency_scorer import ConsistencyScorer
data = [
{
"query": "What is the boiling point of water?",
"responses": [
"Water boils at 100 degrees Celsius at sea level.",
"The boiling point of water is 100°C at standard pressure.",
"At 1 atm pressure, water reaches its boiling point at 100°C.",
"Water transitions to steam at 100 degrees Celsius under normal conditions.",
"The boiling point of water is 100°C, equivalent to 212°F.",
"Under standard atmospheric conditions, water boils at 100°C.",
"Water boils at 373 Kelvin, i.e. 100°C at sea level.",
"Standard boiling point of water is 100°C at atmospheric pressure.",
],
}
]
scorer = ConsistencyScorer()
results = scorer.score(data)
for r in results:
print(r.query_id, r.stability_score)
# → q_abc12345 0.921
No API key needed with default settings. The package uses a lightweight TF-IDF embedding fallback when no embedding_config is provided.
Input format
scorer.score() accepts a list of dicts, each with:
| Key | Type | Required | Description |
|---|---|---|---|
query |
str |
✓ | The prompt / question sent to the RAG pipeline |
responses |
List[str] |
✓ | One answer string per run. Minimum 2, recommended 8–15. |
query_id |
str |
– | Optional identifier. Auto-generated if omitted. |
data = [
{
"query": "Who invented the telephone?",
"query_id": "telephone_q1",
"responses": [
"Alexander Graham Bell invented the telephone in 1876.",
# ... at least 7 more responses
]
}
]
Output format
Each call to .score() returns a List[QueryScore] — one per input query.
result = results[0]
result.stability_score # float in [0, 1]. 1 = perfectly stable.
result.mmd2_mean # mean pairwise MMD² across all run pairs
result.mmd2_min # min pairwise MMD²
result.mmd2_max # max pairwise MMD²
result.sigma_used # kernel bandwidth selected for this query
result.tau_used # τ used in exp(-MMD²/τ) mapping
result.n_runs # number of responses scored
result.query_id # identifier
result.query # original query string
# Per-pair statistical diagnostics
for pair in result.pairs:
pair.run_i, pair.run_j # which two runs
pair.mmd2 # raw MMD² (may be slightly negative)
pair.p_value # permutation p-value (Phipson-Smyth corrected)
pair.p_value_bh # BH-FDR adjusted p-value
pair.significant # True if p_value_bh ≤ alpha
# Per-run outlier diagnostics (sorted most → least anomalous)
for run in result.runs:
run.run_index # index into original responses list
run.outlier_score # mean MMD² to all other runs (higher = more anomalous)
run.z_score # bootstrap z-score (> 2 is suspicious)
run.n_facts # number of atomic facts extracted
run.extraction_source # "llm" or "heuristic"
# Serialise
import json
json.dumps(result.to_dict())
Interpreting stability_score
| Score | Interpretation |
|---|---|
| ≥ 0.80 | Stable — answers are semantically consistent |
| 0.60–0.79 | Watch — mild drift; monitor over time |
| 0.40–0.59 | Investigate — notable inconsistency |
| < 0.40 | Unstable — likely retrieval or generation failure |
Thresholds depend on your tau setting. Run the calibration utilities to set domain-specific thresholds.
Configuration
from rag_consistency_scorer import ConsistencyScorer, ScorerConfig
scorer = ConsistencyScorer(
config=ScorerConfig(
# --- Extraction ---
extraction_mode="heuristic", # or "llm"
llm_config=None, # required if extraction_mode="llm"
# --- Embedding ---
embedding_config=None, # uses TF-IDF fallback if not set
# --- Statistical ---
tau=None, # None = auto-calibrate from data (recommended)
n_permutations=500, # higher → more accurate p-values, slower
alpha=0.05, # BH-FDR significance level
bandwidth_candidates=10,
seed=42,
)
)
LLM-backed extraction (OpenAI)
scorer = ConsistencyScorer(
config=ScorerConfig(
extraction_mode="llm",
llm_config={
"api_key": "sk-...",
"model": "gpt-4o-mini",
"max_facts": 40,
"heuristic_fallback": True, # fall back silently on LLM error
},
embedding_config={
"api_key": "sk-...",
"model": "text-embedding-3-small",
},
)
)
Azure OpenAI
scorer = ConsistencyScorer(
config=ScorerConfig(
extraction_mode="llm",
llm_config={
"api_key": "your-azure-key",
"model": "gpt-4o-mini", # deployment name
"api_base": "https://your-resource.openai.azure.com/",
"api_version": "2024-02-01",
},
embedding_config={
"api_key": "your-azure-key",
"model": "text-embedding-ada-002", # deployment name
"api_base": "https://your-resource.openai.azure.com/",
"api_version": "2024-02-01",
},
)
)
How it works
For each query the scorer:
- Extracts atomic facts from each response (heuristic regex or LLM-based).
- Embeds each fact into a vector space and L2-normalises.
- Selects σ (kernel bandwidth) via power-guided selection on a log-uniform grid, replacing the naive median heuristic.
- Computes pairwise MMD² (unbiased U-statistic) between every pair of runs.
- Runs permutation tests with Phipson-Smyth corrected p-values.
- Applies BH-FDR correction across all pairs.
- Detects outlier runs via bootstrap z-scores.
- Maps to [0,1]:
stability = exp(-max(0, MMD²_mean) / τ), with τ auto-calibrated from the data.
Running tests
pip install pytest
pytest tests/ -v
What this does NOT measure
- Correctness — a pipeline can be stably wrong.
- Faithfulness to retrieved context.
- Logical contradiction detection.
Use alongside correctness/faithfulness metrics (RAGAS, Giskard, etc.) for a complete RAG evaluation suite.
License
MIT
Metadata
Release files for rag-consistency-scorer 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 |
|---|---|---|---|---|
| rag_consistency_scorer-0.1.0.tar.gz | 22.6 kB | Without provenance | Details for rag_consistency_scorer-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| rag_consistency_scorer-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for rag_consistency_scorer-0.1.0-py3-none-any.whl |
Total release size: 46.3 kB
Release files / rag_consistency_scorer-0.1.0.tar.gz
| Download URL | rag_consistency_scorer-0.1.0.tar.gz |
|---|---|
| Size | 22.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b0a43f9aa7cc3fb2a0cfa3b210eda4c33de967889abbd3899229c0221b7f2a23
|
|
BLAKE2b-256 checksum How to use checksums |
d571762a75b306edb15d56541bef3ab04e005efb46db71f979c8482d0eb77749
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.8.11
|
Release files / rag_consistency_scorer-0.1.0-py3-none-any.whl
| Download URL | rag_consistency_scorer-0.1.0-py3-none-any.whl |
|---|---|
| Size | 23.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0af7b9cb98c1ed8f995cb6f9efe43187653f7f4e88966e69f59750bc601f419a
|
|
BLAKE2b-256 checksum How to use checksums |
27a6d1ba24e6426a7dc9bf791272f3d4a3379e28cd29c23c62a8c7b25fefedf9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.8.11
|
Security for rag-consistency-scorer 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.