Tecniche RAG avanzate una panoramica illustrata

Le tecniche avanzate di RAG una panoramica illustrata

Groningen, Martinitoren, dove l'articolo è stato composto nella tranquillità del Noorderplatsoen

Uno studio completo delle tecniche e degli algoritmi avanzati di recupero e generazione potenziata, sistematizzando vari approcci. L’articolo è corredato da una raccolta di link nel mio knowledge base che fanno riferimento a varie implementazioni e studi menzionati.

Dato che l’obiettivo del post è fornire una panoramica e una spiegazione degli algoritmi e delle tecniche RAG disponibili, non mi addentrerò nei dettagli di implementazione del codice, limitandomi a fare riferimento ad essi e lasciando che si consulti la vasta documentazione e i tutorial disponibili.

Introduzione

Se siete familiari con il concetto di RAG, potete passare direttamente alla parte avanzata di RAG.

Retrieval Augmented Generation, alias RAG, fornisce alle LLM (large language models) le informazioni recuperate da una determinata fonte di dati per basare la risposta generata su di esse. In sostanza, RAG è una combinazione di ricerca e suggerimento delle LLM, in cui si chiede al modello di rispondere alla query fornita con le informazioni trovate tramite l’algoritmo di ricerca come contesto. Sia la query che il contesto recuperato vengono iniettati nel prompt che viene inviato alle LLM.

RAG è l’architettura più popolare dei sistemi basati sulle LLM nel 2023. Ci sono molti prodotti costruiti quasi interamente su RAG, dai servizi di domande e risposte che combinano motori di ricerca web con LLM, a centinaia di app per chattare con i propri dati.

Anche l’area della ricerca vettoriale è stata stimolata da questa tendenza, anche se i motori di ricerca basati su embedding sono stati sviluppati con faiss nel 2019. Le startup di database vettoriali come chroma, weavaite.io e pinecone si sono basate su indici di ricerca open source esistenti, principalmente faiss e nmslib, e hanno aggiunto uno spazio di archiviazione aggiuntivo per i testi di input, insieme ad altri strumenti di recente.

Ci sono due librerie open source più importanti per i pipeline e le applicazioni basate su LLMLangChain e LlamaIndex, fondate con un mese di differenza nell’ottobre e novembre 2022, rispettivamente, ispirate dal lancio di ChatGPT e che hanno ottenuto un’ampia adozione nel 2023.

Lo scopo di questo articolo è sistematizzare le principali tecniche avanzate di RAG facendo riferimento alle loro implementazioni, principalmente in LlamaIndex, al fine di agevolare l’approccio di altri sviluppatori a questa tecnologia.

Il problema è che la maggior parte dei tutorial sceglie una o più tecniche e spiega dettagliatamente come implementarle anziché descrivere la piena varietà degli strumenti disponibili.

Un’altra cosa è che sia LlamaIndex che LangChain sono incredibili progetti open source, sviluppandosi a tal punto che la loro documentazione è già più spessa di un libro di testo di apprendimento automatico del 2016.

RAG Naive

Il punto di partenza del flusso di lavoro RAG in questo articolo sarà un corpus di documenti di testo – tralasciamo tutto ciò che viene prima di quel punto, lasciandolo ai fantastici caricatori di dati open source che si connettono a qualsiasi fonte immaginabile, da Youtube a Notion.

Uno schema dell'autore, così come tutti gli schemi successivi nel testo

Il caso RAG in breve è il seguente: si dividono i testi in frammenti, quindi si incapsulano questi frammenti in vettori con un modello di codificatore Transformer. Si inseriscono tutti questi vettori in un indice e infine si crea un prompt per un LLM che dice al modello di rispondere alla query dell’utente data il contesto che abbiamo trovato nella fase di ricerca. Durante l’esecuzione, si vettorizza la query dell’utente con lo stesso modello di codificatore e si esegue la ricerca di questo vettore di query nell’indice, si trovano i migliori k risultati, si recuperano i frammenti di testo corrispondenti dal database e si alimentano nel prompt LLM come contesto.

Il prompt può essere simile a questo:

