RAG dnes niekedy nemá najlepšiu povesť: ľudia si buď myslia, že je úplne triviálny (začať je ľahké, škálovať už menej), alebo že ho prekonali „agentné systémy“ (ktoré sa v mnohých prípadoch pri bližšom pohľade začnú veľmi rýchlo podobať na RAG…).
V tomto blogu si na jednom či dvoch praktických príkladoch ukážeme, ako riešime bežné výzvy, konkrétne:
Spracovanie kombinácie textových a číselných údajov a prečo pri nich naivný RAG zlyháva: kľúčové slová sa prekrývajú a čísla nemajú sémantický význam.
Prečo pomáha navrhovať embeddingy založené na súhrnoch: pre každý blok vytvorte krátky opisný súhrn a potom ho použite na embedding a vyhľadávanie.
Ako vytvárať kontextové súhrny: pridajte kontext nadradeného dokumentu, aby sa dali rozlíšiť podobne vyzerajúce štatistiky.
Kedy sa spoľahnúť na kód a modely Pydantic: ak záleží na doslovnom obsahu, pre vyššiu spoľahlivosť skombinujte vlastný kód alebo model Pydantic s volaniami LLM.
Základy
Systémy RAG slúžia ako základ rôznych riešení, od botov zákazníckej podpory až po interných znalostných asistentov.
Na pozadí zvyčajne prebiehajú tieto kroky:
Rozdelenie zdrojových dokumentov na bloky
Vloženie každého bloku do vektorového priestoru
Načítanie K najrelevantnejších blokov pri zadaní dopytu
Vygenerovanie odpovede na základe týchto blokov
Populárne nástroje ako LangChain, LlamaIndex či Filestore od OpenAI robia tieto kroky takmer triviálnymi. V reálnych dátových postupoch však narazíte aj na údaje, ktoré netvorí len súvislý text, a základný RAG si s nimi nemusí poradiť. V nasledujúcich častiach si ukážeme konkrétne príklady problémov s údajmi a s rastúcou zložitosťou budeme postupne rozvíjať riešenie.
Keď údaje netvorí len text (čo vôbec nie je nezvyčajné)
Pozrime sa na nasledujúci blok údajov z herného prostredia:
JSON
Embeddingy fungujú vďaka naučeným vzťahom medzi slovami založeným na sémantickom význame a gramatike. Vyššie uvedené údaje sú kombináciou textu a čísel, pričom mimo tohto konkrétneho kontextu nemajú čísla žiadny vzťah k slovám. Tento blok údajov by sme teda mohli opísať ako kombináciu pomerne opisných slov, za ktorými nasledujú náhodné čísla.
Ak by sme mali iba tento typ údajov, nebol by to problém. Stále by sme ich mohli načítať pomocou embeddingov niekoľkých dostupných opisných slov (alebo jednoducho použiť prevod textu na SQL). Čo však v prípade, ak sa tento blok skrýva medzi množstvom blokov súvislého textu, v ktorých sa vyskytujú rovnaké slová? Napríklad:
JSON
Teraz si predstavte, že chceme načítať odpoveď na otázku „What is the attack range with Draconic Ascension?” S veľkou pravdepodobnosťou sa nám nepodarí načítať požadovaný relevantný blok, pretože zanikne v množstve ďalších blokov s rovnakými kľúčovými slovami.
Hlavným problémom je, že tieto bloky údajov nevieme dobre rozlíšiť, hoci obsahujú rôzne typy informácií o rovnakej téme. Dali by sa tieto údaje nejako obohatiť či vylepšiť? Samozrejme:smile:
Obohaťte údaje ich zhrnutím – áno, čítate správne
Namiesto priameho vytvorenia embeddingu samotného bloku môžeme najprv vygenerovať súhrn opisujúci jeho obsah a následne vykonať embedding a vyhľadávanie podľa tohto súhrnu. Pri generovaní by sme naďalej používali pôvodné údaje prepojené so súhrnom.
Pre dva vyššie uvedené príklady blokov by sme teda vygenerovali napríklad takéto súhrny:
Štatistiky dosahu, rýchlosti a poškodenia útoku (predvolené aj so schopnosťou Draconic Ascension).
Opis a podrobnosti schopnosti Draconic Ascension vrátane podmienok aktivácie, vizuálnych efektov a príbehového pozadia.
Následne upravíme aj dopyt tak, aby bol „zosúladený“ so súhrnom. Napríklad otázku „What is the attack range with Draconic Ascension?” by sme zmenili na „What is the statistics of attack range with Draconic Ascension?” Je to mimoriadne dôležité, keď dopyt zadávajú používatelia mimo technických odborov v ~~„neformálnom“~~ bežnom ľudskom jazyku. Koniec koncov nemusia vedieť ani riešiť, ako RAG funguje a ako maximalizovať presnosť a úplnosť výsledkov.


