Publikováno 20. května 2026 · Aktualizováno 23. července 2026

E-mailový AI agent není RAG nad e-maily z minulosti: co jsem se naučil při vývoji e-mail agenta, od optimalizace nákladů přes různé přístupy po nejlepší řešení

Co jsem se při vývoji e-mailového AI agenta naučil o modelových voláních, RAGu, prompt cache, evaluacích, slepých uličkách a spolehlivém provozu.

Vytvořit e-mailového AI agenta, který na několika připravených příkladech napíše přesvědčivou odpověď, není příliš složité. Mnohem těžší je dostat takový systém do stavu, kdy spolehlivě pracuje s reálnými e-maily, různě dlouhou historií konverzací, měnícími se informacemi a situacemi, které se nevejdou do předem připraveného scénáře.

Finální výsledek proto nevznikl jedním promptem ani nahráním dokumentů do vektorové databáze. Vyžadoval opakované evaluace nad skutečnými e-maily, ruční čtení návrhů, sledování jednotlivých kroků, úpravy znalostní báze, kontroly v kódu i vracení změn, které na papíře vypadaly chytře, ale v praxi odpovědi zhoršily.

Nejtěžší nebylo přimět model, aby napsal slušný e-mail. Nejtěžší bylo zjistit:

  • kdy nemá psát vůbec;
  • odkud se má vzít aktuální pravda;
  • které kontroly má dělat další model a které obyčejný kód;
  • jak poznat, že „zlepšení“ ve skutečnosti zhoršilo výsledek;
  • a co dělat, když se dvě správně vypadající části systému složí do špatné odpovědi.

Nechtěl jsem autonomní podporu

Agent pracuje nad reálnou zákaznickou e-mailovou schránkou. Přicházejí do ní produktové dotazy, problémy s přístupem, žádosti o záznam přednášky, reakce na kampaně, automatické faktury i zprávy obsahující zdravotní nebo osobní kontext.

V takovém prostředí nestačí, že odpověď zní přesvědčivě. Právě přesvědčivě napsaná nepravda je jeden z nejhorších možných výstupů.

Od začátku jsem proto nastavil jednoduchou hranici: agent nikdy e-mail automaticky neodešle. Vytvoří koncept v Gmailu, pošle upozornění přes Telegram a poslední kontrolu i odeslání nechá člověku.

To zásadně ovlivnilo celou architekturu. Nestavěl jsem náhradu člověka, ale systém, který má připravit co nejlepší první verzi a zároveň dát jasně najevo, kdy mu chybí informace.

Jeden e-mail může znamenat sedm modelových volání

Celé zpracování je poskládané jako graf v LangGraphu. Zjednodušeně vypadá takto:

Gmail push notifikace
→ triage a odfiltrování automatických zpráv
→ klasifikace záměru
→ vytvoření vyhledávacích dotazů
→ hledání ve znalostech a historických e-mailech
→ reranking nalezených podkladů
→ napsání odpovědi
→ kontroly a případná oprava
→ koncept v Gmailu
→ upozornění přes Telegram
→ lidská kontrola a odeslání

Běžný úplný průchod tak může obsahovat přibližně sedm až osm modelových volání. Ne všechna ale dělají totéž.

Hlavní writer dostane nejsilnější model a jako jediný má volně formulovat odpověď. Menší a levnější modely řeší úzké úkoly: vrátí respond nebo ignore, přiřadí typ dotazu, vytvoří vyhledávací dotaz, vyberou relevantní podklady nebo upozorní, že odpověď neřeší původní otázku.

Postupně jsem zjistil, že tohle rozdělení je důležitější než samotný počet volání. Užitečný „mikromodel“ má malý a předvídatelný výstup. Nebezpečný kontrolní model dostane možnost přepsat celý e-mail, doplnit chybějící fakta a stát se druhým writerem.

Podobnou specializaci dnes veřejně popisuje také Intercom u svého agenta Fin: samostatný model pro vyhledání kandidátů, reranker, shrnutí kontextu, detekci předání člověku a porozumění reakci zákazníka. Intercom: Fin AI Agent explained

Rozdíl je v cíli a měřítku. Fin má velkou část konverzací sám vyřešit. Můj agent připravuje koncept, který vždy uvidí člověk. Kopírovat celou architekturu autonomního systému by proto znamenalo platit za další kontrolní vrstvy, jejichž roli už u mě plní lidská kontrola.

Historický e-mail není zdroj pravdy

Agent používá dvě oddělené kolekce v RAG databázi.

První obsahuje vybrané historické e-maily. Ty pomáhají se stylem, oslovením, délkou a způsobem, jakým byly podobné situace dříve vysvětleny.