un esempio di prompt RAG

L’ingegneria dei prompt è la cosa più economica che puoi provare per migliorare la tua pipeline RAG. Assicurati di aver consultato una guida completa all’ingegneria dei prompt di OpenAI qui.

Ovviamente, nonostante OpenAI sia leader di mercato come fornitore LLM, ci sono numerose alternative come Claude di Anthropic, modelli più piccoli ma molto capaci come Mixtral di Mistral, Phi-2 di Microsoft e molte opzioni open source come Llama2, OpenLLaMA, Falcon, quindi hai la scelta del modello per la tua pipeline RAG.

RAG avanzato

Adesso analizzeremo una panoramica delle tecniche avanzate di RAG. Qui è mostrato uno schema che rappresenta le fasi principali e gli algoritmi coinvolti. Alcuni loop di logica e comportamenti agenti complessi a più fasi sono omessi per mantenere lo schema leggibile.

Alcuni componenti chiave di un'architettura RAG avanzata. È più una scelta di strumenti disponibili che una schema dettagliato.

Gli elementi verdi nello schema sono le tecniche RAG di base discusse in seguito, quelli blu sono testi. Non tutte le idee avanzate di RAG sono facilmente visualizzabili su un singolo schema, ad esempio, varie approcci di ingrandimento del contesto sono omessi: ci approfondiremo questo nel corso del percorso.

1. Chunking & vectorisation

Innanzitutto, vogliamo creare un indice di vettori che rappresentano i contenuti dei nostri documenti e quindi, durante l’esecuzione, cercare la minima distanza coseno tra tutti questi vettori e il vettore di query che corrisponde al significato semantico più vicino.

1.1 Chunking I modelli di Transformer hanno una lunghezza fissa della sequenza di input e anche se la finestra del contesto di input è ampia, il vettore di una frase o di poche frasi rappresenta meglio il loro significato semantico rispetto a un vettore mediato su poche pagine di testo (dipende anche dal modello, ma è vero in generale), quindi dividi i tuoi dati: suddividi i documenti iniziali in frammenti di una certa dimensione senza perdere il loro significato (suddividendo il testo in frasi o paragrafi, senza tagliare una singola frase in due parti). Ci sono varie implementazioni di splitter di testo in grado di svolgere questa operazione.

La dimensione del frammento è un parametro da considerare: dipende dal modello di embedding che usi e dalla sua capacità in token. I modelli di codificatore Transformer standard come BERT-based Sentence Transformers prendono al massimo 512 token, OpenAI ada-002 è in grado di gestire sequenze più lunghe come 8191 token, ma il compromesso qui è un contesto sufficiente per il LLM per ragionare rispetto a un embedding di testo sufficientemente specifico per eseguire efficacemente la ricerca. Qui puoi trovare una ricerca che illustra le preoccupazioni sulla selezione della dimensione del frammento. In LlamaIndex questo è coperto dalla classe NodeParser con alcune opzioni avanzate come la definizione del tuo splitter di testo, metadati, relazioni tra nodi/frammenti, ecc.

1.2 VettorizzazioneIl passo successivo consiste nel scegliere un modello per incorporare i nostri chunks — ci sono diverse opzioni, io scelgo i modelli ottimizzati per la ricerca come bge-large o la famiglia di embedding E5 — basta controllare la classifica MTEB per gli ultimi aggiornamenti.

Per un’implementazione end2end dello step di chunking e vettorizzazione, controlla un esempio di un intero processo di ingestione dei dati in LlamaIndex.

2. Indice di archivio vettoriale 2.1

In questo schema e in tutto il testo successivo, omesso il blocco Encoder e inviamo direttamente la nostra query all'indice per semplificare lo schema. Ovviamente, la query viene sempre vettorizzata per prima. Lo stesso vale per i k cunks principali — l'indice recupera i k vettori, non i chunks, ma li sostituisco con i chunks poiché il loro recupero è un passaggio banale.

La parte cruciale del pipeline RAG è l’indice di ricerca, che memorizza il contenuto vettorizzato ottenuto nel precedente passaggio. L’implementazione più semplice utilizza un indice piatto — un calcolo della distanza forzata tra il vettore di query e tutti i vettori dei chunks.

