Find exactly which parts of your LLM output are hallucinated.
Most LLM tools tell you if an answer is "good". FactEval tells you exactly which sentence is wrong β and why.
FactEval doesn't give you a score β it shows you the exact mistake.
Input: "Paris is the capital of Germany."
Output: Paris is the capital of Germany β
β Contradicts evidence: "Paris is the capital of France."
FactEval verifies claims against provided reference context (e.g., retrieved documents in a RAG system). It helps developers debug LLM outputs by:
- Breaking answers into atomic claims
- Checking each claim against reference evidence
- Highlighting hallucinated parts with explanations and diagnostics
Built for debugging real-world RAG pipelines and LLM systems.
- RAG developers
- LLM app builders
- Anyone debugging hallucinations
π https://huggingface.co/spaces/sahilfarib/FactEval
pip install factevalFor development:
git clone https://github.com/sahilaf/FactEval.git
cd FactEval && pip install -e ".[dev]"β‘ Note: First run loads models (~15s). After that:
fast_check(): ~0.3sanalyze(): ~1.3s
fast_check() = fastest, recommended
analyze() = full pipeline (auto claim extraction)
from facteval import fast_check
result = fast_check(
claims=["Paris is the capital of Germany.", "Paris has 5 million people."],
contexts=["Paris is the capital of France. Population: 2.2M."],
)
for claim in result["claims"]:
print(f'{claim["label"]:15s} {claim["claim"]}')
print(f' β {claim["reason"]}')contradicted Paris is the capital of Germany.
β Contradicted by: "Paris is the capital of France."
contradicted Paris has 5 million people.
β Contradicted by: "Population: 2.2M."
Paste this into your RAG app to instantly catch hallucinations.
Think of FactEval as: LLM output β broken into claims β verified against truth
from facteval import fast_check
# After your LLM call
response = llm(query)
# Validate before returning to user
result = fast_check(
claims=response.split("."), # Simple claim splitting
contexts=docs
)
if result["summary"]["hallucination_rate"] > 0:
print("β οΈ Potential hallucination detected")If you don't want to split claims yourself, analyze() uses a lightweight LLM (Qwen 1.5B) to automatically decompose complex answers into atomic claims:
from facteval import analyze
result = analyze(
answer="Paris is the capital of Germany and has 5 million people.",
contexts=["Paris is the capital of France. Paris has approximately 2.2 million inhabitants."],
)# Quick check
facteval check --answer "The earth is flat." --context "The earth is an oblate spheroid."
# From file
facteval check input.json --output results.json
# With calibrator
facteval check input.json --calibrator calibrator.pkl- Claim-level verdicts β each sentence gets β supported, β contradicted, or β unverifiable
- Highlighted output β color-coded HTML showing exactly which parts are wrong
- Human-readable reasons β every verdict explains why
- Pipeline diagnostics β hallucination vs. retrieval gap vs. missing context
- Calibrated confidence β isotonic regression for trustworthy probability scores
- Lightweight mode β
fast_check()runs instantly (~0.3s) without heavy models - Drop-in API β works seamlessly with LangChain, LlamaIndex, or custom pipelines
- Batch NLI β all claims in a single forward pass
- CLI β
facteval checkfor scripting and CI/CD
| Feature | FactEval | FacTool | QAFactEval |
|---|---|---|---|
| Claim-level granularity | β | β | β |
| Calibrated confidence scores | β | β | β |
| Pipeline Diagnostics (why it failed) | β | β | β |
Sub-second mode (fast_check) |
β | β | β |
| HTML highlighting output | β | β | β |
Short version:
{
"claims": [
{
"claim": "Paris is the capital of Germany.",
"label": "contradicted",
"confidence": 0.9971,
"reason": "Contradicts evidence: \"Paris is the capital of France.\"",
"diagnostics": {
"failure_type": "hallucination",
"retrieval_quality": "strong",
"suggestion": "Claim directly contradicts the evidence."
}
}
],
"summary": {
"total_claims": 2, "supported": 0, "contradicted": 2,
"unverifiable": 0, "hallucination_rate": 1.0
},
"highlighted_answer": "<mark>Paris is the capital of Germany β</mark>..."
}Full output includes
Each claim also contains:
evidenceβ the matched reference sentenceevidence_scoreβ retrieval similarity (0β1)raw_nli_scoresβ per-label NLI probabilities (entailment,neutral,contradiction)calibrated_confidenceβ post-calibration confidence (if calibrator provided)calibration_errorβ estimated calibration error
Top-level fields:
calibratedβ whether a fitted calibrator was usedpipeline_time_secondsβ total processing time
| Label | Meaning |
|---|---|
β
supported |
Claim is entailed by the evidence |
β contradicted |
Claim contradicts the evidence |
β unverifiable |
No relevant evidence, or evidence is neutral |
failure_type |
What happened | What to do |
|---|---|---|
verified |
Supported by strong evidence | Nothing β it's correct |
hallucination |
Contradicts strong evidence | Factual error in LLM output |
possible_hallucination |
Contradicts weak evidence | Add better context to confirm |
no_evidence |
No context for this topic | Add reference passages |
retrieval_gap |
Evidence too dissimilar | Context may not cover this claim |
inconclusive |
Evidence is neutral | Cannot confirm or deny |
Use FactEval if you are:
- Building a RAG system and need to verify answers against retrieved documents
- Debugging hallucinations in LLM outputs
- Evaluating whether generated answers are grounded in context
- Building CI/CD checks for LLM-powered features
Not intended for:
- General fact-checking without reference context (FactEval needs ground truth documents)
- Real-time inference on user-facing APIs (model loading adds latency)
| Mode | First run | Subsequent runs | VRAM |
|---|---|---|---|
check() (full) |
~60s (loads 3 models) | ~1.3s | ~3.4 GB |
verify() (lightweight) |
~15s (loads 2 models) | ~0.3s | ~0.5 GB |
First run downloads and loads models from Hugging Face. After that, models are cached in memory. Use
verify()when you already have claims and need low-latency evaluation.
- RAG pipelines β verify that generated answers are grounded in retrieved documents
- LLM evaluation workflows β measure hallucination rates across test sets
- AI product debugging β find exactly where your model is making things up
Answer Text βββ Claim Extractor βββ Evidence Retriever βββ NLI Verifier βββ Calibrator βββ Output
(Qwen 1.5B) (MiniLM + FAISS) (DeBERTa) (Isotonic)
β β
βββ Semantic Highlighting ββββββββββββββ
(reuses MiniLM embeddings)
| Stage | Model | Size | Latency |
|---|---|---|---|
| Claim Extraction | Qwen/Qwen2.5-1.5B-Instruct |
~3 GB | ~1s |
| Evidence Retrieval | all-MiniLM-L6-v2 + FAISS |
~90 MB | <10ms |
| NLI Verification | DeBERTa-v3-base-mnli-fever-anli |
~370 MB | <10ms/batch |
| Calibration | Isotonic Regression (sklearn) | ~1 KB | <1ms |
pip install -e ".[demo]"
python demo/app.pyFeatures:
- Highlighted answer text with β ββ annotations
- Per-claim cards with reasons, diagnostic badges, and suggestions
- Summary dashboard with hallucination rate
- 4 built-in examples
git remote add hf https://huggingface.co/spaces/YOUR_USERNAME/facteval
git push hf mainFactEval/
βββ README.md
βββ ANALYSIS.md # Development analysis & lessons learned
βββ LICENSE # MIT
βββ pyproject.toml # Package config + CLI entry point
βββ app.py # HF Spaces entry point
βββ requirements.txt # HF Spaces dependencies
βββ examples/
β βββ basic.py # Minimal usage example
β βββ rag_debug.py # RAG pipeline debugging example
βββ demo/
β βββ app.py # Gradio interactive demo
βββ facteval/
βββ __init__.py # Public API: check(), verify()
βββ config.py # Model names, prompts, defaults
βββ models.py # Claim, Evidence, ClaimWithEvidence
βββ claim_extractor.py # Qwen2.5-1.5B claim decomposition
βββ retriever.py # FAISS + MiniLM evidence retrieval
βββ verifier.py # DeBERTa batch NLI + reasons
βββ calibrator.py # Isotonic regression calibration
βββ core.py # Pipeline orchestration + diagnostics
βββ cli.py # facteval check CLI
MIT β see LICENSE.