Druhá obsahuje spravovanou znalostní bázi: produkty, přístupy, odkazy, pravidla, procesy a další fakta.

Toto rozdělení vzniklo z praktického problému. Historická odpověď může znít přesně tak, jak má, a zároveň obsahovat starou cenu, neplatný odkaz nebo proces, který se mezitím změnil. Sémantická podobnost ještě neznamená autoritu.

Do writeru proto historické e-maily vstupují jen jako stylový precedent. Nemají právo přebít aktuální znalostní bázi.

Stejně důležitá je opačná hranice: stav konkrétní platby, objednávky nebo uživatelského účtu nepatří do RAG vůbec. Pokud agent nemá přístup k živému systému, nesmí si tento stav odvodit z textu. Musí použít připojené API, nebo odpověď označit k lidské kontrole.

Audit RAG odhalil chybu v dělení textu

Pozdější audit živé kolekce ukázal zajímavý problém. Databáze už v té době disku odpovídala. Chyba byla v algoritmu, který text dělil na bloky.

Z 81 zkontrolovaných bloků:

  • 41 začínalo uprostřed věty nebo slova;
  • 53 končilo uprostřed věty;
  • 48 míchalo alespoň tři různé nadpisy a témata;
  • 9 obsahovalo URL fyzicky rozříznutou hranicí bloku.

Model tak někdy nedostal špatně vyhledaný odkaz. Dostal správný dokument, ve kterém ale viděl jen https://mar… nebo druhou polovinu URL v dalším bloku.

Žádný sofistikovaný reasoning tento problém spolehlivě neopraví. Model nemůže použít znaky, které se do jeho kontextu vůbec nedostaly.

Nový návrh ingestu proto používá nadpisy jako sémantické hranice, drží jednu sekci u jednoho tématu a obsahuje tvrdou kontrolu, že každá URL zůstala znak po znaku zachovaná. Nová verze se ingestuje do samostatné kolekce, aby šlo starou jedním přepínačem vrátit.

Stejné pravidlo dnes doporučuje i Intercom: používat H1–H3 jako sémantické hranice a psát sekce tak, aby dávaly smysl i po vytažení z původního článku. Intercom: Optimizing content for Fin

Tohle je pro mě jedna z hlavních lekcí celého projektu: produkční RAG není funkce „nahraj dokumenty“. Je to datový systém s verzováním, identitou zdrojů, aktualizacemi, kontrolou chunků, retrieval testy a rollbackem.

Prompt postupně narostl na vlastní programovací jazyk

Writer prompt měl v jedné fázi přibližně 459 řádků, 23 sekcí a kolem dvanácti bloků označených jako tvrdý zákaz.

Obsahoval hlas autorky, strukturu odpovědi, fakta, URL pravidla, prodejní logiku, zdravotní omezení, podpis, délku podle typu e-mailu i seznam věcí, které model nikdy nesmí slíbit.

Každá jednotlivá instrukce dávala smysl. Dohromady ale nikoliv.

Model někdy dodržel nové pravidlo, ale přestal respektovat starší.

Problém byl v počtu instrukcí: pravidlo v promptu existuje, ale v množství ostatních pravidel už nemá dostatečnou váhu.

Přirozený nápad byl rozdělit odpovědi do pevných šablon podle typu dotazu. Experiment ale narazil na skutečnou povahu problému. Nejčastější výtka k návrhům nebyla, že nemají správnou strukturu, ale že působí příliš obecně. Pevné šablony tuto vlastnost ještě zesilovaly.

Šablony jsem proto nepoužil.

Novější směr je střízlivější: krátké společné jádro s hlasem a bezpečnostními pravidly, jeden blok pro konkrétní typ e-mailu a několik podmíněných bloků, například pro zdravotní varování nebo konzultaci. Sestavení promptu je deterministické.

Prompt cache není jen přepínač

Dlouhý writer prompt byl také nejdražší částí celého běhu. Celkový vstup writeru se podle e-mailu pohyboval přibližně kolem 16–20 tisíc tokenů.

Na první pohled se nabízelo zapnout prompt cache. U podporovaných OpenAI modelů přes OpenRouter funguje automaticky. Jenže automatická cache ještě neznamená automatický zásah.

Původní system prompt obsahoval dynamické hodnoty rozeseté uvnitř statických instrukcí: nalezená fakta, odkazy, podobné e-maily, typ dotazu a další data se doplňovala přes řadu textových náhrad. Tím se začátek promptu mezi dvěma e-maily měnil a cache neměla dostatečně dlouhý identický prefix.

Řešením nebyla nová knihovna. Přesunul jsem statická pravidla do neměnného system promptu a veškerý obsah konkrétního e-mailu do následující user message.