Un indice di ricerca adeguato, ottimizzato per il recupero efficiente su più di 10.000 elementi, è un indice vettoriale come faiss, nmslib o annoy, che utilizzano un’implementazione di vicini approssimati come clustering, alberi o l’algoritmo HNSW.

Ci sono anche soluzioni gestite come OpenSearch o ElasticSearch e database vettoriali, che si occupano del processo di ingestione dei dati descritto nel passaggio 1 sotto la superficie, come Pinecone, Weaviate o Chroma.

A seconda della scelta dell’indice, dei dati e delle esigenze di ricerca, puoi anche archiviare metadati insieme ai vettori e utilizzare filtri di metadati per cercare informazioni in base a date o fonti, ad esempio.

LlamaIndex supporta molti indici di archivio vettoriale, ma ci sono anche altre implementazioni di indici più semplici supportate come indici di elenco, indici di alberi e indici di tabelle di parole chiave — ne parleremo nell’articolo sulla parte di recupero con Fusion.

2. 2 Indici gerarchici

Se hai molti documenti da recuperare, è necessario essere in grado di effettuare ricerche efficienti all’interno di essi, trovare informazioni rilevanti e sintetizzarle in una sola risposta con riferimenti alle fonti. Un modo efficace per farlo in caso di un database grande è creare due indici — uno composto da riassunti e l’altro composto da chunk di documenti, e cercare in due passaggi: prima filtrando i documenti rilevanti per i riassunti e poi cercando solo all’interno di questo gruppo rilevante.

2.3 Domande ipotetiche e HyDE

Un altro approccio consiste nel chiedere a un LLM di generare una domanda per ogni blocco e incorporare queste domande in vettori, eseguendo una ricerca delle query in base a questo indice di vettori delle domande (sostituendo i vettori dei blocchi con i vettori delle domande nel nostro indice) e poi, dopo il recupero, instradare i blocchi di testo originali e inviarli come contesto al LLM per ottenere una risposta. Questo approccio migliora la qualità della ricerca grazie ad una maggiore similarità semantica tra la query e la domanda ipotetica rispetto a quella che avremmo per un blocco effettivo.

Esiste anche l’approccio logico invertito chiamato HyDE – si chiede a un LLM di generare una risposta ipotetica data la query e quindi si utilizza il suo vettore insieme al vettore di query per migliorare la qualità della ricerca.

2.4 Arricchimento del contesto

Il concetto qui è quello di recuperare piccoli blocchi per una migliore qualità della ricerca, ma aggiungere contesto circostante per far ragionare il LLM. Ci sono due opzioni: espandere il contesto con frasi intorno al blocco più piccolo recuperato o suddividere ricorsivamente i documenti in un numero di blocchi genitori più grandi, contenenti blocchi figli più piccoli.

2.4.1 Recupero della finestra di frasi In questo schema, ogni frase in un documento viene incorporata separatamente, il che fornisce una grande accuratezza per la distanza coseno tra la query e il contesto. Per ragionare meglio sul contesto trovato dopo aver ottenuto la frase singola più rilevante, estendiamo la finestra di contesto di k frasi prima e dopo la frase recuperata e quindi inviamo questo contesto esteso al LLM.

La parte verde è l’incorporamento della frase trovato durante la ricerca nell’indice e l’intero paragrafo nero + verde viene fornito al LLM per ampliare il suo contesto durante il ragionamento sulla query fornita

2.4.2 Ricerca di riunione automatica (alias Ricerca documento genitore)

L’idea qui è abbastanza simile a quella della Ricerca della finestra di frasi – cercare informazioni più granulari ed estendere quindi la finestra di contesto prima di alimentare tale contesto a un LLM per il ragionamento. I documenti vengono suddivisi in blocchi figli più piccoli relativi a blocchi genitori più grandi.

I documenti vengono suddivisi in un'organizzazione gerarchica di blocchi e quindi i blocchi più piccoli vengono inviati all'indice. Al momento del recupero, recuperiamo k blocchi foglia e, se ci sono n blocchi che si riferiscono allo stesso blocco genitore, li sostituiamo con questo blocco genitore e lo inviamo al LLM per generare la risposta.

