Inteligența Artificială revoluționează modul în care interacționăm cu tehnologia. Ușurința de utilizare și accesibilitatea transformă treptat o multitudine de domenii, îmbunătățirea aplicațiilor fiind una dintre direcții. Unele dintre exemple sunt îmbunătățirea experienței clienților și identificarea de inovații în crearea de conținut.
În acest studiu de caz, am încercat să transform o aplicație simplă care prezintă rețete pe baza ingredientelor și a numelui rețetelor dintr-o bază de date PostgreSQL. În acest sens, am integrat o componentă Large Language Model (LLM), Retrieval Augmented Generation (RAG) și un model customizat.
Am pornit de la o aplicație creată cu NextJS și care avea o bază de date Supabase. Aplicația în sine este destul de simplă: o pagină ce permite căutarea de rețete pe bază de ingrediente și de nume de rețetă; o pagină ce expune rezultatele, unde fiecare rezultat poate fi accesat ca o nouă pagină ce deschide rețeta și detaliile acesteia.
Am dorit să îmbunătățim funcționalitățile acestei aplicații prin intermediul LLM, astfel încât criteriile de căutare să fie mai largi și personalizate pentru utilizatori. În acest sens, am folosit frameworkul Ollama și RAG, în loc de a face căutare bazată doar pe interogări simple.
În general, componentele LLM sunt potrivite pentru multiple activități de procesare a limbajului natural. Răspunsurile generate de acestea pot fi precise, corecte, cu alte cuvinte exact ceea ce trebuie unui utilizator. Totuși, aceasta nu este o regulă.
Sunt situații când o întrebare adresată către ChatGPT produce un rezultat ce pare incorect sau înșelător, în ciuda nivelului aparent de încredere pe care îl afișează modelul. Verificarea ulterioară a informației arată că LLM-ul a "mințit". Acest fenomen se numește halucinație. Modelele cu utilizare generală sunt pre-antrenate pe volume mari de date din surse variate, dar sunt insuficient de fiabile în cazuri unde este nevoie de informație de ultimă oră, informație specifică unui domeniu, verificare factuală de informații etc.
Retrieval Augmented Generation are drept obiectiv înlăturarea acestei probleme, combinând generarea avansată de text a unui LLM cu o operație de căutare semantică menită să extragă informație precisă și relevantă contextual. Aceasta funcționează astfel:
Utilizatorul scrie și trimite o interogare care este trimisă mai departe unui LLM embedded.
Modelul transformă interogarea într-un "vector embedded" care este comparat cu vectorii altor date de intrare din baza de date cu vectori.
Cele mai relevante rezultate din baza de date cu vectori sunt transmise mai departe unui LLM generativ care le folosește ca parte a promptului pentru a genera rezultatul final.
Pentru a implementa acest algoritm, trebuie să înțelegem ce este un embedding. Componentele embedding sunt reprezentări avansate ale unor vectori. Acești vectori înțeleg sensul unui cuvânt plasând cuvintele într-un spațiu vectorial continuu. Cuvintele cu sensuri similare sunt reprezentate de vectori care sunt apropiați în acest spațiu. Modelele embedding folosesc "componente embedding contextuale" ce țin cont de mecanismul de atenție al arhitecturii unui Transformer pentru a distinge același cuvânt în contexte diferite (în funcție de cuvintele ce se află în jurul unuia și aceluiași cuvânt). Prin urmare, unul și același cuvânt poate avea embeddinguri diferite bazate pe context. Cele mai folosite embeddinguri contextuale sunt ELMo și BERT.
Pentru a descărca un LLM, trebuie să instalăm Ollama, o platformă gratuită ce ne permite să rulăm LLM-uri pe mașini locale. Pentru acest studiu de caz, am folosit modelul embedding all-minilm și modelul llava-llama3 pentru generare de text și interpretare de imagini.
Există mai multe feluri de face acest lucru, dar cel mai simplu poate fi din terminal, rulând:
ollama run all-minilm
ollama run llava-llama3
Dacă modelele nu se află pe mașina locală, Ollama le va aduce pentru noi.
Customizând modelul llava-llama3, putem să îi îmbunătățim performanța. De exemplu, putem seta un mesaj detaliat de sistem:
/set system "You are an expert in food, culinary arts, and gastronomy. Your primary focus is to provide detailed, accurate descriptions of food items…"
Este important de remarcat că, pe măsură ce este mai detaliat mesajul de sistem, pe atât mai bună va fi generarea de text. Rezultatul modelului se poate îmbunătăți semnificativ dacă definim rolul, criteriile generale, studiile de caz concrete, tonul, stilul și câteva scenarii pe post de exemple.
Mai departe, putem salva modelul nostru customizat (numit foodie) cu:
/save foodie
Cel mai important detaliu în selectarea unei baze de date este ca aceasta să ofere suport pentru vectori. Supabase oferă acest lucru prin intermediul pgvector. În plus, trebuie să adăugăm o coloană ce include vectorul embedded pentru fiecare rețetă. Valoarea embedded trebuie să fie cât mai detaliată pe cât posibil.
Din moment ce Ollama nu oferă suport pentru embedding de tip imagine, dacă includem imagini, trebuie să cerem modelului să o descrie și să includă acea descriere alături de elementele căutate.
messageToEmbed = message;
…
if (image) {
const arrayBuffer = await image.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const images = [uint8Array];
const generateResponse = await ollama.generate({
model: "foodie",
prompt:
"Try to guess the food on this image. Do not
detail the plating, aesthetics, focus only on
the food and what it could be made of.",
images,
});
messageToEmbed += " " +
generateResponse.response;
}
Rezultatul, adică messageToEmbed, va fi inclus de modelul all-minilm:
try {
const embeddingsResponse = await ollama
.embeddings({
model: "all-minilm",
prompt: messageToEmbed,
});
const embeddedMessage = embeddingsResponse.embedding;
Căutarea de tip vectorial este efectuată în Supabase, folosindu-se distanța cosine pe post de metrică de similaritate ce va returna primele trei rețete relevante.
Următorul pas este reprezentat de ceea ce diferențiază RAG de căutarea semantică: rezultatul este transformat folosind un model generativ, adăugând exact trei etichete (tags) fiecărei rețete din rezultatul generat de modelul nostru (modelul foodie):
const detailedRecipes = await Promise.all(
data.map(async (recipe: Recipe) => {
const generateTags = await ollama.generate({
model: "foodie",
prompt: `Generate exactly 3 tags for this
recipe: ${JSON.stringify(
recipe
)}, separated by comma`,
});
const tags = generateTags.response.split(",")
.map((tag) => tag.trim());
return { ...recipe, tags };
Implementarea inițială permitea ca detaliile unei rețete să poată fi accesate după efectuarea unei căutări, dând click pe rețetă. Drept urmare, toate informațiile relevante, precum timpul de gătit, ingredientele și pașii apăreau pe ecran, dar le lipsea descrierea în sine a rețetei.
Pentru a îmbunătăți aplicația, putem personaliza informația pe care utilizatorii o văd. Pentru a realiza acest lucru, putem salva descrierea în "messageToEmbed" deja calculat, practic în locul unde atât descrierea imaginii, cât și criteriile de căutare sunt calculate. Dacă utilizatorul dă click pe o rețetă care a fost returnată de RAG, apoi utilizatorul accesează pagina de detalii, putem genera o descriere a rețetei pe baza criteriilor de căutare ale utilizatorului.
const prompt = `In exactly 2 sentences explain why this recipe is exceptional, based on the user's search criteria. Highlight the flavors and experience without listing steps or ingredients. Be persuasive and concise. This is the recipe: ${JSON.stringify(
recipe
)}. User's search criteria: ${userInput}`;
const generateDescription = await ollama.generate({
model: "foodie",
prompt,
});
description = generateDescription.response;
return NextResponse.json({ description });
După cum s-a arătat mai sus, LLM-urile pot îmbunătăți aplicațiile noastre prin personalizarea rezultatelor și prin extragerea de interpretări ale datelor noastre. Acestea sunt unelte puternice care vor fi, cel mai probabil, utilizate la scară largă. Totuși, când decidem să integrăm LLM-uri în aplicațiile noastre, ar trebui să ținem cont și de alte aspecte.
Unul din cele mai importante aspecte este securitatea datelor. Securitatea datelor reprezintă una din principalele preocupări ale clienților, întrucât păzirea informațiilor sensibile este crucială pentru menținerea încrederii și a conformității cu standardele. Precum s-a arătat în exemplul de mai sus, modelele locale pot fi folosite în locul celebrelor LLM-uri, dar această alegere are nevoie de investigații suplimentare înainte de integrarea unui LLM în aplicațiile voastre.
Un alt aspect de luat în calcul este de a evalua dacă merită introdusă încă o dependință în aplicație, în măsura în care aceasta poate presupune mentenanță suplimentară în viitor.
În ultimul rând, datele de antrenament pot genera prejudecăți care trebuie gestionate pentru a asigura rezultate corecte și fiabile.
Ținând cont de aceste aspecte, putem folosi LLM-urile pentru a îmbunătăți aplicațiile, respectând principiile de securitate și fiabilitate.
[1] Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, L., & Polosukhin, I. "Attention is All You Need." 6 Dec. 2017.
[2] Lewis, Patrick, et al. "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks."
[3] Mehul, J. "RAG: Part 3: Embeddings." Medium, https://medium.com/@j13mehul/rag-part-3-embeddings-ff415eb9fed9.
[4] "Explained: Tokens and Embeddings in LLMs." The Research Nest, Medium, https://medium.com/the-research-nest/explained-tokens-and-embeddings-in-llms-69a16ba5db33.
[5] Ollama. GitHub, https://github.com/ollama/ollama/tree/main/docs.
[6] Johnson S., Unsplash, https://unsplash.com/photos/a-computer-circuit-board-with-a-brain-on-it-_0iV9LmPDn0.