Zpět na blog
Pro vývojáře

Jak zabránit halucinacím AI: RAG, Elasticsearch a validace výstupů v praxi

Nasadili jste AI do e-shopu a model začal doporučovat produkty, které neexistují? Generuje odkazy do prázdna? Vymýšlí si specifikace? Tenhle článek vychází z reálného projektu, kde Google Gemini doporučoval příslušenství k produktům — a halucinoval. Ukážeme si architekturu, která halucinace eliminovala, a dalších 7 technik, jak LLM modely udržet při realitě.

ES index

Co jsou halucinace a proč vznikají

Halucinace je situace, kdy jazykový model (LLM) vygeneruje odpověď, která vypadá přesvědčivě, ale je fakticky nesprávná. Model si „vymyslí" data, odkazy, čísla nebo produkty, které neexistují. Nejedná se o záměrnou lež — model nemá koncept pravdy. Predikuje statisticky nejpravděpodobnější sekvenci tokenů na základě trénovacích dat.

V kontextu e-commerce je to obzvlášť nebezpečné. Představte si, že zákazníkovi doporučíte příslušenství s odkazem na produkt, který ve vašem katalogu neexistuje. Nebo model tvrdí, že objektiv X je kompatibilní s fotoaparátem Y, přestože mají jiný bajonet. Výsledek? Ztráta důvěry, zbytečné reklamace a poškozená reputace e-shopu.

15–20 %

odpovědí LLM modelů obsahuje faktické nepřesnosti při dotazech na specifická data bez kontextu

Reálný problém: Gemini doporučovalo neexistující produkty

V jednom z našich e-shopových projektů jsme nasadili Google Gemini pro dvě úlohy: doporučování kompatibilního příslušenství (např. baterie, objektivy, paměťové karty k fotoaparátům) a vyhledávání podobných produktů (alternativy ve stejné kategorii). Naivní přístup — zeptat se Gemini „Jaké příslušenství se hodí k Sony Alpha A7V?" — selhal okamžitě.

Model generoval produktové názvy a URL, které vypadaly věrohodně, ale neodpovídaly žádnému reálnému produktu v katalogu. Vymýšlel katalogová čísla, odkazoval na neexistující stránky a kombinoval reálné značky s fiktivními modely. Klasická halucinace — statisticky pravděpodobný, ale fakticky nesmyslný výstup.

Klíčové poznání: LLM model nemá přístup k vaší databázi. Nemůže vědět, které produkty skutečně prodáváte, jaké mají URL, skladové zásoby nebo ceny. Pokud ho necháte odpovídat bez kontextu, bude hádat — a hádat sebevědomě.

Řešení: RAG architektura s Elasticsearch

Problém jsme vyřešili architekturou RAG (Retrieval-Augmented Generation) — vzorem, kdy model nedostává otevřený dotaz, ale pracuje výhradně s daty, která mu předložíme. Klíčový princip: model nevyhledává — model vybírá.

Celý systém má tři vrstvy:

  1. Elasticsearch vrstva — vyhledá reálné kandidáty z databáze produktů
  2. LLM vrstva (Gemini) — z kandidátů vybere nejvhodnější na základě technické kompatibility
  3. Validační vrstva — ověří, že výstup modelu obsahuje pouze produkty z původního seznamu

Architektura

Databáze → Elasticsearch (kandidáti) → Gemini (výběr + ranking) → Validace ID → HTML výstup

Na žádném místě model negeneruje vlastní data. Pouze vybírá z předložených.

Krok 1: Elasticsearch hledá kandidáty

Než se vůbec obrátíme na LLM, Elasticsearch najde 30–50 reálných produktů, které by mohly být relevantní. Používáme tři vyhledávací strategie a výsledky kombinujeme:

Strategie A: Zmínka modelu v popisu

Hledáme produkty, které v popisu, parametrech nebo názvu zmiňují zdrojový produkt. Například pokud hledáme příslušenství k „Sony Alpha A7V", najdeme baterie, které mají v popisu „kompatibilní se Sony Alpha A7V".

Elasticsearch query

{
  "multi_match": {
    "query": "Sony Alpha A7V",
    "fields": ["body^5", "params^3", "title^1", "unfiltered^1"],
    "type": "cross_fields",
    "operator": "and"
  }
}