Recuperare prima blocchi più piccoli durante il recupero, quindi se più di n blocchi nei k blocchi migliori recuperati sono collegati allo stesso nodo genitore (blocco più grande), sostituire il contesto fornito al LLM con questo nodo genitore – funziona come una fusione automatica di alcuni blocchi recuperati in un blocco genitore più grande, da qui il nome del metodo. Solo da notare – la ricerca viene eseguita solo all’interno dell’indice dei nodi figlio. Consulta il tutorial di LlamaIndex su Ricerca ricorsiva + Riferimenti ai nodi per una comprensione più approfondita.

Un’idea relativamente vecchia che potresti prendere il meglio di entrambi i mondi: la ricerca basata su parole chiave, algoritmi di recupero sparsi come tf-idf o lo standard dell’industria delle ricerche, BM25e la ricerca semantica o vettoriale moderna e combinarli in un unico risultato di recupero. L’unico trucco qui è quello di combinare correttamente i risultati recuperati con diverse punteggi di similarità: questo problema viene di solito risolto con l’aiuto dell’algoritmo Fusione del rango reciproco, che riorienta i risultati recuperati per l’output finale.

In LangChain questo è implementato nella classe Ensemble Retriever, che combina una lista di retriever che definisci, ad esempio un indice vettoriale faiss e un retriever basato su BM25, utilizzando RRF per il reranking.

In LlamaIndex questo viene fatto in un modo abbastanza simile.

La ricerca ibrida o fusion di solito fornisce risultati di recupero migliori in quanto vengono combinati due algoritmi di ricerca complementari, che tengono conto sia della similarità semantica che del matching delle parole chiave tra la query e i documenti memorizzati.

3. Reranking & filtering

Quindi abbiamo ottenuto i nostri risultati di recupero con uno qualsiasi degli algoritmi descritti sopra, ora è il momento di raffinarli tramite filtraggio, reranking o qualche trasformazione. In LlamaIndex ci sono diverse Postprocessors disponibili, che filtrano i risultati in base al punteggio di similarità, alle parole chiave, ai metadati o li rirestuiscono con altri modelli come un LLM, un sentence-transformer cross-encoder, un endpoint di reranking di Cohere o in base ai metadati come la recente data di pubblicazione – fondamentalmente, tutto ciò che puoi immaginare.

Questo è l’ultimo passo prima di alimentare il contesto recuperato nel LLM per ottenere la risposta finale.

Ora è il momento di passare alle tecniche RAG più sofisticate come la trasformazione della query e il routing, entrambi coinvolgendo LLM e rappresentando quindi un comportamento agentico – una logica complessa che coinvolge il ragionamento LLM all’interno del nostro pipeline RAG.

4. Trasformazioni della query

Le trasformazioni della query sono una famiglia di tecniche che utilizzano un LLM come motore di ragionamento per modificare l’input dell’utente al fine di migliorare la qualità del recupero. Ci sono diverse opzioni per farlo.

Principi di trasformazione della query illustrati

Se la query è complessa, LLM può decomporla in diverse sotto-queries. Ad esempio, se fai una domanda del tipo: – “Quale framework ha più stelle su Github, Langchain o LlamaIndex?”, è improbabile che troveremo un confronto diretto in qualche testo nel nostro corpus, quindi ha senso decomporre questa domanda in due sotto-queries che presuppongono un recupero di informazioni più semplice e concreto: – “Quante stelle ha Langchain su Github?” – “Quante stelle ha Llamaindex su Github?” Verranno eseguite in parallelo e poi il contesto recuperato verrà combinato in un unico prompt per il LLM per sintetizzare una risposta finale alla query iniziale. Entrambe le librerie hanno questa funzionalità implementata – come un Multi Query Retriever in Langchain e come un Motore di Query Sub Question in Llamaindex.

  1. Step-back prompting utilizza LLM per generare una query più generale, per la quale otteniamo un contesto più generale o di alto livello utile per ancorare la risposta alla nostra query originale. Viene eseguito anche il recupero per la query originale e entrambi i contesti vengono alimentati nel LLM nella fase finale di generazione della risposta. Ecco un’implementazione di LangChain qui.
  2. Il rewriting della query utilizza LLM per riformulare la query iniziale al fine di migliorarne il recupero. Sia LangChain che LlamaIndex hanno implementazioni, sebbene un po’ diverse, trovo che la soluzione di LlamaIndex sia più potente qui.

