Într-o eră digitală în care informația curge într-un ritm amețitor, tehnologiile care combină căutarea eficientă cu generarea de conținut devin esențiale. Aceste soluții inovatoare ne ajută să navigăm mai eficient prin complexitatea datelor și să găsim rapid răspunsuri relevante la întrebările noastre. Odată cu popularizarea modelelor de limbaj de mari dimensiuni (LLM), căutările semantice au evoluat semnificativ, devenind un instrument valoros în procesul de informare. Din păcate, antrenarea unui LLM necesită resurse greu accesibile, iar situațiile în care căutarea trebuie să extragă rezultate precise și de actualitate sunt tot mai frecvente. În acest context, sistemele care integrează extragerea informațiilor cu generarea de conținut vin să acopere acest gol punând în valoare puterea căutărilor semantice pe date relevante.
Un sistem RAG (Retriever-Augmented Generation) este o combinație a două elemente: un retriever și un generator.
Retriever: Folosește baze de date vectoriale (precum Qdrant, Pinecone sau FAISS) pentru a căuta fragmentele relevante din documente.
Retrieverul parcurge datele în căutarea fragmentelor contextuale relevante pentru întrebarea pusă, iar generatorul creează și oferă răspunsurile folosind acest context.
De ce sunt importante sistemele RAG ?
Sistemele RAG revoluționează modul în care interacționăm cu informația. Iată câteva exemple de aplicații ce pot fi implementate folosind un sistem RAG:
Consultant digital personalizat: extrage punctele cheie din note, cărți sau articole de cercetare în câteva minute, economisind ore de cercetare.
Chat Bot pentru servicii cu clienți: retrage automat răspunsuri dintr-o bază de date mare de întrebări și răspunsuri sau documente companiei fără a fi nevoie de căutare manuală. Marele avantaj este că LLM-ul nu trebuie reantrenat pentru a reține informațiile actualizate.
Sună interesant ? Haideți să ne construim propriul sistem RAG local
În acest articol propunem un setup local, ușor de utilizat, ce ne permite să păstrăm private datele sensibile.
Server local: Opțiunile populare includ Ollama și LM Studio. Pentru a găzdui LLM-ul, vom utiliza LM Studio datorită interfeței sale grafice intuitive, ceea ce simplifică selecția și gestionarea modelului.
1.Descărcați LM Studio pentru sistemul dumneavoastră de operare de pe site-ul oficial.
2.Mergeți la secțiunea Discover
și alegeți un model pe care să îl descărcați. Voi folosi qwen2.5-7B-instruct-1M
, deoarece este un model mic, însă suficient de capabil, dar vă sfătuiesc să experimentați cu diferite modele.
3.Încărcați modelul și mergeți la secțiunea Developer
pentru a porni serverul (Ctrl + R). Acest lucru va face modelul disponibil local.
4.Creați un mediu virtual de Python în directorul proiectului RAG:
python -m venv .venv
source .venv/Scripts/activate
# exemplu pentru terminalul Git Bash
5.Instalați dependințele:
pip install numpy faiss-cpu transformers
sentence-transformers openai
Directorul data
pentru fișiere .txt
. (sursa documentelor pentru retriever)
Scripturile: data_retriever.py
implementează retrieverul, response_generator.py
generatorul LLM, în rag.py
asamblăm sistemul pe care îl vom chema din fișierul main.py
.
1.La început, documentele sunt împărțite în fragmente de o dimensiune aleasă (valoarea predefinită fiind de 512 caractere). Spre exemplu, câteva fișiere text pot vor deveni sute (sau mii) de fragmente text de 512 caractere fiecare. Acestea sunt apoi indexate pentru căutare rapidă și transformate în codificări numerice numite embedings
.
class DataRetriever:
def __init__(self, directory_path,
chunk_size=512, top_k=5):
self.directory_path = directory_path
if not os.path.isdir(self.directory_path):
raise ValueError(f"Directory does not exist:
{self.directory_path}")
self.model = SentenceTransformer(
'all-MiniLM-L6-v2')
self.chunk_size = chunk_size
self.top_k = top_k
self.index_path = 'faiss_index'
self.embeddings_path = 'embeddings.pkl'
self.document_chunks = self._chunk_documents()
self.index, self.embeddings =
self._load_or_build_index()
2.Când utilizatorul abordează o cerință, aceasta este, de asemenea, codificată în reprezentări numerice (embedings). Retrieverul returnează primele top_k
(în cazul nostru 5) fragmente de text din documente cu conținut similar cerinței utilizatorului. Folosind reprezentările numerice, căutarea este mult mai amplă decât o simplă căutare după cuvinte cheie, întrucât acum se iau în considerare și cuvintele apropiate ca sens (sinonime, familie de cuvinte etc.).
def retrieve(self, query):
query_embedding = self.model.encode([query],
convert_to_tensor=True).cpu().numpy()
distances, indices = self.index.search(
query_embedding, self.top_k)
relevant_texts = [f"... {
self.document_chunks[idx]} ..." for
idx in indices[0]]
return '\n'.join(relevant_texts)
Implementarea celorlalte metode ajutătoare se poate găsi în codul sursă.
1.LM Studio folosește API-ul openai
. Dorim totuși, să fim flexibili, lăsând deschisă posibilitatea de a folosi sistemul RAG și cu alte API-uri (precum Ollama). Astfel, vom crea, mai întâi o interfață pe care să o moștenim:
from abc import ABC, abstractmethod
class IResponseGenerator(ABC):
@abstractmethod
def query(self, user_prompt):
pass
class OpenaiResponseGenerator(IResponseGenerator):
2.Ca să folosim modelul local, vom specifica adresa serverului local: http://127.0.0.1:1234 pentru variabila base_url la inițializare.
def __init__(self, base_url, api_key, model,
history_length=20):
self.client = OpenAI(base_url=base_url,
api_key=api_key)
3.Deoarece LLM-urile nu au capacitatea intrinsecă de a reține mai mult de un mesaj, dacă dorim să putem purta o conversație, va trebui să reținem mesajele anterioare într-o listă:
Într-o conversație cu un LLM există trei roluri posibile: cel de system
, de user
și de assistant
. Rolul de system
va fi întotdeauna primul și ne ajută să definim un comportament general pentru modelul de limbaj de-a lungul conversației. Vom adăuga posibilitatea de setare a unui prompt de sistem prin metoda set_system_prompt
și vom folosi o metodă ajutătoare add_to_chat_history
pentru a ne asigura că acesta este primul în conversație, chiar dacă se depășește mărimea istoricului.
def add_to_chat_history(self, message):
sys_prompt_not_in_history = not any(
msg['role'] == 'system' and msg['content'] ==
self.system_prompt for msg in self.chat_history
)
if self.system_prompt and sys_prompt_not_in_history:
self.chat_history.insert(0, {'role': 'system',
'content': self.system_prompt})
self.chat_history.append({'role': 'user',
'content': message})
if len(self.chat_history) > self.history_length:
self.chat_history = self.chat_history[
-self.history_length:]
4.Metoda query
este responsabilă pentru a extrage răspunsul LLM-ului.
def query(self, user_prompt):
try:
self.add_to_chat_history(user_prompt)
response = self.client.chat.completions.create(
messages=self.chat_history,
model=self.model
)
assistant_response = {
'role': 'assistant',
'content': response.choices[0].message.content
}
self.chat_history.append(assistant_response)
if len(self.chat_history) > self.history_length:
self.chat_history = self.chat_history[
-self.history_length:
]
return response.choices[0].message.content
except Exception as e:
return f"S-a produs o excepție: {str(e)}"
După ce am construit retrieverul și generatorul, vom combina aceste două componente pentru a forma sistemul RAG. În esență, vom modifica promptul pentru a adăuga în context citatele relevante extrase din documente.
from data_retriever import DataRetriever
from response_generator import IResponseGenerator
class RAG:
def __init__(
self,
retriever: DataRetriever,
generator: IResponseGenerator
):
self.retriever = retriever
self.generator = generator
def generate_response(self, user_query):
retrieved_docs = self.retriever.retrieve(
user_query
)
intro = (
"Având în vedere anumite informații "
"relevante din documente:"
)
pre_prompt = (
"Răspunde la următoarea cerere "
"a utilizatorului:"
)
full_prompt = (
f"{intro}\n```\n{retrieved_docs}\n```\n"
f"{pre_prompt}\n{user_query}"
)
response = self.generator.query(full_prompt)
return response
Definim funcționalitatea prin care utilizatorul interacționează cu sistemul.
def start_chat(rag_system):
user_query = input("Utilizator: ")
while user_query.lower() != 'quit':
response = rag_system.generate_response(
user_query
)
print(f"Asistent: {response}")
user_query = input("Utilizator: ")
Implementăm metoda main, unde configurăm retrieverul și generatorul, iar apoi sistemul RAG.
def main():
retriever = DataRetriever(
'data',
chunk_size=512,
top_k=5
)
base_url, api_key = (
"http://127.0.0.1:1234/v1",
"api_key"
)
model = "qwen2.5-7b-instruct-1m"
generator = OpenaiResponseGenerator(
base_url,
api_key,
model,
history_length=10
)
rag_system = RAG(retriever, generator)
start_chat(rag_system)
if __name__ == "__main__":
main()
Adăugați documentele în format .txt
în directorul data
, apoi rulați python main.py
. Încercați să formulați întrebări al căror răspuns se află în documentele dumneavoastră.
Ajustarea parametrilor (mărimea chunkurilor, numărul rezultatelor returnate) precum și promptul sistemului pot îmbunătăți rezultatele obținute.
Marele beneficiu al sistemelor RAG (Retriever-Augmented Generation) este capacitatea acestora de a oferi răspunsuri mai precise și relevante prin integrarea informațiilor externe cu modele generative. Aceasta permite sistemelor de inteligență artificială să acceseze cunoștințe actualizate și să răspundă la întrebări complexe, îmbunătățind astfel acuratețea și utilitatea răspunsurilor. Această abordare ajută la depășirea limitărilor modelelor de limbaj bazate exclusiv pe datele cu care au fost antrenate.
Generative AI în Programare
Miercuri, 26 Martie, ora 18:00
sediul Betfair Romania Development
Facebook Meetup StreamEvent YouTubede Ovidiu Mățan
de Ioana Barboș