Uvod
U Luxoftu, naši inovativni timovi neprestano pomeraju granice integracije veštačke inteligencije. Generativna AI i LLM-ovi (Large Language Models) su najmodernije tehnologije koje mogu transformisati trenutne pristupe razvoju aplikacija. Sa rastućim interesovanjem za AI, integracija LLM-ova u moderne aplikacije postala je ključna za automatizaciju zadataka koji su ranije zahtevali značajne resurse. Ovim člankom započinjemo našu istraživačku temu o automatizovanim aplikacijama zasnovanim na LLM-ovima, gde ćemo predstaviti relevantne koncepte, efikasne obrasce i neke zaključke. Ovog puta, govorićemo o upotrebi postojećih modela, dok će naredni članci govoriti o finom podešavanju i treniranju novih modela. Sve slike u ovom članku, osim ako nije drugačije naznačeno, kreirao je Aleksandr Seleznjov.
Opis problema
Primarni izazov za aplikacije zasnovane na LLM-ovima je dobijanje izlaza iz modela koji sam po sebi nije sposoban da ih proizvede. Naš zadatak ovde je poboljšati performanse modela, a time i kvalitet našeg izlaza.
Razumevanjem i primenom koncepata i obrazaca o kojima se ovde diskutuje, programeri mogu unaprediti sposobnosti aplikacija zasnovanih na LLM-ovima, i tako ih učiniti efikasnijim i robusnijim.
Ključni koncepti
U prvom delu ovog članka pokrićemo osnovne LLM koncepte koji su nam potrebni da bismo razumeli ono što sledi. Ako ste već upoznati sa osnovama ili ste dovoljno hrabri, slobodno pređite direktno na drugi deo o obrascima rešenja.
Kako funkcionišu Large Language Models?
Pre nego što nastavimo, evo dve važne stavke koje treba imati na umu:
- LLM-ovi i aplikacije zasnovane na LLM-ovima su dve različite stvari. Uobičajeno je, na primer, čuti da model GPT-4-Omni može pristupiti URL-ovima i pružiti detalje o sadržaju web stranice — ali to nije nužno tačno. Zapravo, aplikacija ChatGPT pristupa URL-u, dobija sadržaj i dodaje ga korisnikovom zahtevu koji zatim obrađuje njen bekend model (GPT-4-Omni). Ovo je zbog drugog koncepta:
- Zadatak LLM-a je samo da kompletira korisnikov unos (prompt). To radi dodajući jedan po jedan deo teksta (koji se naziva token), i ponavlja ovaj proces dok ne dostigne maksimalan broj tokena ili logičan kraj:
Model može generisati izlazne tokene zahvaljujući načinu na koji je treniran: bio je izložen rečenicama iz trening podataka (za GPT modele, sadržaj sa interneta) i imao je zadatak da generiše završetke. Rezultati se zatim porede sa stvarnim završecima, iako su se značajno razlikovali, unutrašnji parametri modela se prilagođavaju. Ovaj proces se ponavlja sve dok izlazi na kontrolnim unosima ne postanu slični kontrolnim izlazima.
Promptovi
Ulazni tekst koji se daje modelu da ga kompletira naziva se prompt.
Tokeni
Token je osnovna jedinica teksta s kojom model radi. Obično se tokeni posmatraju kao reči, ali to može varirati u zavisnosti od arhitekture modela, jezika unosa, trening podataka itd.
Na primer, evo kako GPT-3.5 tokenizuje engleske i hrvatske rečenice, prema platform.openai.com/tokenizer
Engleski:
Izvor: OpenAI Platform Tokenizer
Hrvatski:
Izvor: OpenAI Platform Tokenizer
Kao što možemo videti, ove rečenice imaju isto semantičko značenje, ali se odnos između tokena i karaktera između ova dva jezika značajno razlikuje. Broj ulaznih i izlaznih tokena obično koriste pružaoci LLM usluga kao osnovu za obračunavanje troškova. Takođe se koristi za ograničavanje opterećenja usluge sa limitom tokena po minutu.
Kompletiranje naspram chat kompletiranja
Do sada smo razgovarali o tome kako LLM-ovi kompletiraju unos koji korisnik pruži na osnovu sličnih rečenica iz trening podataka. Da bismo to ilustrovali, koristimo model „gpt-35-turbo-instruct" za kompletiranje jednostavnog prompta ("Hello, my name is Ale"):
Karakteri označeni zelenom bojom su izlaz modela.
Međutim, navikli smo da dobijamo „odgovore” od modela, što pruža iskustvo „razgovora sa modelom“. Hajde da koristimo model „gpt-35-turbo-16k" sa istim promptom:
Kao što možete videti, ovaj model generiše očekivaniji odgovor na isti prompt.
Zašto reaguje na ovaj način? Zato što je model fino podešen koristeći skup trening podataka koji se sastoji od blokova teksta u formatu zahtev-odgovor. Za model je, dakle, prirodno da tretira prompt kao pitanje, a izlaz kao odgovor na to pitanje. Takvi modeli su sposobni da održavaju duge dijaloge jer UI chat aplikacije šalje istoriju razgovora modelu za kompletiranje.
Neuspeh LLM-ova: Prekid podataka, halucinacije i nedoslednost
Postoje mnogi slučajevi gde današnji LLM-ovi ne uspevaju da pruže pouzdane i tačno frakcionisane odgovore. Problemi sa kojima se naš tim najčešće susretao prilikom razvoja aplikacija zasnovanih na LLM-ovima su prekid podataka, halucinacije i nedoslednost.
Prekid podataka
LLM-ovi generišu izlaze koristeći trening podatke koji ne uključuju informacije o događajima koji su se desili nakon što su podaci prikupljeni, pa se čini da model „ne zna“ za kasnije događaje. Poslednja tačka u kojoj su podaci za trening ažurirani naziva se prekid podataka.
Na primer, hajde da pitamo model „gpt-35-turbo-16k" o događaju koji se desio nakon prekida podataka:
Sličan problem se javlja kada zamolimo model da diskutuje o poverljivim korporativnim informacijama koje nisu bile predstavljene u trening skupu podataka, pa model ne može da generiše izlaz na osnovu tih informacija.
Halucinacije
Još jedno često primećeno ponašanje LLM-ova je generisanje činjenično netačnih izlaza, čak i kada su činjenice bile deo trening podataka. Ovo se često naziva halucinacijom.
Hajde da „prevarimo" model „gpt-35-turbo-16k" da generiše halucinaciju:
Prvi odgovor je halucinacija izazvana sugestivnom prirodom pitanja — referenca na „jedinog preživelog". Model je jednostavno generisao tekst na osnovu obrazaca iz trening podataka. Drugi je odgovor, međutim, pouzdaniji, jer je prompt bolje usklađen sa činjeničnim informacijama u trening setu.
Mnogo istraživanja je u toku kako bi se razumeo razlog pojave halucinacija i kako ih sprečiti. Nećemo sada ulaziti u detalje, ali ako razumete glavni koncept i razmislite o problemu, trebalo bi da budete u mogućnosti da bar smanjite broj halucinacija u vašim interakcijama sa modelom.
Nedoslednost
S obzirom na stohastičku prirodu izlaza LLM-ova, kada se određeni tipovi promptova ponove, model će generisati različite rezultate. Ovo dovodi do problema kada se izlaz koristi u automatizovanim scenarijima, poput razvoja aplikacija zasnovanih na GenAI.
Pogledajmo primer. Pitali smo model dva puta za bash skriptu. Drugi put, skripta je mnogo duža i sadrži neku vrstu provere, dok prva verzija ne sadrži te elemente.
Obrasci rešenja
Projekti modernizacije često uključuju zadatke konverzije koda. Naš tim koristi generativnu AI zasnovanu na LLM-ovima kako bi ubrzao ovaj proces i razvili smo nekoliko uspešnih rešenja. U ovom delu članka, prikazaćemo obrasce za prevazilaženje nekih od neuspeha LLM-ova koji su opisani iznad.
Važno je napomenuti da naš tim nije izmislio ove obrasce — samo smo otkrili koji su nam funkcionisali. Ako je potrebno, možete pronaći detaljnije teorijske opise ovih obrazaca ovde.
Demo aplikacija
Naši primeri ispod odnosiće se na ASM konvertor, aplikaciju srednje složenosti čiji je zadatak da pomogne u konverziji mainframe korisnih programa napisanih u asembleru u Linux Java CLI programu. Ovi korisni programi na mainframe-u su slični uobičajenim Linux korisnim programima (poput sort ili wget), ali su razvijeni za rešavanje specifičnih slučajeva upotrebe.
Specifikacije za ASM konvertor su:
Ulaz:
- Tekst programa u asembleru
- Potpuna specifikacija asembler jezika za platformu
- Stil rezultujućeg koda
- Rezultujuće biblioteke i moduli koda
Izlaz:
- Dokument koji pokriva poslovnu logiku, interfejse, rukovanje greškama itd.
- Test slučajevi sa ulaznim i očekivanim izlaznim podacima
- Java Maven projekat sa JUnit testovima koji se kompajlira i prolazi unit testove
Integracije:
- Azure OpenAI kao pozadinska LLM usluga
- CLI interfejs za korišćenje unutar automatizovanih scenarija
Evo osnovnog toka rada:
Konverzija se izvodi pomoću automatizovane pipeline kombinovane sa četiri nezavisna „softverska agenta”:
- Dokumentator (Documenter) — analizira HLASM kod; preuzima relevantne ASM specifikacije koristeći RAG; generiše zahteve, specifikacije i test slučajeve.
- Konvertor (Converter) — analizira ASM kod i dokumentaciju; generiše Java kod; osigurava da je Java kod moguće kompajlirati.
- Tester — analizira kod i dokumentaciju; generiše JUnit testove; osigurava da se testovi mogu kompajlirati.
- Debager (Debugger) — analizira Java, JUnit kod i dokumentaciju; izvršava JUnit testove; ispravljanjem Java koda i testova osigurava da svi testovi prolaze.
ASM konvertor je napisan u Python-u koristeći „Langchain” i „Langgraph” okvire. On je zatvorenog koda (zbog toga nema GitHub linka).
Sada ćemo pogledati neke obrasce rešenja sa primerima iz ASM konvertora.
Poboljšanje izlaza za pojedinačne promptove
Iz našeg iskustva, samo 10% LLM promtova sa naivnim pristupom „zahtev -> odgovor“ proizvodi prihvatljive rezultate. Međutim, poznavanje obrazaca za kreiranje odgovarajućih promtova značajno povećava kvalitet izlaza.
Sistemski promptovi
Sistemski prompt je deo prompta koji je skriven od korisnika. Često se koristi za kontrolu izlaza unutar chat aplikacija.
Na primer, ovde je poređenje izlaza za isti korisnički prompt bez sistemskog prompta (levo) i sa sistemskim promptom koji zahteva od modela da pomogne u kreiranju CV-a (desno):
U okviru ASM konvertera, svaki akter ima svoj sistemski prompt koji se koristi za pojednostavljenje prompta za specifične zadatke. Na primer, evo sistemskog prompta za ulogu dokumentatora:
Korišćenje ovog prompta kao posebnog sistemskog prompta znači da svi promptovi specifični za zadatke unutar uloge dokumentatora mogu izostaviti ovu informaciju i fokusirati se na sam zadatak.
Sistemski promptovi se takođe mogu koristiti za rešavanje problema sa pekidom podataka. Na primer, mogu se dodati informacije o domenima (koje model inače ne prepoznaje), a model će izabrati činjenice relevantne za korisnikov zahtev. Treba imati na umu da je veličina ovde ograničenje: prosečan članak sa Wikipedije može stati u sistemski prompt, ali knjiga neće.
Šabloni promptova
Šabloniranje promtova je tehnika koja pomaže u kreiranju promptova koji su potpuno razvijeni i testirani, ali i dalje imaju prostor za unos korisničkih podataka.
Unutar ASM konvertora, šabloni promptova se koriste za unos promenljivih podataka u model. Na primer, evo jednog od promptova za ulogu konvertora koji vrši inicijalnu translaciju:
Kao što možete videti, tokeni {assembler_docs} i {assembler_code} unutar prompta su rezervisana mesta koja će biti zamenjena stvarnim ASM kodom i ASM dokumentacijom.
Promptovanje sa nekoliko primera
Šablon sa nekoliko primera se koristi kada je potrebno da LLM stekne novu veštinu ili nauči specifičan šablon. U promptu se pruža nekoliko primera željenog izlaza.
U okviru ASM konvertora, promptovanje sa nekoliko primera se koristi da pojača upotrebu specifičnih biblioteka i stilova koda. Na primer, evo isečka iz jednog od prompta za ulogu Testera:
Kao što možete videti, pruženi su različiti primeri izlaza kako bi LLM mogao da ih koristi kao modele.
Lanac razmišljanja
Obrazac „lanac razmišljanja" koristi se kako bi LLM pratio određene puteve zaključivanja prilikom generisanja odgovora, tako što se pruža detaljan opis kako ulaz treba da bude konvertovan u željeni izlaz.
U ASM konvertora, lanac razmišljanja se koristi kako bi se primenili određeni stilovi konverzije. Na primer, evo isečka iz jednog od promptova za ulogu konvertora:
Prompt pruža i primer željenog izlaza i objašnjenje zašto je taj izlaz poželjan.
Podela velikog zadatka na više promptova
Obrasci iz sledeće grupe koriste se za razlaganje zadataka na nekoliko promptova i njihovo izvršavanje na organizovan i usklađen način. Za razliku od prethodnih, ovi obrasci često zahtevaju dodatne alate ili programiranje za implementaciju.
Možemo identifikovati dva podtipa:
- Lanac — Pokretanje više promptova jedan za drugim po unapred definisanom redosledu. Obradićemo dva primera: „više uloga“ i „samodoslednost“.
- Ciklus — Pokretanje promptova u lancu sa petljama, sa funkcijom koja određuje da li petlju treba napustiti. Ovi će biti obrađeni u sledećem članku.
Lanac: Više uloga
Često je zadatak veći ili složeniji nego što model može da obradi u okviru jednog prompta. Recimo da želimo da migriramo Java rešenje sa Java 6 na Java 17, uključujući:
- Ažuriranje koda klasa
- Ažuriranje unit testova
- Ažuriranje Java dokumentacije
Ovo je previše da bi se obradilo u jednom promptu. Ako pokušate, dobićete delimično generisan kod, halucinacije ili irelevantan izlaz.
Rešenje je da se zadatak podeli na nekoliko promptova na osnovu toga šta tačno treba da se uradi (uloga) i da se izvrši jedan po jedan, pri čemu se izlaz svake faze prosleđuje u sledeću:
U ASM konvertoru, obrasci sa više uloga koriste se kao celokupna arhitektura, kao i za povezivanje promptova zajedno.
Proces inicijalne konverzije unutar ASM konvertora je dobar primer: zadatak je podeljen na manje korake, što povećava tačnost izlaza.
Lanac: Samodoslednost
Ovaj obrazac pruža način za prevazilaženje nedoslednosti u izlazu LLM-a i smanjenje halucinacija. Svaki prompt ili lanac se izvršava nekoliko puta, a sličnost izlaza se upoređuje. Ako je više od polovine izlaza slično, smatra se da su tačni, i konačni izlaz se zasniva na njima.
Ovaj obrazac se koristi unutar uloge dokumentatora u ASM konvertoru kako bi se osiguralo da je dokumentacija programa ispravna:
U bloku za poređenje sličnosti, iteracija se smatra neuspešnom ako nijedna dva međuizlaza nisu slična. U tom slučaju, gornji kontrolni tok ponovo pokreće proces.
Zaključak
Pokazali smo osnove generisanja teksta pomoću LLM-a, uobičajene greške i različita rešenja za njihovo prevazilaženje. Takođe smo pogledali primere kako su obrasci rešenja implementirani u aplikaciji za konverziju koda. Ovaj pregled bi trebalo da vam pomogne da pokrenete sopstvene projekte ili unapredite postojeće. Srećno!
Ako želite da saznate više o Luxoft-u i našim timovima, posetite profil naše kompanije.