Citazioni di riferimento

Questa non ha un numero poiché è più uno strumento che una tecnica per migliorare il recupero, anche se è molto importante. Se abbiamo utilizzato più fonti per generare una risposta, sia a causa della complessità della query iniziale (abbiamo eseguito più sottoquery e poi combinato il contesto recuperato in una risposta), sia perché abbiamo trovato contesto rilevante per una singola query in vari documenti, sorge la domanda se potremmo fare un riferimento preciso alle nostre fonti.

Ci sono un paio di modi per farlo:

  1. Inserire questo compito di referenziamento nella nostra prompt e chiedere a LLM di menzionare gli ID delle fonti utilizzate.
  2. Abbinare le parti della risposta generata ai frammenti di testo originali nel nostro indice – llamaindex offre una soluzione efficiente basata sul fuzzy matching per questo caso. Nel caso in cui non abbiate mai sentito parlare del fuzzy matching, si tratta di una tecnica di corrispondenza di stringhe incredibilmente potente.

5. Motore di chat

La cosa più importante nello sviluppo di un sistema RAG che possa funzionare più di una volta per una singola query è la logica della chat, tenendo conto del contesto del dialogo, come nei classici chatbot dell’era pre-LLM. Questo è necessario per supportare domande di approfondimento, anafora o comandi arbitrari degli utenti relativi al contesto del dialogo precedente. Si risolve con la tecnica di compressione delle query, tenendo conto del contesto della chat insieme alla query dell’utente.

Come sempre, ci sono diversi approcci a questa compressione del contesto – un popolare e relativamente semplice ContextChatEngine, che recupera prima il contesto rilevante per la query dell’utente e poi lo invia a LLM insieme alla cronologia della chat dalla memoria buffer per far sì che LLM sia consapevole del contesto precedente durante la generazione della risposta successiva.

Un caso leggermente più sofisticato è CondensePlusContextMode – in questa modalità, a ogni interazione, la cronologia della chat e l’ultimo messaggio vengono condensati in una nuova query, quindi questa query viene inviata all’indice e il contesto recuperato viene passato a LLM insieme al messaggio originale dell’utente per generare una risposta.

È importante notare che LlamaIndex supporta anche Chat Engine basato su agenti OpenAI, che offre una modalità di chat più flessibile, e Langchain supporta anche l’API funzionale di OpenAI.

Un'illustrazione dei diversi tipi e principi dei motori di chat

Ci sono altri tipi di motori di chat come ReAct Agent, ma passiamo direttamente agli agenti stessi nella sezione 7.

6. Instradamento delle query

L’instradamento delle query è il passaggio decisionale basato su LLM su cosa fare successivamente sulla base della query dell’utente – le opzioni di solito sono di riassumere, di eseguire una ricerca in un indice di dati o di provare un certo numero di percorsi diversi per poi sintetizzare il loro output in una singola risposta.

Gli instradatori di query vengono utilizzati anche per selezionare un indice, o più in generale, una memoria dati, a cui inviare la query dell’utente – sia che si abbiano più fonti di dati, ad esempio, un archivio di vettori classico e un database a grafo o un database relazionale, oppure si abbia una gerarchia di indici – per uno storage di documenti multipli, un caso abbastanza classico sarebbe un indice di riassunti e un altro indice di frammenti di documenti vettoriali, ad esempio.

La definizione dell’instradatore di query include la configurazione delle scelte che può effettuare. La selezione di un’opzione di instradamento viene effettuata con una chiamata a LLM, restituendo il risultato in un formato predefinito, utilizzato per instradare la query nell’indice specificato o, se stiamo parlando di un comportamento agnatico, a sotto-catene o addirittura ad altri agenti come mostrato nel seguente schema dell’agente a documenti multipli.