Jeden zaznamenaný běh potom ukázal:

  • první writer volání: 17 041 vstupních tokenů, z toho 11 520 načtených z cache;
  • opakované writer volání: 17 085 tokenů, z toho 16 640 z cache.

To je přibližně 68 % a 97 % cachovaného vstupu. Není to totéž jako 68% nebo 97% úspora celé ceny — ta závisí na konkrétním modelu a poskytovateli. Je to ale jasný důkaz, že stabilní prefix funguje.

V současné verzi stojí modelová volání potřebná k vytvoření jednoho návrhu odpovědi v průměru přibližně 0,009 dolaru, tedy necelý jeden cent. E-mail odfiltrovaný už v triage stojí jen asi 0,0001 dolaru. Výpočet nezahrnuje provoz serveru a vektorové databáze.

OpenRouter doporučuje totéž: neměnný obsah umístit na začátek, proměnlivý kontext až za něj a skutečné zásahy ověřovat přes prompt_tokens_details.cached_tokens. OpenRouter: Prompt Caching

Největší lekce tedy nebyla „zapnul jsem cache“, ale „musel jsem změnit architekturu promptu, aby vůbec existovalo něco stabilního, co lze cachovat“.

Hřbitov chytrých zlepšení

Nejcennější část historie projektu je seznam věcí, které zněly správně, prošly implementací a nakonec byly vypnuté nebo vrácené.

ExperimentProč vypadal rozumněCo ukázala praxe
Planner, evidence contracts a další „production hardening“Více struktury mělo omezit halucinacePo několika fázích se kvalita zhoršila, včetně chybných tvrzení o platbách. Projekt se vrátil na starší větev Phase 5.
Přepsání writer promptu do čistší podobyMenší technický dluh měl zlepšit dodržování pravidelNovější writer podával horší výsledky, takže se obnovila starší, méně elegantní verze.
Šablony podle intentuStabilní dotazy si říkají o standardní odpovědiHlavní problém byla přílišná obecnost. Šablony ji ještě zvýraznily.
Analýza před writeremPrvní model připraví strategii, druhý podle ní napíše odpověďV malém A/B testu měla zapnutá varianta užitečnost 3,0 proti 3,25 bez ní, přidala zhruba 15 % nákladů a v jednom návrhu si vymyslela datum nákupu.
LLM critic po writeruDruhý model zkontroluje a opraví prvníVarianta s kritikem prošla 7 z 10 případů, základní varianta 8 z 10. Critic jednou ponechal jen podpis a jindy přidal nesouvisející prodejní URL.
Sémantické kontroly správnosti URLModel pozná, zda odkaz odpovídá účeluKontroly blokovaly i správné odkazy, například opětovné zaslání odkazu zákazníkovi, který produkt už koupil.

Tyto testy nejsou důkazem, že plánování nebo LLM kritici nefungují nikdy. Byly malé a vztahují se ke konkrétnímu systému.

Ukázaly ale, že v agentovi s povinnou lidskou kontrolou jejich přínos nevyvážil cenu a nové chybové stavy. Kontrolní model nebyl jen pojistka. Byl to další model schopný pokazit už správnou odpověď.

Úzká modelová volání ano, druhý autor ne

Výsledkem není systém bez modelových kontrol. Rozdíl je v jejich pravomoci.

Menší model je užitečný, když má vrátit úzký výsledek:

  • má se na e-mail odpovědět;
  • jaký je hlavní záměr;
  • které tři podklady jsou relevantní;
  • zda návrh vůbec odpovídá na otázku;
  • zda se má případ předat člověku.

Mnohem riskantnější je otázka: „Přepiš tuto odpověď tak, aby byla správnější.“ Model pak může doplnit něco, co ve vstupu nebylo, odstranit důležitou část nebo změnit správný odkaz.

Pevné záruky jsem proto postupně přesouval do kódu:

  • automatické a infrastrukturní odesílatele ignoruje pevný seznam pravidel;
  • uzavřená konverzace se znovu nerozvíjí;
  • nepovolené URL se odstraní nebo označí ke kontrole;
  • tvrzení o provedené či budoucí akci se kontrolují samostatně;
  • česká diakritika, únik slovenštiny a podpis mají vlastní validaci;
  • situace vyžadující zásah v jiném systému dostanou needs_review.

Model může navrhovat. Kód má garantovat. Člověk rozhoduje.

Nejvážnější chyba neměla nic společného s promptem

V březnu se po restartu občas ztratilo nebo zdvojilo zpracování Gmail notifikace. Startupová kontrola e-mailové schránky a nový webhook mohly současně zpracovat stejné vlákno a vytvořit dva koncepty.

