MMD-based semantic consistency scoring for RAG pipeline outputs
Project description
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
Project details
Release history Release notifications | RSS feed
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 rag_consistency_scorer-0.1.0.tar.gz.
File metadata
- Download URL: rag_consistency_scorer-0.1.0.tar.gz
- Upload date:
- Size: 22.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0a43f9aa7cc3fb2a0cfa3b210eda4c33de967889abbd3899229c0221b7f2a23
|
|
| MD5 |
21b48ac44bb5be992939b1211773f053
|
|
| BLAKE2b-256 |
d571762a75b306edb15d56541bef3ab04e005efb46db71f979c8482d0eb77749
|
File details
Details for the file rag_consistency_scorer-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rag_consistency_scorer-0.1.0-py3-none-any.whl
- Upload date:
- Size: 23.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0af7b9cb98c1ed8f995cb6f9efe43187653f7f4e88966e69f59750bc601f419a
|
|
| MD5 |
8a30b29bfd55b46e3fc88663a2b41b92
|
|
| BLAKE2b-256 |
27a6d1ba24e6426a7dc9bf791272f3d4a3379e28cd29c23c62a8c7b25fefedf9
|