Pole body (popis produktu) má nejvyšší váhu (^5), protože výrobci příslušenství většinou uvádí kompatibilitu právě v popisu. Parametry (params) mají váhu ^3, název a nefiltrovaný text nižší.

Strategie B: Kompatibilní parametry

Ze zdrojového produktu extrahujeme klíčové technické parametry — typ baterie, bajonet objektivu, formát paměťové karty, rozměry filtrů — a hledáme produkty, které tyto hodnoty obsahují.

Příklad

Zdrojový produkt: Sony Alpha A7V
Extrahované parametry: baterie NP-FZ100, bajonet Sony E, SD UHS-II

→ Elasticsearch hledá produkty obsahující "NP-FZ100" NEBO "Sony E mount" NEBO "SD UHS-II"

Strategie C: Značka + kategorie (fallback)

Pokud první dvě strategie nenajdou dostatek kandidátů, hledáme obecné příslušenství od stejné značky v jiné kategorii. Výsledky řadíme podle „karmy" produktu — interní metriky kvality a obliby.

Tip

Všechny strategie filtrují pouze aktivní, viditelné a skladem dostupné produkty. To eliminuje další zdroj potenciálních problémů — doporučení produktů, které si zákazník nemůže koupit.

Krok 2: Gemini vybírá ze seznamu kandidátů

Kandidáty z Elasticsearch předáme Gemini jako strukturovaný seznam s ID. To je klíčový rozdíl oproti naivnímu přístupu — model nedostává otevřenou otázku, ale konkrétní „menu", ze kterého může vybírat.

Formát kandidátů předaných modelu

ID:4521 | Baterie Sony NP-FZ100 | kat: SONY-BAT-FZ100 | Sony | Baterie | 2280 mAh, Li-Ion, 7.2V
ID:4522 | Objektiv Sony FE 24-70mm f/2.8 GM II | kat: SEL2470GM2 | Sony | Objektivy | E-mount, full-frame
ID:4523 | SanDisk Extreme PRO SDXC 128GB | kat: SDSDXXD-128G | SanDisk | Paměťové karty | UHS-I, V30, 200MB/s
...

Každý kandidát obsahuje: ID z databáze, název, katalogové číslo, značku, kategorii, klíčové parametry a případně cenu a skladovou dostupnost. Model dostane systémovou instrukci, která ho striktně omezuje:

System prompt

Jsi technický specialista e-shopu. Tvým úkolem je z předloženého seznamu kandidátů vybrat POUZE technicky kompatibilní příslušenství.

Ověřuj kompatibilitu podle parametrů (bajonet, typ baterie, formát média, rozměr atd.).
Pokud si nejsi jistý kompatibilitou, produkt NEZAŘAZUJ.
Odpovídej POUZE validním JSON objektem.

Formát odpovědi:
{
  "groups": [
    {"name": "Baterie", "products": [{"id": 4521, "reason": "NP-FZ100, přímá kompatibilita"}]},
    {"name": "Objektivy", "products": [{"id": 4522, "reason": "E-mount, full-frame"}]}
  ]
}

Klíčová slova v promptu: „POUZE", „z předloženého seznamu", „validním JSON". Model nemá prostor pro kreativitu — může pouze vybírat ID z předloženého seznamu a zdůvodnit výběr.

Krok 3: Validace výstupu

I s perfektním promptem může model občas vrátit neočekávaný výstup. Proto je třetí vrstva — serverová validace — nezbytná. Každé ID z Gemini odpovědi ověřujeme proti původnímu seznamu kandidátů:

PHP — validační logika

$selected = json_decode($geminiResponse, true);

foreach ($selected['groups'] as $group) {
  foreach ($group['products'] as $rec) {
    $id = (int) ($rec['id'] ?? 0);
    
    // Klíčová kontrola: existuje ID v původním seznamu kandidátů?
    if (!$id || !isset($candidateDetails[$id])) {
      continue; // Neznámé ID = tiše přeskočit
    }
    
    // HTML generujeme z DATABÁZOVÝCH dat, ne z Gemini výstupu
    $product = $candidateDetails[$id];
    $html .= buildProductCard($product);
  }
}

