Glavna navigacija

Praktični primjeri prilagođenih RAG rješenja

Primjeri iz stvarnog svijeta pokazuju kako prilagođeni RAG sustavi rješavaju složene probleme upravljanja znanjem u tvrtkama.

RAG je danas ponekad na lošem glasu: neki ga smatraju posve jednostavnim (lako ga je pokrenuti, ali ne i skalirati), a drugi misle da su ga nadmašili „agentski sustavi” (koji u mnogim slučajevima, čim zagrebete ispod površine, vrlo brzo počnu nalikovati RAG-u...)

U ovom blogu donosimo jedan ili dva razrađena primjera kako bismo pokazali kako rješavamo neke česte izazove, odnosno:

  • Obrada mješovitih tekstualnih i brojčanih podataka te zašto oni zbunjuju naivni RAG: ključne se riječi preklapaju, a brojevi nemaju semantičko značenje.

  • Zašto pomaže dizajn u kojem sažeci imaju prednost pri ugradnji: generirajte kratak opisni sažetak za svaki isječak, a zatim ugrađujte i pretražujte taj sažetak.

  • Kako generirati kontekstualne sažetke: uključite kontekst nadređenog dokumenta kako bi se razlikovali statistički podaci slične strukture.

  • Kada se osloniti na kôd i Pydantic modele: kada je doslovan sadržaj važan, radi pouzdanosti kombinirajte prilagođeni kôd i/ili Pydantic model s pozivima LLM-u.

Izrada prilagođenih RAG rješenja

Osnove

RAG sustavi pokreću sve, od botova za podršku do internih pomoćnika za znanje.

U pozadini obično radite sljedeće:

  1. Podijelite izvorne dokumente na isječke

  2. Ugradite svaki isječak u vektorski prostor

  3. Dohvatite K najrelevantnijih isječaka u trenutku upita

  4. Generirajte odgovor utemeljen na tim isječcima

Popularni alati — LangChain, LlamaIndex i Filestore tvrtke OpenAI — čine te korake gotovo trivijalnima. No u stvarnim ćete procesima naići na podatke koji nisu samo gusti tekst, a osnovni RAG s njima se može teško nositi. U sljedećim odjeljcima prikazat ćemo konkretne primjere izazova s podacima i postupno nadograđivati rješenje kako složenost bude rasla.

Kad stvari postanu složenije

  1. Kada vaši podaci nisu samo tekst (što zapravo nije rijetkost)

Razmotrite sljedeći isječak podataka iz konteksta videoigre:

JSON

{ "Attack": { "Range": { "default": 5, "with_Draconic_Ascension": 5.5, }, "Speed": { "default": "2 seconds", "with_Draconic_Ascension": "2.2 seconds", }, "Damage": { "default": 10, "with_Draconic_Ascension": 12, } }} 

Ugradnje funkcioniraju zahvaljujući naučenim odnosima među riječima, koji proizlaze iz semantičkog značenja i gramatike. U prethodnim podacima pomiješani su tekst i brojevi, pri čemu izvan tog konkretnog konteksta brojevi nisu povezani s riječima. Stoga bismo mogli reći da je taj isječak podataka uglavnom spoj koliko-toliko opisnih riječi iza kojih slijede nasumični brojevi.

To zapravo ne bi bio problem kada bi to bila jedina vrsta podataka koju imamo jer bismo ih i dalje mogli dohvatiti pomoću ugradnji nekoliko dostupnih opisnih riječi (ili jednostavno upotrijebiti pretvorbu teksta u SQL). No što ako je taj isječak skriven među mnoštvom tekstualno gustih isječaka u kojima se također pojavljuju te riječi? Na primjer:

JSON

{"Draconic_Ascension": { "description": "Transform into dragon form. Attack range and speed are increased by 10%, damage is boosted by 20%.", "details": { "activation_conditions": "Can only be activated when HP is below 50%or Fury meter is full.", "visual_effects": "Wings unfurl, scales shimmer with embers, voice lines change to echoing growls.", "lore": "An ancient bloodline awakens. The bearer of the mark channels the soul of the last Flamewing Wyrm, becoming a living storm of fire and fury." } }}

