Hallucination Types in Language Models: A Complete Guide › Code: Detecting Intrinsic Hallucinations with NLI

Michael BrenndoerferMarch 18, 20268 min read
Language AI HandbookAll chapters
Part 56 of 67Hallucination and Factuality
Chapter 429Hallucination Types in Language Models: A Complete Guide › Code: Detecting Intrinsic Hallucinations with NLI
Reading section 10 of 15 · Code: Detecting Intrinsic Hallucinations with NLI · 8 minSection 10 of 15 · 8 min

Book contents

Section 10 of 15 · 8 min read

Hallucination Types in Language Models: A Complete Guide › Code: Detecting Intrinsic Hallucinations with NLIOne section · Sections in this chapter10 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's build an NLI-based intrinsic hallucination detector. We will use a cross-encoder NLI model to score whether each sentence in a generated summary is entailed by or contradicted by the source document.

The approach works as follows: for each sentence in the output, we retrieve the source sentence with the highest lexical overlap and run the NLI classifier on that pair. A sentence with a high contradiction score is flagged as a likely intrinsic hallucination.

Candidate retrieval is important because NLI classifiers assess a pair of sentences, not a whole document. Unrelated source sentences can receive unstable contradiction scores, so taking the maximum contradiction over every possible pair can create false positives. Retrieving the closest source sentence first gives the classifier a relevant premise and mirrors the retrieve-then-verify design used in practical faithfulness systems.

First, install the required package:

In[6]:
Code
# !uv pip install transformers torch -q
In[7]:
Code
import warnings

warnings.filterwarnings("ignore")

Now define example source documents and generated summaries covering each hallucination type:

In[8]:
Code
# Source document
source = """
Anthropic was founded in 2021 by Dario Amodei and Daniela Amodei,
along with several other former OpenAI researchers.
The company raised $1.5 billion in its Series C funding round in 2023.
Anthropic focuses on AI safety research and the development of reliable AI systems.
Its primary product line, Claude, is designed to be helpful, harmless, and honest.
"""

# Summaries representing different hallucination types
summaries = {
    "faithful": (
        "Anthropic was founded in 2021 by Dario and Daniela Amodei. "
        "The company focuses on AI safety research. "
        "Claude is designed to be helpful, harmless, and honest."
    ),
    "intrinsic": (
        "Anthropic was founded in 2020 by Dario Amodei and Daniela Amodei. "
        "The company raised $1.5 billion in its Series C round in 2023. "
        "Its primary product is Claude."
    ),  # 2021 changed to 2020: direct contradiction of source
    "extrinsic": (
        "Anthropic was founded in 2021 by Dario and Daniela Amodei. "
        "The company has offices in San Francisco and London. "  # London not in source
        "Anthropic focuses on AI safety research."
    ),
    "fabrication": (
        "Anthropic was founded in 2021 by Dario Amodei, who previously "
        "won the Turing Award for his work on neural scaling laws. "  # Award invented
        "The company focuses on AI safety research."
    ),
}
In[9]:
Code
def split_sentences(text):
    """Split text into sentences using punctuation boundaries."""
    text = text.strip()
    sentences = re.split(r"(?<=[.!?])\s+", text)
    return [s.strip() for s in sentences if len(s.strip()) > 10]


def lexical_overlap(left, right):
    """Jaccard overlap between lowercase alphanumeric token sets."""
    left_tokens = set(re.findall(r"[a-z0-9]+", left.lower()))
    right_tokens = set(re.findall(r"[a-z0-9]+", right.lower()))
    return len(left_tokens & right_tokens) / max(
        len(left_tokens | right_tokens), 1
    )


def score_hallucination(nli_model, source_text, summary_text):
    """
    Score each summary sentence for hallucination using cross-encoder NLI.

    For each summary sentence, retrieve the source sentence with the highest
    lexical overlap, then record the NLI entailment and contradiction scores.
    """
    source_sentences = split_sentences(source_text)
    summary_sentences = split_sentences(summary_text)

    results = []
    for hyp in summary_sentences:
        premise = max(
            source_sentences,
            key=lambda source_sentence: lexical_overlap(source_sentence, hyp),
        )
        output = nli_model(
            {"text": premise, "text_pair": hyp},
            truncation=True,
            max_length=512,
            top_k=None,
        )
        scores = {item["label"]: item["score"] for item in output}

        results.append(
            {
                "sentence": hyp,
                "entailment": scores["entailment"],
                "contradiction": scores["contradiction"],
            }
        )

    avg_entailment = float(np.mean([r["entailment"] for r in results]))
    avg_contradiction = float(np.mean([r["contradiction"] for r in results]))
    return results, avg_entailment, avg_contradiction

Load the NLI model. On first run this downloads the weights; subsequent runs use the local cache:

In[10]:
Code
nli_model = pipeline(
    "text-classification",
    model="cross-encoder/nli-deberta-v3-small",
    device=-1,  # CPU; set to 0 for GPU
)

Run the hallucination scorer on each summary type:

In[11]:
Code
scored_summaries = {}
for summary_type, summary_text in summaries.items():
    results, avg_entail, avg_contra = score_hallucination(
        nli_model, source, summary_text
    )
    scored_summaries[summary_type] = {
        "results": results,
        "avg_entailment": avg_entail,
        "avg_contradiction": avg_contra,
    }
Out[12]:
Console

==========================================================
Summary type: FAITHFUL
  Avg entailment:    0.949
  Avg contradiction: 0.003
  Sentence-level flags:
    [SUPPORTED   ] Anthropic was founded in 2021 by Dario and Daniela Amodei.
    [SUPPORTED   ] The company focuses on AI safety research.
    [SUPPORTED   ] Claude is designed to be helpful, harmless, and honest.

==========================================================
Summary type: INTRINSIC
  Avg entailment:    0.663
  Avg contradiction: 0.333
  Sentence-level flags:
    [HALLUCINATED] Anthropic was founded in 2020 by Dario Amodei and Daniela Amodei.
    [SUPPORTED   ] The company raised $1.5 billion in its Series C round in 2023.
    [SUPPORTED   ] Its primary product is Claude.

==========================================================
Summary type: EXTRINSIC
  Avg entailment:    0.662
  Avg contradiction: 0.001
  Sentence-level flags:
    [SUPPORTED   ] Anthropic was founded in 2021 by Dario and Daniela Amodei.
    [UNCERTAIN   ] The company has offices in San Francisco and London.
    [SUPPORTED   ] Anthropic focuses on AI safety research.

==========================================================
Summary type: FABRICATION
  Avg entailment:    0.431
  Avg contradiction: 0.004
  Sentence-level flags:
    [UNCERTAIN   ] Anthropic was founded in 2021 by Dario Amodei, who previously won...
    [SUPPORTED   ] The company focuses on AI safety research.

The faithful summary scores high on entailment across all sentences. The intrinsic hallucination (wrong year: 2020 vs 2021) shows up as a direct contradiction. The extrinsic summary scores as uncertain or neutral: the London detail is neither contradicted nor entailed by the source. The fabrication also registers as neutral, since the source says nothing about awards.

This illustrates a key insight: NLI-based scoring is well-suited for catching intrinsic hallucinations (direct contradictions) but limited for extrinsic ones, which score as "neutral" rather than "contradicted." Detecting extrinsic hallucinations and fabrications requires external knowledge sources, which is the focus of the Hallucination Detection chapter.

Notice that the fabricated Turing Award claim also scores as uncertain or low on both dimensions. The NLI model cannot detect the fabrication because it has no world knowledge of whether Dario Amodei won the Turing Award or not. It can only assess whether the claim is supported or contradicted by the source document, and since the source says nothing about awards, the claim is neither. This limitation is fundamental to the NLI approach, not an artifact of model quality.

Now let's visualize the scoring results:

Out[13]:
Visualization
Grouped bar chart comparing NLI entailment and contradiction scores across four summary hallucination types.
Average NLI entailment and contradiction scores for four summary types. The faithful summary achieves the highest entailment score, while the intrinsic summary has the highest contradiction score because its founding-year claim conflicts with the retrieved source sentence. Extrinsic and fabrication summaries keep low contradiction scores because their unsupported claims appear neutral rather than contradicted; their average entailment reflects the supported claims that remain in each summary.
Out[14]:
Visualization
Horizontal bar chart of per-sentence NLI entailment and contradiction scores for an intrinsic hallucination example.
Per-sentence NLI entailment and contradiction scores for the intrinsic hallucination summary after retrieving the closest source sentence for each claim. The first sentence, which changed the founding year from 2021 to 2020, receives the highest contradiction score while the faithful sentences achieve high entailment scores. This sentence-level localization is what makes NLI-based hallucination detection practically useful: it pinpoints the specific claim that conflicts with the source, enabling targeted correction rather than a coarse document-level quality flag.

The per-sentence breakdown localizes the error to the specific hallucinated sentence. This is how production faithfulness checkers like SummaC work: by decomposing outputs into atomic claims and scoring each one individually, rather than assigning a single quality score to the whole response.

Key Parameters

The key parameters for the NLI-based hallucination scorer are:

  • model: The cross-encoder checkpoint used for NLI classification. cross-encoder/nli-deberta-v3-small balances speed and accuracy; larger variants like cross-encoder/nli-deberta-v3-large improve precision at higher compute cost.
  • device: Set to -1 for CPU or 0 for the first available GPU. Batch scoring on GPU is significantly faster when evaluating many documents.
  • max_length: Truncation limit for premise-hypothesis pairs (512 tokens). Pairs exceeding this are truncated, which can affect accuracy on long source sentences.
  • top_k=None: Returns scores for all NLI labels (entailment, neutral, contradiction). Without this, the pipeline returns only the top-scoring label, which is insufficient for hallucination scoring.
  • contradiction threshold (0.5): The score at which a sentence is flagged as hallucinated. Lower thresholds increase recall at the cost of precision. The right value depends on your application's tolerance for false positives.