Sia LlamaIndex che LangChain supportano i router di query.

7. Agenti in RAG

Gli agenti (supportati sia da Langchain che da LlamaIndex) sono presenti fin dai primi giorni in cui è stato rilasciato il primo LLM API — l’idea era quella di fornire un LLM in grado di ragionare con un set di strumenti e un compito da completare. Gli strumenti potrebbero includere alcune funzioni deterministiche come una qualsiasi funzione di codice o un’API esterna o addirittura altri agenti — questa idea di concatenazione LLM è da dove LangChain ha preso il suo nome.

Gli agenti sono un argomento molto ampio di per sé ed è impossibile approfondire a sufficienza l’argomento all’interno di una panoramica di RAG, quindi continuerò semplicemente con il caso di recupero multi-documento basato sugli agenti, facendo una breve sosta alla stazione degli Assistants di OpenAI, poiché è una cosa relativamente nuova, presentata alla recente conferenza per sviluppatori OpenAI come GPTs e funzionante sotto il cofano del sistema RAG descritto di seguito.

OpenAI Assistants hanno essenzialmente implementato molti degli strumenti necessari intorno a un LLM che precedentemente avevamo in open source — una cronologia della chat, una memorizzazione delle conoscenze, un’interfaccia per l’upload dei documenti e, forse più importante, un’API per la chiamata di funzioni. Quest’ultima fornisce la possibilità di convertire il linguaggio naturale in chiamate API a strumenti esterni o interrogazioni al database.

In LlamaIndex c’è una classe OpenAIAgent che unisce questa logica avanzata alle classi ChatEngine e QueryEngine, fornendo una chat basata sulla conoscenza e consapevole del contesto insieme alla capacità di effettuare chiamate multiple alle funzioni OpenAI in un’unica conversazione, il che porta davvero a un comportamento agente intelligente.

Diamo un’occhiata allo schema degli agenti multi-documento — una configurazione piuttosto sofisticata che prevede l’inizializzazione di un agente (OpenAIAgent) per ogni documento, in grado di riassumere i documenti e di utilizzare le classiche meccaniche di domande e risposte, e un agente principale responsabile del routing delle query agli agenti dei documenti e della sintesi della risposta finale.

Ogni agente di documento dispone di due strumenti — un indice di memorizzazione dei vettori e un indice di riassunto, e in base alla query instradata decide quale utilizzare. E per l’agente principale, tutti gli agenti dei documenti sono strumenti rispettosi.

Questo schema illustra un’architettura RAG avanzata con molte decisioni di routing prese da ogni agente coinvolto. Il vantaggio di un tale approccio è la capacità di confrontare diverse soluzioni o entità descritte in documenti diversi e nei loro riassunti insieme alle classiche meccaniche di riassunto e domande e risposte su documenti singoli — questo copre essenzialmente gli utilizzi più frequenti di chattare con una collezione di documenti.

Uno schema illustrante agenti multi-documento, che coinvolge sia il routing delle query che i modelli di comportamento degli agenti.

Lo svantaggio di un simile schema complesso può essere intuito dall’immagine — è un po’ lento a causa delle numerose iterazioni con i LLM all’interno dei nostri agenti. Nel caso, la chiamata di un LLM è sempre l’operazione più lunga in un flusso di lavoro RAG — la ricerca è ottimizzata per la velocità per design. Quindi, per un grande archivio di documenti multipli, consiglio di pensare a semplificazioni di questo schema che lo rendano scalabile.

8. Sintesi della risposta

Questo è l’ultimo passo di qualsiasi pipeline RAG: generare una risposta basata sul contesto che abbiamo recuperato attentamente e sulla query iniziale dell’utente. L’approccio più semplice sarebbe concatenare e fornire tutto il contesto recuperato (sopra una soglia di rilevanza) insieme alla query a un LLM in una volta sola. Ma, come sempre, ci sono altre opzioni più sofisticate che coinvolgono chiamate multiple a LLM per raffinare il contesto recuperato e generare una risposta migliore.