Všimněte si dvou klíčových principů:

  • Neznámá ID jsou tiše zahozena — pokud model vymyslí ID, které nebylo v kandidátním seznamu, jednoduše se ignoruje. Žádná chyba, žádný fallback, prostě se nepoužije.
  • HTML se generuje z databáze, ne z modelu — názvy, URL, ceny, obrázky — vše pochází z ověřených databázových dat. Model pouze určil, které produkty vybrat, nikoli jak je zobrazit.

Tato architektura dělá halucinace strukturálně nemožnými. Model nemůže vygenerovat odkaz na neexistující produkt, protože odkazy generuje server z databáze. Model pouze rozhoduje „ano/ne" pro reálné produkty.

Proč je RAG tak účinný

RAG (Retrieval-Augmented Generation) není jen buzzword — je to fundamentální změna v tom, co od modelu požadujeme. Porovnejme dva přístupy:

Naivní přístup (halucinace garantovány)

"Jaké příslušenství se hodí k Sony Alpha A7V? Vrať seznam s odkazy."

→ Model generuje názvy, URL i parametry z "hlavy" → halucinace

RAG přístup (halucinace eliminovány)

"Zde je seznam 30 reálných produktů z našeho katalogu [data]. Vyber kompatibilní příslušenství. Vrať pouze ID."

→ Model vybírá z reálných dat → validace ID → bezpečný výstup

Rozdíl je v tom, že RAG přesouvá úlohu modelu z generování faktů na reasoning nad fakty. Model nemusí „vědět" — musí pouze „rozhodnout". A rozhodování nad strukturovanými daty je přesně to, v čem LLM modely excelují.

0

halucinovaných odkazů po nasazení RAG architektury — model fyzicky nemůže odkázat na neexistující produkt

Další techniky proti halucinacím

RAG s Elasticsearch je základ, ale existuje celá řada dalších technik, které můžete kombinovat. Projdeme si sedm nejúčinnějších.

1. Constrained output — JSON schéma a strukturované odpovědi

Místo volného textu vyžadujte od modelu striktně definovaný formát odpovědi. Moderní LLM API (Gemini, Claude, GPT-4o) podporují JSON mode nebo response schemas, které model nutí odpovídat v přesně definované struktuře.

Gemini API — response schema

'generationConfig' => [
  'responseMimeType' => 'application/json',
  'responseSchema' => [
    'type' => 'object',
    'properties' => [
      'groups' => [
        'type' => 'array',
        'items' => [
          'type' => 'object',
          'properties' => [
            'name' => ['type' => 'string'],
            'products' => [
              'type' => 'array',
              'items' => [
                'type' => 'object',
                'properties' => [
                  'id' => ['type' => 'integer'],
                  'reason' => ['type' => 'string']
                ]
              ]
            ]
          ]
        ]
      ]
    ]
  ]
]

Když model musí vrátit JSON s předdefinovanou strukturou, nemůže „odbočit" do volného textu a vymýšlet narativní odpovědi. Pole id s typem integer ho nutí vrátit číslo, ne vymyšlený URL.

2. Google Search Grounding

Gemini API nabízí vestavěnou funkci Google Search grounding — model si může ověřit fakta proti živým výsledkům z Google vyhledávání. Aktivujete ji jedním parametrem:

Gemini API — search grounding

'tools' => [
  ['google_search' => (object)[]]
]

V našem projektu jsme tuto metodu použili jako alternativní přístup — Gemini generoval doporučení a současně si ověřoval existenci produktů přes Google. Ale i s touto funkcí jsme museli přidat post-processingovou vrstvu, která ověřovala vygenerované URL proti skutečné databázi a odstraňovala neexistující odkazy pomocí DOM manipulace.

Search grounding je užitečný jako doplňková vrstva, ale sám o sobě nestačí. Model může najít reálnou URL z Google, která ale neodpovídá vašemu katalogu — třeba produkt, který jste přestali prodávat nebo který je u konkurence.

Tip

Search grounding zvyšuje latenci odpovědi o 1–3 sekundy (model musí počkat na výsledky z Google). Pro real-time doporučení v e-shopu je RAG s Elasticsearch rychlejší a spolehlivější.

3. Chain-of-Verification (CoVe)