Příčina nebyla v AI. Gmail history_id byl uložený jen v paměti procesu, souběžné úlohy k němu přistupovaly bez zámku a před vytvořením konceptu neexistovala poslední idempotentní kontrola.

Oprava proto vypadala jako běžné distribuované systémy:

  • trvale uložený monotónní kurzor;
  • asynchronní zámek při jeho změně;
  • claim konkrétního vlákna během zpracování;
  • kontrola štítku těsně před vytvořením konceptu;
  • stránkování Gmail API;
  • bezpečné zpracování opožděných notifikací.

Agent může perfektně rozumět e-mailu a přesto být nespolehlivý produkt, pokud dvakrát vytvoří stejný návrh. Idempotence je pro e-mailového agenta stejně důležitá jako prompt.

Nejdřív měřit, potom opravovat

Jiný problém vypadal jako ořezávání odpovědí modelem. Některé návrhy končily uprostřed věty nebo bez povinného podpisu. První hypotéza byla příliš nízký limit výstupních tokenů.

Do writeru jsem proto přidal záznam finish_reason, velikosti promptů a délky odpovědi a připravil malý diagnostický dataset.

Ve vzorku 24 reálných dvojic skončilo všech 24 generování s finish_reason=stop. Ani jeden případ nepotvrdil ukončení kvůli limitu délky. Dvacet dva návrhů ale selhalo na přísné kontrole podpisu: v jedenácti případech kvůli mezerám, v dalších jedenácti kvůli chybějící diakritice nebo jiné textové variantě.

Původní hypotéza se tedy v tomto vzorku nepotvrdila. Problém, který vypadal jako limit modelu, byl z velké části problém validace a formátování.

Podobně se z údajného kontextového limitu 13,7 tisíce tokenů nakonec vyklubal limit konkrétního účtu a kreditu u poskytovatele OpenRouter.

Obě situace mě naučily totéž: před architektonickou opravou potřebuji pozorovatelný signál, který potvrzuje skutečnou příčinu.

Eval skóre není realita

Pro testování jsem sestavil dataset skutečných dotazů spárovaných s historickými odpověďmi. Hlavní sada obsahovala 57 dvojic, menší dvacetipoložkové podmnožiny sloužily pro rychlejší iterace a další oddělené případy jako kontrolní sada.

Každý běh ukládá nejen finální návrh, ale také:

  • výstupy jednotlivých uzlů;
  • nalezené RAG bloky a jejich skóre;
  • vybrané odkazy a fakta;
  • verze promptů a modelů;
  • aktivní feature flagy;
  • spotřebu a cachované tokeny;
  • důvod opakování nebo předání ke kontrole.

LLM hodnotitel pomáhá rychle najít podezřelé případy, ale nemá poslední slovo. Souhrnné skóre může vypadat dobře, i když odpověď vynechá rozhodující odkaz, slíbí neprovedenou akci nebo elegantně zopakuje zastaralou informaci.

Při ladění proto postupuji od špatné věty zpět:

  1. Byla správná informace nalezená?
  2. Dostal ji správný typ odpovědi?
  3. Ignoroval ji writer?
  4. Přepsala nebo propustila ji kontrolní vrstva?

Každému selhání se snažím přiřadit jednu primární příčinu: retrieval, směrování, writer nebo kontrola. Teprve potom měním kód.

Feature flagy, které dnes zůstávají vypnuté, proto nevnímám jen jako nepořádek. Jsou záznamem experimentů a umožňují každou změnu porovnat i okamžitě vrátit.

Architektura, která nakonec zůstala

Po všech experimentech se systém ustálil na poměrně střízlivém rozdělení odpovědnosti:

  • fakta patří do spravované znalostní RAG databáze;
  • historické e-maily slouží hlavně jako příklad stylu;
  • úzké modelové kroky klasifikují, hledají a upozorňují;
  • jeden hlavní model píše;
  • tvrdá pravidla a jisté kontroly patří do kódu;
  • neznámý stav se označí ke kontrole, nevymyslí;
  • poslední rozhodnutí zůstává na člověku.

Viditelnou částí produktu je text e-mailu. Většina práce ale leží pod ním: aktualizace znalostí, integrita chunků, idempotence webhooků, měření tokenů, regresní testy, rollbacky a přesné vymezení pravomoci každého kroku.

Proto podle mě nestačí zaplatit si službu, nahrát několik stránek do RAG a říct, že máme e-mailového agenta. Tím vznikne dobré demo. Spolehlivý pomocník vzniká až opakovaným měřením na vlastních datech a ochotou odstranit i funkce, které vypadají architektonicky chytře.

Nejdůležitější lekce tohoto projektu nakonec není, jak do grafu přidat další AI uzel.

Je to schopnost dokázat, že tam nemá být.