Sada zamislite da želimo dohvatiti odgovor na pitanje „What is the attack range with Draconic Ascension?” Vrlo vjerojatno nećemo uspjeti dohvatiti željeni isječak jer je skriven u šumu drugih isječaka s istim ključnim riječima.

Temeljni je problem to što te isječke podataka ne možemo dobro razlikovati, iako sadržavaju različite vrste informacija o istoj temi. Možemo li te podatke nekako obogatiti ili poboljšati? Naravno da možemo :smile:

  1. Obogatite podatke njihovim sažimanjem — da, dobro ste pročitali

Umjesto izravne ugradnje samog isječka, možemo prvo generirati sažetak koji opisuje podatke, a zatim ugraditi i pretraživati taj sažetak. U koraku generiranja i dalje bismo upotrebljavali izvorne podatke povezane sa sažetkom.

Za dva prethodno prikazana isječka generirali bismo sažetke poput ovih:

  1. Statistički podaci o dometu, brzini i šteti napada (zadano i uz Draconic Ascension).

  2. Opis i pojedinosti sposobnosti Draconic Ascension, uključujući uvjete aktivacije, vizualne efekte i pozadinsku priču.

Zatim proširujemo i upit kako bismo ga „uskladili” sa sažetkom. Primjerice, upit „What is the attack range with Draconic Ascension?” pretvorili bismo u „What is the statistics of attack range with Draconic Ascension?” To je osobito važno kada upite za dohvaćanje postavljaju korisnici izvan tehničkih područja, i to ~~„slobodnim stilom”~~ običnim ljudskim jezikom. Naposljetku, ne moraju znati niti ih treba zanimati kako RAG funkcionira da bi se maksimalno povećale preciznost i odziv.

Dijagram koji prikazuje kada stvari postanu složenije.

  1. Ne izvlačite stvari iz konteksta (što općenito vrijedi i u životu)

Sljedeći je scenarij rad s mnoštvom isječaka podataka koji izgledaju jednako, poput ovih u nastavku:

Plain Text

# Chunk one{ "Attack": { "Range": { "default": 9, }, ... }}# Chunk two{ "Attack": { "Range": { "default": 6, }, ... }}# Chunk three{ "Attack": { "Range": { "default": 7, }, ... }}

Ako zadržimo isti pristup, zamislite upit „what is character X’s attack range?” S upravo generiranim sažecima igrali bismo igru pogađanja utemeljenu na sreći jer bi i oni izgledali vrlo slično. Kako ih onda možemo razlikovati?

Jednostavan je odgovor: pružite kontekst. U isječak podataka možemo jednostavno uključiti referencu na njegov nadređeni dokument, primjerice {”character”: “X”} u ovom slučaju. Tako bismo sada mogli precizno dohvatiti prave podatke za Lik X čak i kada imamo iste podatke za Lik Y i Lik Z.

Bolji i općenitije primjenjiv pristup bio bi generiranje kontekstualnog sažetka isječka. Odnosno, umjesto generiranja sažetka samo za isječak podataka, mogli bismo proslijediti i njegov nadređeni dokument i isječak kako bismo generirali opći kontekstualni sažetak. U njemu bismo objasnili kako se isječak uklapa u nadređeni dokument, primjerice:

  1. Ovaj isječak pruža detaljne statističke podatke o... za Lik X. Isječak se uklapa u cijeli dokument tako što prikazuje snagu Lika X u brzini napada...

  2. Ovaj isječak pruža detaljne statističke podatke o... za Lik Y. Isječak se uklapa u cijeli dokument tako što prikazuje poboljšane statistike Lika Y uz njezinu posebnu sposobnost...

  3. Ovaj isječak pruža detaljne statističke podatke o... za Lik Z. Isječak se uklapa u cijeli dokument tako što prikazuje statistike Lika Z zbog kojih je vrlo prikladan za ulogu tenka u timskim mečevima...