Technika, kdy model sám sebe kontroluje ve vícekrokovém procesu:

  1. Generování — model vytvoří odpověď
  2. Generování ověřovacích otázek — model sám navrhne otázky, kterými lze odpověď zkontrolovat
  3. Odpovědi na ověřovací otázky — model odpovídá na každou otázku nezávisle (bez kontextu původní odpovědi)
  4. Oprava — model porovná původní odpověď s ověřovacími odpověďmi a opraví nesrovnalosti

Příklad CoVe promptu

Krok 1: Vygeneruj doporučení příslušenství k [produkt].

Krok 2: Pro každé doporučení si polož otázky:
- Existuje tento produkt skutečně?
- Je technicky kompatibilní? Proč konkrétně?
- Jaký je zdroj mého tvrzení o kompatibilitě?

Krok 3: Odpověz na každou otázku samostatně.

Krok 4: Odstraň doporučení, kde odpovědi odhalily nesrovnalost.

CoVe neeliminuje halucinace úplně, ale výrazně je redukuje — model si často sám odhalí slabá místa. Hodí se zejména pro úlohy, kde nemáte strukturovaná zdrojová data pro plnohodnotný RAG.

4. Temperature a sampling parametry

Parametr temperature ovlivňuje „kreativitu" modelu. Pro faktické úlohy, kde nechcete žádnou kreativitu, nastavte temperature na minimum:

  • Temperature 0.0–0.2 — deterministický výstup, model volí nejpravděpodobnější tokeny. Ideální pro klasifikaci, extrakci dat a výběr z předložených možností.
  • Temperature 0.5–0.7 — mírná variabilita. Vhodné pro generování popisů nebo marketingových textů.
  • Temperature 0.8–1.0+ — vysoká kreativita. Vhodné pro brainstorming, ale zvyšuje riziko halucinací.

Gemini API — nízká temperature

'generationConfig' => [
  'temperature' => 0.1,
  'topP' => 0.8,
  'topK' => 40,
  'responseMimeType' => 'application/json'
]

Pro naši úlohu doporučování příslušenství používáme temperature: 0.1 — model by měl vybírat kompatibilní produkty konzistentně, ne „kreativně". Kombinace s topPtopK dále zužuje distribuci pravděpodobností a eliminuje dlouhý ocas nepravděpodobných tokenů.

5. Prompt engineering — explicitní omezení

Způsob, jakým formulujete prompt, má obrovský vliv na míru halucinací. Několik osvědčených technik:

Říct modelu, že může říct „nevím"

Modely mají tendenci vždy odpovědět — i když odpověď neznají. Explicitně jim dejte svolení říct „nevím":

Prompt

Pokud si nejsi jistý kompatibilitou, produkt NEZAŘAZUJ. Je lepší vrátit méně výsledků než jeden špatný. Pokud nenajdeš žádný kompatibilní produkt, vrať prázdné pole.

Vyžadovat zdůvodnění (reasoning)

Když model musí ke každému výběru uvést důvod, kvalita výrazně stoupá — model si „uvědomí" slabá místa ve svém reasoning:

Prompt

Pro každý vybraný produkt uveď pole "reason" s konkrétním technickým důvodem kompatibility. Nestačí "je kompatibilní" — uveď konkrétní parametr (např. "NP-FZ100, přímá kompatibilita" nebo "E-mount bajonet, full-frame pokrytí").

Negativní instrukce

Explicitně zakázat nežádoucí chování:

Prompt

NIKDY nevymýšlej produkty, které nejsou v seznamu. NIKDY negeneruj URL adresy. NIKDY neuváděj katalogová čísla, která nejsou ve vstupních datech. Pokud si nejsi 100% jistý, raději produkt vynech.

6. Post-processing a sanitizace výstupu

I se všemi výše uvedenými technikami je serverová validace nutná. Nikdy nespoléhejte na to, že model vrátí 100% korektní výstup. Implementujte tyto kontroly:

  • ID validace — každé ID z odpovědi ověřit proti whitelist množině kandidátů
  • JSON parsing s fallbackem — model občas vrátí JSON s extra textem; parsujte robustně pomocí regex extrakce JSON bloku
  • URL sanitizace — pokud model přece jen vrátí URL, generujte je na serveru z databázového slugu, nikdy nepoužívejte URL z modelu
  • Duplicity — model může stejný produkt doporučit vícekrát; deduplikujte podle ID
  • Limit výstupů — omezte maximální počet doporučení (v našem případě max 12 produktů)