I principali approcci alla sintesi delle risposte sono: 1. Raffinare iterativamente la risposta inviando il contesto recuperato a LLM pezzo per pezzo 2. Sintetizzare il contesto recuperato per adattarlo alla richiesta 3. Generare risposte multiple basate su diverse porzioni di contesto e quindi concatenarle o sintetizzarle. Per ulteriori dettagli consultare la documentazione del modulo di sintesi delle risposte.

Affinamento dell’Encoder e del LLM

Questo approccio prevede l’affinamento di alcuni dei due modelli DL coinvolti nella nostra pipeline RAG, ovvero il Transformer Encoder, responsabile della qualità delle incapsulazioni e quindi della qualità del recupero del contesto o un LLM, responsabile dell’ottimo utilizzo del contesto fornito per rispondere alla query dell’utente — fortunatamente, quest’ultimo è un buon apprendista con poche informazioni.

Un grande vantaggio oggi è la disponibilità di LLM di alta qualità come GPT-4 per generare set di dati sintetici di alta qualità. Ma dovresti sempre essere consapevole del fatto che prendere un modello open-source addestrato da team di ricerca professionisti su grandi set di dati accuratamente raccolti, puliti e convalidati e fare una rapida messa a punto utilizzando un piccolo set di dati sintetici potrebbe limitare le potenzialità del modello in generale.

Affinamento dell’Encoder

Sono stato anche un po’ scettico sull’approccio di affinamento dell’Encoder poiché gli ultimi Encoder del Transformer ottimizzati per la ricerca sono piuttosto efficienti. Quindi ho testato l’aumento delle prestazioni ottenuto con l’affinamento di bge-large-en-v1.5 (primo 4 della MTEB leaderboard al momento della scrittura) nell’impostazione di LlamaIndex notebook e ha dimostrato un aumento del 2% nella qualità del recupero. Niente di drammatico, ma è bello essere consapevoli di questa opzione, specialmente se hai un dataset a dominio stretto per cui stai costruendo RAG.

Affinamento del Ranker

L’altra opzione ben consolidata è avere un cross-encoder per il riordinamento dei risultati recuperati se non ti fidi completamente dell’Encoder di base. Funziona nel seguente modo: passi la query e ciascuna delle prime k porzioni di testo recuperate al cross-encoder, separate da un token SEP, e lo affini per produrre 1 per le porzioni rilevanti e 0 per quelle non rilevanti. Un buon esempio di tale processo di sintonizzazione può essere trovato qui; i risultati mostrano che il punteggio a coppia è migliorato del 4% tramite l’affinamento del cross-encoder.

Affinamento del LLM

Recentemente, OpenAI ha iniziato a fornire l’affinamento del LLM tramite l’ API e LlamaIndex ha un tutorial sull’affinamento di GPT-3.5-turbo con impostazioni di RAG per “distillare” parte delle conoscenze di GPT-4. L’idea qui è prendere un documento, generare un certo numero di domande con GPT-3.5-turbo, quindi utilizzare GPT-4 per generare risposte a queste domande basandosi sui contenuti del documento (costruendo una pipeline RAG potenziata da GPT-4) e quindi affinare GPT-3.5-turbo su quel set di coppie domanda-risposta. Il framework ragas usato per la valutazione della pipeline RAG mostra un aumento del 5% nelle metriche di veridicità, il che significa che il modello GPT 3.5-turbo affinato ha fatto un migliore uso del contesto fornito per generare la risposta rispetto a quello originale.

Un approccio leggermente più sofisticato è dimostrato nell’ultimo articolo RA-DIT: Recupero potenziato da tuner Dual Instruction di Meta AI Research, suggerendo una tecnica per ottimizzare sia LLM che il Retriever (un Dual Encoder nell’articolo originale) sui triplette di query, contesto e risposta. Per i dettagli di implementazione fare riferimento a questa guida. Tale tecnica è stata utilizzata sia per il fine-tuning delle LLM di OpenAI tramite l’API di fine-tuning che per il modello open-source Llama2 (nell’articolo originale), ottenendo un aumento del ~5% nelle metriche delle attività ad alta conoscenza (rispetto a Llama2 65B con RAG) e un aumento di alcuni punti percentuali nelle attività di ragionamento del buon senso.