Nevytrhávajte veci z kontextu (platí to všeobecne aj v živote)
Ďalším scenárom je spracovanie množstva rovnako vyzerajúcich blokov údajov, ako sú tieto:
Plain Text
Ak by sme pokračovali rovnakým spôsobom, predstavte si otázku „what is character X’s attack range?” Pri práve vytvorených súhrnoch by sme sa spoliehali na šťastie a hádali, pretože aj ony by vyzerali veľmi podobne. Ako ich teda môžeme rozlíšiť?
Jednoduchou odpoveďou je doplniť kontext. Do bloku údajov môžeme jednoducho pridať odkaz na nadradený dokument, v tomto prípade napríklad {”character”: “X”}. Tak dokážeme presne načítať správne údaje o postave X, aj keď máme rovnaké údaje o postavách Y a Z.
Lepším a všeobecnejšie použiteľným riešením je však vytvoriť kontextový súhrn bloku. To znamená, že namiesto súhrnu iba samotného bloku údajov môžeme zadať nadradený dokument aj daný blok a vytvoriť všeobecný kontextový súhrn. V ňom uvedieme, ako blok zapadá do nadradeného dokumentu, napríklad:
Tento blok poskytuje podrobné štatistiky … pre postavu X. Do celého dokumentu zapadá tým, že ukazuje silnú stránku postavy X v rýchlosti útoku…
Tento blok poskytuje podrobné štatistiky … pre postavu Y. Do celého dokumentu zapadá tým, že ukazuje zvýšené štatistiky postavy Y pri použití jej špeciálnej schopnosti…
Tento blok poskytuje podrobné štatistiky … pre postavu Z. Do celého dokumentu zapadá tým, že ukazuje štatistiky postavy Z, vďaka ktorým sa výborne hodí na úlohu tanka v tímových zápasoch…
Táto metóda (čiastočne inšpirovaná spoločnosťou Anthropic) sa môže zdať pre uvedený príklad prehnaná. Je však veľmi účinná pri blokoch, ktoré by sa dali „mimo kontextu“ nesprávne pochopiť. Navyše ponúka jednotný prístup pre všetky bloky a zachováva prehľadný technický postup.


Keď musíte byť ~~posadnutí kontrolou~~ dôslední
Údaje zvyčajne získavame v celku a pre systém RAG ich rozdeľujeme na bloky. V tomto príklade ukážeme trochu inú situáciu: údaje sú rozdelené na bloky, no nevhodne. Ide o náhodné úseky jedného logického bloku, ktoré treba opäť spojiť. Logický blok je časť obsahu, ktorá prirodzene patrí k sebe, napríklad podsekcia dokumentu alebo ucelený odsek.


Pri prvom pokuse sme všetky údaje odovzdali v jednom volaní LLM a požiadali ho, aby ich podľa vlastného uváženia zoskupil a vrátil zoskupený obsah. LLM by to mal zvládnuť veľmi dobre, však? Áno aj nie.
Aj pri viacerých iných príležitostiach sme zistili, že LLM majú sklon uľahčovať si prácu a nie sú spoľahlivé, ak požadujete úplný a presný obsah, najmä pri dlhom kontexte. A je to úplne pochopiteľné. Pre tento konkrétny prípad použitia to však bola zásadná prekážka, pretože sme potrebovali presný obsah slovo za slovom – bez súhrnov a bez vynechania akejkoľvek časti originálu. Nesmieme vynechať žiadny detail.
Pozitívom, samozrejme, bolo, že LLM výborne porozumel významu a štruktúre rozdelených blokov. Teda pokiaľ neodmietol presne zopakovať celý obsah. Dočerta:/
Ako teda využiť silné stránky LLM a zároveň sa vyhnúť oblastiam, v ktorých nie je spoľahlivý? Obrátili sme sa na starého dobrého priateľa – kód (rozumej vlastnú funkciu v Pythone). A na maximálne jednoduchý model Pydantic. Riešenie vyzerá takto:
Postupne prechádzať sekcie a pritom udržiavať aktuálny logický blok
Pri každej sekcii sa opýtať LLM: Patrí táto sekcia do aktuálneho logického bloku? Odpoveď musí byť áno alebo nie (podľa modelu Pydantic).
Ak áno, pridať sekciu k bloku. Ak nie, uzavrieť a uložiť dokončený aktuálny logický blok a danou sekciou začať nový.


V porovnaní s jediným spracovaním celého obsahu síce použijeme o niečo viac tokenov, no pri tomto konkrétnom použití, kde je najvyššou prioritou zachovanie presného obsahu, sa malé dodatočné náklady rozhodne oplatili.
Ide o veľmi jednoduché riešenie, ktoré sa však riadi dôležitou zásadou: keď potrebujeme dôslednosť, nespoliehame sa výlučne na LLM, pretože sú zo svojej podstaty pravdepodobnostné.
Vlastný kód, funkcie a modely Pydantic umožňujú dosiahnuť predvídateľné a spoľahlivé výsledky a zároveň naplno využiť schopnosti LLM.
Tvorba riešenia generatívnej AI je rovnako technickou výzvou ako výzvou v oblasti AI. Dúfame, že vás tieto príklady inšpirovali pustiť sa do riešenia vlastných jedinečných výziev. Ak si chcete prečítať viac o technicky orientovaných riešeniach generatívnej AI, pozrite si náš blogový príspevok o návrhu agentného systému založeného na smerovaní.