Ova metoda (djelomično nadahnuta Anthropicovim pristupom) možda se čini pretjeranom za prethodni primjer, no vrlo je učinkovita za isječke koji bi se mogli pogrešno protumačiti „izvan konteksta”. Osim toga, pruža jedinstven pristup primjenjiv na sve isječke i održava uredan inženjerski proces.

Dijagram koji prikazuje kada stvari postanu složenije.

  1. Kada morate biti ~~opsjednuti kontrolom~~ rigorozni

Podatke obično dobivamo kao cjeline i dijelimo ih na isječke za RAG sustav. U ovom primjeru prikazujemo nešto malo drukčije: podatke koji su podijeljeni na loše isječke. To su nasumični dijelovi logičke cjeline koje zapravo treba ponovno grupirati. Logički isječak znači dio sadržaja koji prirodno pripada cjelini, poput pododjeljka dokumenta ili smislenog odlomka.

Dijagram koji prikazuje kada stvari postanu složenije.

U prvom pokušaju sve te podatke šaljemo LLM-u i tražimo da ih grupira kako smatra prikladnim, a zatim vrati grupirani sadržaj. LLM bi u tome trebao biti prilično dobar, zar ne? Pa, i da i ne.

Utvrdili smo, kao i u nekoliko drugih prilika, da se LLM-ovi znaju ponašati lijeno i nisu pouzdani kada tražite potpun i točan sadržaj, osobito ako je kontekst dug. Što je posve razumljivo. No za ovaj konkretan slučaj to je bilo neprihvatljivo jer nam je sadržaj potreban doslovno, riječ po riječ — bez sažimanja i bez izostavljanja ijednog dijela izvornog sadržaja. Ne smijemo propustiti nijedan detalj.

Naravno, pozitivna je strana bila to što je izvrsno razumio semantiku i strukturu razlomljenih isječaka. Ali samo ako ne odbije doslovno ponoviti sadržaj. Kvragu :/

Kako onda iskoristiti ono u čemu je LLM dobar, a izbjeći ono u čemu nije pouzdan? Okrenuli smo se dobrom starom prijatelju — kodu (odnosno prilagođenoj Python funkciji). I Pydantic modelu koji „ne može biti jednostavniji”. Evo rješenja:

  • Prolazite kroz odjeljke i pritom održavajte trenutačni logički isječak

  • Za svaki odjeljak pitajte LLM: pripada li ovaj odjeljak trenutačnom logičkom isječku? Neka odgovori s da ili ne (prema Pydantic modelu).

  • Ako da, pridružite odjeljak isječku. Ako ne, zabilježite dovršeni trenutačni logički isječak, a zatim s tim odjeljkom započnite novi.

Dijagram koji prikazuje kada stvari postanu složenije.

Naravno, tako trošimo nešto više tokena nego jednim prolaskom kroz cijeli sadržaj, ali za ovaj konkretan slučaj, u kojem je očuvanje točnog sadržaja najvažnije, manji dodatni trošak itekako se isplatio.

To je vrlo jednostavno rješenje, ali slijedi važno načelo: kada je potrebna rigoroznost, ne želimo se oslanjati isključivo na LLM-ove jer su oni ipak probabilistički.

Prilagođeni kôd i funkcije te Pydantic modeli mogu se upotrijebiti za predvidljiv i pouzdan rezultat, a da se pritom i dalje iskoriste mogućnosti LLM-ova.

Zaključak

Izrada rješenja generativne umjetne inteligencije podjednako je inženjerski izazov i izazov umjetne inteligencije. Nadamo se da su vas ovi primjeri nadahnuli da se uhvatite ukoštac s vlastitim jedinstvenim izazovima. Želite li saznati više o inženjerski usmjerenim rješenjima generativne umjetne inteligencije, pročitajte naš blog o dizajnu agentskih sustava utemeljenih na usmjerivaču.

Autor

Cynthia Yu