PHP — extrakce JSON z odpovědi

// Model občas vrátí JSON zabalený v markdown code blocku
$text = $geminiResponse;

// Zkusit extrahovat JSON z ```json ... ``` bloku
if (preg_match('/```json\s*(.*?)\s*```/s', $text, $m)) {
  $text = $m[1];
}

// Fallback: najít první { ... } blok
if (preg_match('/\{\.*\}/s', $text, $m)) {
  $text = $m[0];
}

$data = json_decode($text, true);
if (!$data) {
  // Log error, return empty result — never crash
  return [];
}

7. Cachování a human-in-the-loop

Dvě doplňkové strategie, které v praxi významně pomohou:

Cachování výsledků

Výsledky LLM doporučení ukládáme do cache (databáze) s TTL několik dní. Výhody:

  • Konzistence — stejný zákazník vidí stejná doporučení při opakované návštěvě
  • Rychlost — odpadá latence API volání (200–2000 ms)
  • Náklady — eliminace zbytečných API volání šetří tokeny
  • Kontrola — cachovaná doporučení lze manuálně revidovat a opravit

Human-in-the-loop

Pro kritické případy přidejte možnost lidské kontroly:

  • Admin rozhraní, kde produktový manažer vidí AI doporučení a může je schválit/zamítnout
  • Tlačítko „Špatné doporučení" pro zákazníky — feedback, který se loguje pro pozdější analýzu
  • Pravidelný audit — náhodný vzorek doporučení procházený člověkem

Celkový přehled technik

Shrňme si všechny techniky a jejich účinnost:

Technika Účinnost Kdy použít
RAG (Elasticsearch + LLM)★★★★★Vždy, když máte data
Constrained output (JSON)★★★★☆Strukturované úlohy
Validace výstupu (ID check)★★★★★Vždy — poslední síť
Nízká temperature★★★☆☆Faktické úlohy
Prompt engineering★★★☆☆Vždy — základní hygiena
Chain-of-Verification★★★★☆Bez strukturovaných dat
Google Search grounding★★★☆☆Ověření faktů z webu
Cachování + HITL★★★★☆Produkční systémy

Žádná technika sama o sobě nestačí. V produkčním systému je ideální kombinovat minimálně 3–4 z nich. V našem e-shopovém projektu používáme všech sedm vrstev současně.

Praktický checklist pro vývojáře

Pokud implementujete LLM do systému, kde záleží na faktické správnosti, projděte tento checklist:

  1. Máte strukturovaná zdrojová data? → Implementujte RAG. Elasticsearch, Meilisearch, pgvector — cokoliv, co vám vrátí reálné kandidáty z vaší databáze.
  2. Předáváte modelu data s unikátními ID? → Model musí vracet ID, ne volný text. ID validujete na serveru.
  3. Vyžadujete JSON output? → Použijte responseMimeType: application/json a response schema, pokud to API podporuje.
  4. Máte nízkou temperature? → Pro faktické úlohy 0.0–0.2. Kreativita je nepřítel přesnosti.
  5. Říkáte modelu, že může říct „nevím"? → Explicitně. Jinak bude vždy generovat odpověď.
  6. Validujete výstup na serveru? → Nikdy nezobrazujte surový LLM výstup uživateli bez validace.
  7. Generujete URL a metadata na serveru? → Nikdy nepoužívejte URL z modelu. Vždy generujte z databáze.
  8. Cachujete výsledky? → Šetří náklady, zvyšuje konzistenci, umožňuje audit.
  9. Logujete vstupy i výstupy? → Bez logů nedokážete diagnostikovat problémy ani měřit kvalitu.
  10. Máte fallback pro případ selhání? → Co se stane, když API neodpoví? Zobrazíte „žádná doporučení", ne rozbitou stránku.

Zlaté pravidlo: LLM model používejte pro reasoning, ne pro retrieval. Nechte ho rozhodovat nad daty, která mu poskytnete — ale nikdy ho nenechte data vymýšlet. To je úloha pro databázi, ne pro jazykový model.

3+1

vrstvy obrany — RAG retrieval, constrained output, serverová validace + cachování jako bonus