Nel caso in cui conosci approcci migliori per il fine-tuning delle LLM per RAG, ti preghiamo di condividere la tua esperienza nella sezione dei commenti, soprattutto se sono applicati alle LLM open source più piccole.

Valutazione

Ci sono diversi framework per la valutazione delle prestazioni dei sistemi RAG che condividono l’idea di avere diverse metriche separate come la rilevanza della risposta, la basatura della risposta, la fedeltà e la rilevanza del contesto recuperato.

Ragas, menzionato nella sezione precedente, utilizza la fedeltà e la rilevanza della risposta come metriche di qualità delle risposte generate e la precisione del contesto e recall classiche per la parte di recupero dello schema RAG.

In un recente corso breve molto interessante Building and Evaluating Advanced RAG di Andrew NG, LlamaIndex e il framework di valutazione Truelens, propongono la triade RAG – la rilevanza del contesto recuperato per la query, la basatura (quanto la risposta LLM è supportata dal contesto fornito) e la rilevanza della risposta alla query.

La metrica chiave e più controllabile è la rilevanza del contesto recuperato – fondamentalmente le parti 1-7 della pipeline RAG avanzata descritta sopra, oltre alle sezioni di ottimizzazione Encoder e Ranker, sono destinate a migliorare questa metrica, mentre la parte 8 e il fine-tuning LLM si concentrano sulla rilevanza della risposta e sulla basatura.

Un buon esempio di una pipeline di valutazione del retriever piuttosto semplice può essere trovato qui ed è stato utilizzato nella sezione di ottimizzazione dell’Encoder. Un approccio leggermente più avanzato che tiene conto non solo del tasso di successo, ma del Mean Reciprocal Rank, una metrica comune dei motori di ricerca, e anche delle metriche di risposta generate come la fedeltà e la rilevanza, è dimostrato nel libro di cucina OpenAI.

LangChain ha un framework di valutazione piuttosto avanzato LangSmith in cui è possibile implementare valutatori personalizzati e monitora le tracce in esecuzione all’interno della pipeline RAG per rendere il sistema più trasparente.

Nel caso in cui si stia lavorando con LlamaIndex, è disponibile un pacchetto llama_evaluator rag_evaluator, che fornisce uno strumento rapido per valutare la propria pipeline con un dataset pubblico.

Conclusion

Ho cercato di delineare gli approcci algoritmicamente centrali a RAG e di illustrarne alcuni nella speranza che ciò possa suscitare alcune idee innovative da provare nel tuo flusso di lavoro RAG o portare un po’ di ordine alla vasta varietà di tecniche inventate quest’anno – per me il 2023 è stato l’anno più entusiasmante nell’ambito dell’IA.

Ci sono molte altre altre cose da considerare come RAG basato sulla ricerca web (RAGs di LlamaIndex, webLangChain, ecc.), approfondire le architetture agent-based (e il recente coinvolgimento di OpenAI in questo campo) e alcune idee su LLM Long-term memory.

La principale sfida produttiva per i sistemi RAG, oltre alla pertinenza e fedeltà delle risposte, è la velocità, soprattutto se si utilizzano scheme basati su agenti più flessibili, ma questo è un argomento per un altro post. Questa funzione di streaming utilizzata da ChatGPT e dalla maggior parte degli altri assistenti non è uno stile cyberpunk casuale, ma semplicemente un modo per ridurre il tempo di generazione percepito della risposta. È per questo che vedo un futuro molto luminoso per i LLM di dimensioni ridotte e le recenti versioni di Mixtral e Phi-2 ci stanno conducendo in questa direzione.

Grazie mille per aver letto questo lungo post!

I principali riferimenti sono raccolti nella mia base di conoscenza, c’è un co-pilota con cui chattare attraverso questo set di documenti: https://app.iki.ai/playlist/236.

Trovami su LinkedIn o Twitter