Glavna navigacija

Praktični primjeri prilagođenih RAG rješenja

Primjeri iz prakse pokazuju kako prilagođeni sistemi generisanja potpomognutog pronalaženjem rješavaju složene probleme poslovnog znanja.

RAG je danas ponekad na lošem glasu, bilo zato što ga ljudi smatraju sasvim jednostavnim (jeste u početku, ali ne i pri skaliranju) ili zato što misle da su ga nadmašili „agentski sistemi“ (koji, čim zagrebete ispod površine, često vrlo brzo počnu ličiti na RAG...)

Ovaj blog donosi jedan ili dva praktična primjera koji pokazuju kako rješavamo neke uobičajene izazove, konkretno:

  • Obrada kombinacije tekstualnih i numeričkih podataka i zašto ona zbunjuje naivni RAG: ključne riječi se preklapaju, a brojevi nemaju semantičko značenje.

  • Zašto pomaže dizajn ugrađivanja zasnovan na sažetku: za svaki dio generišite kratak opisni sažetak, a zatim ugrađivanje i pretraživanje obavljajte na tom sažetku.

  • Kako generisati kontekstualne sažetke: uključite kontekst nadređenog dokumenta kako bi statistike sličnog oblika ostale jasno razgraničene.

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

Izrada prilagođenih RAG rješenja

Osnove

RAG sistemi pokreću sve, od botova za podršku do internih asistenata za znanje.

U pozadini obično radite sljedeće:

  1. Podijelite izvorne dokumente na dijelove

  2. Ugradite svaki dio u vektorski prostor

  3. Pronađite K najrelevantnijih dijelova u trenutku upita

  4. Generišite odgovor zasnovan na tim dijelovima

Popularni alati — LangChain, LlamaIndex i OpenAI Filestore — čine te korake gotovo trivijalnim. Međutim, u stvarnim procesima naići ćete na podatke koji nisu samo gusti tekst, a osnovni RAG može imati poteškoća s njima. U narednim odjeljcima prikazat ćemo konkretne primjere problema s podacima i postepeno razvijati rješenje kako se složenost bude povećavala.

Kada stvari postanu složenije

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

Razmotrite sljedeći dio podataka u kontekstu 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, } }} 

Ugrađivanja funkcionišu zahvaljujući naučenim odnosima među riječima, zasnovanim na semantičkom značenju i gramatici. U gornjim podacima pomiješani su tekst i brojevi, pri čemu izvan ovog konkretnog konteksta brojevi nemaju nikakvu vezu s riječima. Zato možemo reći da je ovaj dio podataka uglavnom kombinacija donekle opisnih riječi iza kojih slijede nasumični brojevi.

To zapravo ne bi bio problem kada bi ovo bila jedina vrsta podataka koju imamo, jer bismo ih i dalje mogli pronaći pomoću ugrađivanja nekoliko dostupnih opisnih riječi (ili jednostavno koristiti pretvaranje teksta u SQL). Ali šta ako je ovaj dio zakopan među mnoštvom dijelova gustog teksta u kojima se pojavljuju iste riječi? Naprimjer:

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 pronaći odgovor na pitanje „What is the attack range with Draconic Ascension?“Vrlo vjerovatno nećemo pronaći željeni relevantni dio jer je izgubljen u šumu drugih dijelova koji sadrže iste ključne riječi.

Osnovni je problem to što ne možemo dobro razlikovati ove dijelove podataka, iako sadrže različite vrste informacija o istoj temi. Možemo li to nekako obogatiti ili poboljšati? Naravno da možemo:smile:

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

Umjesto da direktno ugradimo sam dio, prvo možemo generisati sažetak koji opisuje sadržaj podataka, a zatim ugrađivanje i pronalaženje zasnovati na tom sažetku. U koraku generisanja i dalje bismo koristili izvorne podatke povezane sa sažetkom.

Tako bismo za dva prethodno prikazana primjera generisali sažetke poput ovih:

  1. Attack statistics of range, speed, and damage (default and with Draconic Ascension).

  2. Description and details of the Draconic Ascension, including activation conditions, visual effects, and lore.

Zatim proširujemo i upit kako bismo ga „uskladili“ sa sažetkom. Naprimjer, upit „What is the attack range with Draconic Ascension?“pretvorili bismo u „What is the statistics of attack range with Draconic Ascension?“To je naročito važno kada upit za pronalaženje dolazi od korisnika koji nisu iz tehničkih oblasti i pitaju ~~„slobodnim stilom“~~ običnim ljudskim jezikom, jer nije njihova obaveza ni briga da znaju kako RAG funkcioniše radi postizanja maksimalne preciznosti i odziva.

