{"id":316,"date":"2026-06-11T13:01:46","date_gmt":"2026-06-11T13:01:46","guid":{"rendered":"https:\/\/abrarqasim.com\/blog\/llm-hallucination-examples-how-i-catch-them\/"},"modified":"2026-06-11T13:01:46","modified_gmt":"2026-06-11T13:01:46","slug":"llm-hallucination-examples-how-i-catch-them","status":"publish","type":"post","link":"https:\/\/abrarqasim.com\/blog\/llm-hallucination-examples-how-i-catch-them\/","title":{"rendered":"LLM Hallucination Examples (And How I Catch Them)"},"content":{"rendered":"<p>Confession: I shipped a feature last spring that confidently told a user our refund window was 60 days. It&rsquo;s 14. The model made up the number, wrote it in a clean, polite sentence, and I didn&rsquo;t catch it until the user did. That&rsquo;s the thing about LLM hallucination examples that nobody warns you about. They don&rsquo;t look like errors. They look like answers.<\/p>\n<p>I&rsquo;ve spent a chunk of this year building retrieval-augmented chatbots for small teams, and I&rsquo;ve collected a small museum of these failures. Some are funny. A few cost me a weekend. What I want to do here is show you actual examples, explain why they happen even when you &ldquo;did everything right,&rdquo; and walk through the cheap checks I now run before anything reaches a user.<\/p>\n<h2 id=\"the-hallucinations-i-keep-seeing-in-real-apps\">The hallucinations I keep seeing in real apps<\/h2>\n<p>Let me start with specifics, because abstract talk about &ldquo;factual reliability&rdquo; never helped me debug anything.<\/p>\n<p>The made-up policy number. The refund example above. The docs said nothing about 60 days. The model filled a gap with a plausible figure because plausible is what it optimizes for.<\/p>\n<p>The phantom citation. I asked a support bot to answer &ldquo;from the docs only,&rdquo; and it produced a tidy answer with a reference to <code>docs\/billing\/proration.md<\/code>. That file doesn&rsquo;t exist. It invented a filename that matched the shape of our repo.<\/p>\n<p>The confident API method. This one stung because I trusted it. A coding assistant told me to call <code>client.embeddings.batch_create()<\/code>. Looks real. Reads real. The method has never existed. I lost an hour before I checked the actual SDK.<\/p>\n<p>The half-right merge. The worst kind. The model pulls two real facts from context and stitches them into one sentence that&rsquo;s wrong. &ldquo;Your plan includes 50GB of storage and unlimited seats&rdquo; when the plan has 50GB and five seats. Both numbers came from the docs. The combination is fiction.<\/p>\n<p>If you want a longer tour of which models I run locally and how often they do this, I wrote up my setup in <a href=\"https:\/\/abrarqasim.com\/blog\/local-llm-2026-what-i-actually-run-and-what-i-dont\" rel=\"noopener\">my notes on running local LLMs in 2026<\/a>. The short version: every model I&rsquo;ve tried does this. Bigger ones just do it more fluently.<\/p>\n<h2 id=\"why-do-llms-hallucinate-even-with-retrieval\">Why do LLMs hallucinate even with retrieval?<\/h2>\n<p>The intuitive fix is retrieval. Give the model the right documents, tell it to answer only from them, and the made-up answers should stop. I believed this for an embarrassingly long time.<\/p>\n<p>They don&rsquo;t stop. They drop. Big difference.<\/p>\n<p>A token predictor doesn&rsquo;t &ldquo;know&rdquo; where its knowledge ends. When the retrieved chunks don&rsquo;t contain the answer, the model doesn&rsquo;t go quiet. It blends what it retrieved with what it half-remembers from training and gives you a sentence that sounds grounded. The retrieval step lowered the odds. It didn&rsquo;t install a brake.<\/p>\n<p>There&rsquo;s research that backs up the uncomfortable part of this. A 2026 paper on <a href=\"https:\/\/arxiv.org\/abs\/2606.06748\" rel=\"nofollow noopener\" target=\"_blank\">Evidence Graph Consistency in retrieval-augmented generation<\/a> evaluated hallucination detectors across six models on thousands of RAG responses and found something I didn&rsquo;t expect: the structural signal that flags a hallucination in one model family points the opposite way in another. Their conclusion was blunt. Embedding-based graph consistency &ldquo;cannot serve as a model-independent hallucination detection signal.&rdquo; Translation for the rest of us: a check that works on your Llama setup might quietly fail when you swap in a different model. There&rsquo;s no universal smoke detector you can bolt on once and forget.<\/p>\n<p>So retrieval helps, and it&rsquo;s still worth doing well. It just isn&rsquo;t the finish line I wanted it to be.<\/p>\n<h2 id=\"the-cheapest-check-that-actually-caught-things-citation-grounding\">The cheapest check that actually caught things: citation grounding<\/h2>\n<p>After the refund incident I stopped trusting the model&rsquo;s prose and started trusting its receipts. The rule I settled on: every factual claim has to point at a retrieved chunk, and I verify the pointer in code, not in the prompt.<\/p>\n<p>The trick is to make the model emit structured output with explicit source IDs, then check that those IDs were actually in the context you sent. Here&rsquo;s the shape of it:<\/p>\n<pre><code class=\"language-python\">import json\n\ndef answer_with_citations(client, question, chunks):\n    # chunks: list of {&quot;id&quot;: &quot;doc_3&quot;, &quot;text&quot;: &quot;...&quot;}\n    context = &quot;\\n\\n&quot;.join(f&quot;[{c['id']}] {c['text']}&quot; for c in chunks)\n    valid_ids = {c[&quot;id&quot;] for c in chunks}\n\n    prompt = (\n        &quot;Answer using ONLY the sources below. &quot;\n        &quot;Return JSON: {\\&quot;answer\\&quot;: str, \\&quot;citations\\&quot;: [source_id, ...]}. &quot;\n        &quot;If the sources don't contain the answer, set answer to &quot;\n        &quot;\\&quot;I don't have that in the docs.\\&quot; and citations to [].\\n\\n&quot;\n        f&quot;{context}\\n\\nQuestion: {question}&quot;\n    )\n\n    raw = client.complete(prompt)        # your model call\n    result = json.loads(raw)\n\n    # The part that matters: verify the receipts in code.\n    cited = set(result[&quot;citations&quot;])\n    if not cited.issubset(valid_ids):\n        bogus = cited - valid_ids\n        raise HallucinationError(f&quot;model cited unknown sources: {bogus}&quot;)\n\n    return result\n<\/code><\/pre>\n<p>That last block is doing the real work. If the model cites <code>doc_9<\/code> and you only sent <code>doc_1<\/code> through <code>doc_5<\/code>, you know it&rsquo;s improvising before the user sees a word. This single check would have caught my phantom-citation bug. It does nothing for the made-up refund number, though, because the model could cite a real chunk and still misread it. Which is why I don&rsquo;t rely on it alone.<\/p>\n<h2 id=\"detecting-the-subtle-ones-with-self-consistency\">Detecting the subtle ones with self-consistency<\/h2>\n<p>The half-right merges and misread numbers slip past citation checks. For those I lean on a different idea: ask the model more than once and watch whether it agrees with itself.<\/p>\n<p>A hallucinated fact tends to wobble. Confident, real answers tend to repeat. So I sample a few responses at a non-zero temperature and compare. If the answers diverge on the specific claim, I flag it for a human or fall back to &ldquo;I&rsquo;m not sure.&rdquo;<\/p>\n<pre><code class=\"language-python\">from collections import Counter\n\ndef self_consistency_flag(client, prompt, n=5):\n    answers = [client.complete(prompt, temperature=0.7) for _ in range(n)]\n    norm = [a.strip().lower() for a in answers]\n    top, count = Counter(norm).most_common(1)[0]\n    agreement = count \/ n\n    return {&quot;answer&quot;: answers[0], &quot;agreement&quot;: agreement,\n            &quot;trustworthy&quot;: agreement &gt;= 0.6}\n<\/code><\/pre>\n<p>It&rsquo;s crude. It costs you n times the tokens, so I only run it on high-stakes paths like billing or anything that quotes a number. But it works often enough to earn its keep. A 2026 paper called <a href=\"https:\/\/arxiv.org\/abs\/2606.07528\" rel=\"nofollow noopener\" target=\"_blank\">BEACON<\/a> formalizes this instinct far better than my five-sample hack: it pulls a 31-dimensional feature vector from multi-pass generation, including paraphrase stability and chain-of-thought consistency, and gets around 0.81 AUROC at flagging hallucinations from black-box model outputs alone. The takeaway I drew from it is that consistency across samples is a genuine signal, not just a folk remedy. You can build something serious on it.<\/p>\n<p>There&rsquo;s also promising work on structure. One group cut hallucinations on a hard Wikipedia question-answering benchmark by giving the retriever a <a href=\"https:\/\/arxiv.org\/abs\/2606.05901\" rel=\"nofollow noopener\" target=\"_blank\">lightweight graph of the evidence<\/a> instead of a flat pile of chunks, which improved factual precision and recall. I haven&rsquo;t shipped that in production yet, but it&rsquo;s next on my list to try.<\/p>\n<h2 id=\"what-i-actually-do-now-in-order\">What I actually do now, in order<\/h2>\n<p>Here&rsquo;s the pipeline I run, stripped down. None of it is fancy and that&rsquo;s the point.<\/p>\n<p>First, retrieve well. Better chunks mean fewer gaps for the model to paper over. Most of my early &ldquo;hallucinations&rdquo; were really retrieval misses wearing a costume.<\/p>\n<p>Second, force structured answers with citations, and verify the citation IDs in code. Reject anything that points at sources you never sent.<\/p>\n<p>Third, run self-consistency on the money paths only. Numbers, dates, policy claims. If the samples disagree, I&rsquo;d rather say &ldquo;I don&rsquo;t know&rdquo; than guess on a user&rsquo;s behalf.<\/p>\n<p>Fourth, log every &ldquo;I don&rsquo;t have that in the docs&rdquo; response. Those are gold. They tell me exactly which questions my retrieval is failing, which is usually a content gap I can fix by writing one more doc.<\/p>\n<p>And fifth, never let one model grade its own homework. I learned this the hard way and wrote about it separately in <a href=\"https:\/\/abrarqasim.com\/blog\/ai-debugging-why-i-stopped-letting-one-model-fix-itself\" rel=\"noopener\">why I stopped letting one model fix itself<\/a>. A second model, or a plain code assertion, catches things the first one is structurally blind to.<\/p>\n<h2 id=\"try-one-thing-this-week\">Try one thing this week<\/h2>\n<p>Pick your highest-stakes endpoint, the one that quotes a number or a policy. Add the citation-verification block above. That&rsquo;s it. Maybe fifteen lines. You&rsquo;ll either prove your model is well-behaved on that path, or you&rsquo;ll catch it inventing a source ID, and both outcomes are worth the afternoon.<\/p>\n<p>I keep more of these build notes and the projects behind them in <a href=\"https:\/\/abrarqasim.com\/work\" rel=\"noopener\">my work<\/a> if you want to see how this plays out beyond a toy example. Hallucinations aren&rsquo;t going away. But the gap between &ldquo;the model lied to a user&rdquo; and &ldquo;the model got caught lying before anyone saw it&rdquo; is mostly a few lines of verification you control. I&rsquo;d rather own those lines than trust the prose.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Real LLM hallucination examples from my own apps, why retrieval doesn&#8217;t fully fix them, and the cheap code checks I run to catch made-up answers before users see them.<\/p>\n","protected":false},"author":2,"featured_media":315,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"","rank_math_description":"Real LLM hallucination examples from my own apps, why retrieval doesn't fully fix them, and the cheap code checks I run to catch made-up answers before users see them.","rank_math_focus_keyword":"llm hallucination examples","rank_math_canonical_url":"","rank_math_robots":"","footnotes":""},"categories":[4],"tags":[28,21,5,11,357],"class_list":["post-316","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","tag-ai","tag-hallucination","tag-llm","tag-rag","tag-retrieval-augmented-generation"],"_links":{"self":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/316","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/comments?post=316"}],"version-history":[{"count":0,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/316\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media\/315"}],"wp:attachment":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media?parent=316"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/categories?post=316"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/tags?post=316"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}