Find exactly which parts of your LLM output are hallucinated.
Project description
๐ FactEval
Find exactly which parts of your LLM output are hallucinated.
Debug hallucinations in LLM outputs like you debug code.
Input: "Paris is the capital of Germany."
Output: Paris is the capital of Germany โ
โ Contradicts evidence: "Paris is the capital of France."
What is FactEval?
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
โก Try It Instantly
Live Demo on Hugging Face Spaces โ
No setup needed โ paste your text and see hallucinations highlighted in seconds.
๐ฆ Install
pip install facteval
For development:
git clone https://github.com/sahilaf/FactEval.git
cd FactEval && pip install -e ".[dev]"
๐ Quick Start
โก Note: First run downloads and loads models (~15s). After that: ~0.3s per query.
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."
๐งช Drop-in RAG Evaluator
Paste this into your RAG app to instantly catch hallucinations:
from facteval import fast_check
# 1. Your existing pipeline
response = llm(query)
docs = retriever(query)
# 2. Drop-in evaluation
result = fast_check(
claims=response.split("."), # Simple claim splitting
contexts=docs
)
if result["summary"]["contradicted"] > 0:
print("๐จ Hallucination detected! Halting response.")
Full Pipeline (Automated Extraction)
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."],
)
CLI (Command Line Interface)
# 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
โจ Features
- 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
๐ Comparison
| Feature | FactEval | FacTool | QAFactEval |
|---|---|---|---|
| Claim-level granularity | โ | โ | โ |
| Calibrated confidence scores | โ | โ | โ |
| Pipeline Diagnostics (why it failed) | โ | โ | โ |
Sub-second mode (fast_check) |
โ | โ | โ |
| HTML highlighting output | โ | โ | โ |
๐ Output Format
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
Labels
| Label | Meaning |
|---|---|
โ
supported |
Claim is entailed by the evidence |
โ contradicted |
Claim contradicts the evidence |
โ unverifiable |
No relevant evidence, or evidence is neutral |
Diagnostics
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 |
๐ค When Should You Use FactEval?
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)
โก Performance
| 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.
๐งช Built For
- 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
๐๏ธ Architecture
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 |
๐ฅ๏ธ Try It Locally
pip install -e ".[demo]"
python demo/app.py
Features:
- Highlighted answer text with โ โโ annotations
- Per-claim cards with reasons, diagnostic badges, and suggestions
- Summary dashboard with hallucination rate
- 4 built-in examples
Deploy to Hugging Face Spaces
git remote add hf https://huggingface.co/spaces/YOUR_USERNAME/facteval
git push hf main
๐ Project Structure
FactEval/
โโโ 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
๐ License
MIT โ see LICENSE.
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 facteval-0.1.1.tar.gz.
File metadata
- Download URL: facteval-0.1.1.tar.gz
- Upload date:
- Size: 23.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6313f5827ce1d7b575aeb2dd15c60e3233ac3dcdee63266446785e111a005233
|
|
| MD5 |
1fa45167fe033f9f27e9168d14496a0a
|
|
| BLAKE2b-256 |
aafc9eb0fbfc2107dd263f19ea3b9a86bad10bc9f81bb642085d1da3af57279e
|
File details
Details for the file facteval-0.1.1-py3-none-any.whl.
File metadata
- Download URL: facteval-0.1.1-py3-none-any.whl
- Upload date:
- Size: 22.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
676b34b8fc7a2799df32a7489d111f90a8f57a35c18b03ddeae54b919e73a650
|
|
| MD5 |
0055f4a43187f9547158180da7ca8672
|
|
| BLAKE2b-256 |
37c7888cb0e1d7378ddb97c3f0fa7d58e58643deb3b9c7c30125b2c7e2a81206
|