Dijagram koji prikazuje kada stvari postaju složenije.

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

Sljedeći scenarij odnosi se na obradu mnoštva dijelova podataka koji izgledaju isto, 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 pitanje „what is character X’s attack range?“S ovim upravo generisanim sažecima igrali bismo igru pogađanja zasnovanu na sreći jer bi i oni izgledali vrlo slično. Kako ih onda možemo razlikovati?

Jednostavan odgovor glasi: pružite kontekst. U dio podataka mogli bismo jednostavno uključiti referencu na njegov nadređeni dokument, npr. {”character”: “X”} u ovom slučaju. Tako bismo sada mogli precizno pronaći odgovarajuće podatke za lik X, čak i kada imamo iste podatke za likove Y i Z.

Ipak, bolji i općenitije primjenjiv pristup bio bi generisanje kontekstualnog sažetka dijela. Drugim riječima, umjesto generisanja sažetka samo za sam dio podataka, mogli bismo proslijediti i njegov nadređeni dokument i dio kako bismo generisali opći kontekstualni sažetak. U njemu bismo objasnili kako se taj dio uklapa u nadređeni dokument, naprimjer:

  1. This chunk provides detailed statistics of … for character X. The chunk fits into the full document by showing X’s strength in attack speed…

  2. This chunk provides detailed statistics of … for character Y. The chunk fits into the full document by showing Y’s boosted stats with her special ability…

  3. This chunk provides detailed statistics of … for character Z. The chunk fits into the full document by showing Z’s stats that’s well suited as a tank in team matches…

Ova metoda (djelimično inspirisana Anthropicom) možda djeluje pretjerano za prethodni primjer, ali je vrlo učinkovita za dijelove koji bi se mogli pogrešno protumačiti „izvan konteksta“. Uz to pruža jedinstven pristup primjenjiv na sve dijelove i održava uredan inženjerski proces.

Dijagram koji prikazuje kada stvari postaju složenije.

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

Podatke obično dobijamo u cjelovitim jedinicama, a zatim ih dijelimo na manje dijelove za RAG sistem. U ovom primjeru prikazujemo nešto drugačije — podatke podijeljene na loše dijelove. To su nasumični odlomci jednog logičkog dijela koje zapravo treba ponovo grupisati. Logički dio je sadržaj koji prirodno pripada zajedno, poput pododjeljka dokumenta ili smislenog odlomka.

Dijagram koji prikazuje kada stvari postaju složenije.

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

Ustanovili smo, i u nekoliko drugih slučajeva, da LLM-ovi često rade površno i nisu pouzdani kada tražite potpun i tačan sadržaj, naročito ako je kontekst dug. Što je sasvim razumljivo. Ali to je bilo neprihvatljivo za ovaj konkretan slučaj jer nam je sadržaj zaista potreban doslovno, riječ po riječ — bez sažetaka i bez izostavljanja ijednog dijela izvornog sadržaja. Ne smijemo propustiti nijedan detalj.

Naravno, pozitivna strana bila je to što je odlično razumio semantiku i strukturu razlomljenih dijelova. Samo kada ne odbije doslovno ponoviti tačan sadržaj. Dođavola:/

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

  • Prolazite kroz odjeljke uz održavanje trenutnog logičkog dijela

  • Za svaki odjeljak pitajte LLM: pripada li ovaj odjeljak trenutnom logičkom dijelu? Odgovor mora biti da ili ne (u skladu s Pydantic modelom).

  • Ako je odgovor da, dodajte odjeljak tom dijelu. Ako je odgovor ne, vratite trenutni logički dio jer je završen, a zatim započnite novi s tim odjeljkom.

Dijagram koji prikazuje kada stvari postaju složenije.

Naravno, ovdje koristimo nešto više tokena nego pri jednom prolasku kroz cjelokupan sadržaj, ali za ovaj slučaj, u kojem je očuvanje tačnog sadržaja glavni prioritet, taj mali dodatni trošak itekako se isplatio.

Ovo je vrlo jednostavno rješenje, ali slijedi važan princip: 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 koristiti za postizanje predvidljivog i pouzdanog rezultata, uz istovremeno iskorištavanje mogućnosti LLM-ova.

Zaključak

Izrada rješenja generativne umjetne inteligencije podjednako je inženjerski izazov koliko i izazov umjetne inteligencije. Nadamo se da su vas ovi primjeri nadahnuli da se uhvatite ukoštac s vlastitim jedinstvenim izazovima. Ako želite pročitati više o inženjerski usmjerenim rješenjima generativne umjetne inteligencije, pogledajte naš blog o dizajnu agentskih sistema zasnovanom na usmjerivaču.

Autor

Cynthia Yu