The most dangerous LLM failure is not a crash — it is a confident wrong answer. Hallucinations look like expertise: citations, APIs, medical details, legal clauses. Until you treat fluency as orthogonal to truth, every feature you ship will eventually embarrass you in front of a customer or an auditor.
An LLM samples plausible continuations. Training rewarded looking like the training distribution, not checking a ledger. When the prompt asks for a fact the weights never reliably stored — or that changed last week — the model still has to emit tokens. Plausible tokens win. That is hallucination: unsupported or false content presented as if it were known.
Sibling limitations share the same root: sensitivity to wording, shallow multi-step reasoning without scaffolding, bias from data, and non-determinism under sampling. Mitigations are engineering: retrieval, tools, validation, evals, and humans on the high-impact path.
| Type | Example | Typical fix |
|---|---|---|
| Factual | Wrong capital, fake metric | RAG / tools / abstain |
| Citation | Invented DOI or URL | Require fetchable sources |
| Faithfulness | Summary adds claims | Ground on source text only |
| Tool / API | Invented endpoint fields | Schema + live docs tool |
| Reasoning | Arithmetic or logic slip | Tools, scratchpads, checks |
You cannot fix what you do not score. Lightweight eval ideas:
Track hallucination rate next to latency and cost. A model that is 200ms faster but invents policy clauses is not a win for a compliance bot.
Say clearly: the system drafts; tools and humans certify. UX that shows evidence snippets, confidence labels, and easy “report wrong answer” affordances reduces blind trust. Hide the confidence theater (fake percentage meters) unless they map to a real calibrated signal — users treat “94% sure” as science even when it is not.
Detect empty grounding before you let the model “be helpful”:
def should_abstain(docs: list[str], min_chars: int = 40) -> bool:
return sum(len(d.strip()) for d in docs) < min_chars
docs = ["", " "]
if should_abstain(docs):
answer = "UNKNOWN: no supporting documents retrieved."
else:
answer = "(call model with docs)"
print(answer)
Faithfulness check sketch — every claim sentence must overlap the source (crude but teachable):
def unsupported_sentences(answer: str, source: str) -> list[str]:
src = source.lower()
bad = []
for sent in answer.split("."):
s = sent.strip()
if not s:
continue
tokens = [t for t in s.lower().split() if len(t) > 3]
if not tokens:
continue
hit = sum(1 for t in tokens if t in src)
if hit / len(tokens) < 0.3:
bad.append(s)
return bad
src = "Refunds arrive in 5 to 7 business days."
ans = "Refunds arrive in 5 to 7 business days. Platinum users get same-day cash."
print(unsupported_sentences(ans, src))
Force an evidence field in structured answers:
import json
def validate_grounded(raw: str) -> dict:
data = json.loads(raw)
if data.get("status") == "ok" and not data.get("evidence_spans"):
raise ValueError("ok answers require evidence_spans")
if data.get("status") == "unknown" and data.get("answer"):
raise ValueError("unknown must not include a factual answer")
return data
Logging for postmortems:
def log_call(prompt_version: str, temperature: float, grounded: bool, text: str):
return {
"prompt_version": prompt_version,
"temperature": temperature,
"grounded": grounded,
"preview": text[:160],
}
Hallucinations are fluent, unsupported claims from a next-token predictor — contain them with grounding, abstention, validation, evals, and human review.