Rendere utile la primavera AI e OpenAI GPT con RAG sui tuoi documenti

Valorizza la primavera utilizzando AI e OpenAI GPT con RAG per i tuoi documenti

Il progetto AIDocumentLibraryChat utilizza il progetto Spring AI con OpenAI per cercare risposte a domande in una libreria di documenti. Per fare ciò, viene utilizzata la Retrieval Augmented Generation sui documenti.

Retrieval Augmented Generation

Il processo è il seguente:

Il processo è il seguente:

  • Carica il documento
  • Memorizza il documento nel database Postgresql
  • Suddividi il documento per creare Embeddings
  • Crea Embeddings con una chiamata al modello di Embedding di OpenAI
  • Memorizza gli Embeddings del documento nel database vettoriale Postgresql

Cerca documenti:

  • Crea una ricerca
  • Crea un Embedding della ricerca con una chiamata al modello di Embedding di OpenAI
  • Interroga il database vettoriale Postgresql per i documenti con le distanze di Embedding più vicine
  • Interroga il database Postgresql per il documento
  • Crea una richiesta con la ricerca e il frammento di testo del documento
  • Richiedi una risposta dal modello GPT e mostra la risposta in base alla ricerca e al frammento di testo del documento

Caricamento documenti

Il documento caricato viene memorizzato nel database per avere il documento di origine della risposta. Il testo del documento deve essere suddiviso in frammenti per creare gli Embeddings per ogni frammento. Gli Embeddings sono creati da un modello di Embedding di OpenAI e sono vettori con più di 1500 dimensioni per rappresentare il frammento di testo. L’Embedding è memorizzato in un documento AI con il frammento di testo e l’ID del documento di origine nel database vettoriale.

La ricerca di documenti prende la ricerca e utilizza il modello di Embedding di Open AI per trasformarla in un Embedding. L’Embedding viene utilizzato per cercare nel database vettoriale il vettore vicino più simile. Ciò significa che vengono cercati gli Embedding della ricerca e del frammento di testo che hanno le maggiori somiglianze. L’ID nell’AIDocument viene utilizzato per leggere il documento nel database relazionale. Con la ricerca e il frammento di testo dell’AIDocument, viene creato un Prompt di Documento. Successivamente, viene chiamato il modello OpenAI GPT con il Prompt per creare una risposta basata sulla ricerca e il contesto del documento. Ciò fa sì che il modello crei risposte che si basano strettamente sui documenti forniti e ne migliorano l’accuratezza. La risposta del modello GPT viene restituita e visualizzata con un link al documento per fornire la fonte della risposta.

Architettura

L’architettura del progetto si basa su Spring Boot con Spring AI. L’interfaccia utente Angular fornisce l’interfaccia utente per mostrare l’elenco dei documenti, caricare i documenti e fornire la ricerca con la risposta e il documento di origine. Comunica con il backend di Spring Boot tramite l’interfaccia REST. Il backend di Spring Boot fornisce i controller REST per il frontend e utilizza Spring AI per comunicare con i modelli OpenAI e il database vettoriale Postgresql. I documenti vengono memorizzati con JPA nel database relazionale Postgresql. Il database Postgresql viene utilizzato perché combina il database relazionale e il database vettoriale in un’immagine Docker.

Implementazione

Frontend

Il frontend si basa su componenti autonomi caricati in modo lazy con Angular. I componenti autonomi caricati in modo lazy sono configurati in app.config.ts:

La configurazione imposta le rotte e abilita il client HTTP e le animazioni.

Le rotte caricate in modo lazy sono definite in app.routes.ts:

In ‘loadChildren’, l’importazione “…” viene eseguita in modo lazy dal percorso fornito e imposta le rotte esportate definite nella costante ‘mod.XXX’.

La rotta caricata in modo lazy ‘docsearch’ ha l’archivio index.ts per esportare la costante:

Ciò esporta doc-search.routes.ts:

Restituisce l’instradamento al ‘DocSearchComponent’.

Il caricamento del file si trova nel componente DocImportComponent con il template doc-import.component.html:

Il caricamento del file viene eseguito con il tag ‘<input type=”file” (change)=”onFileInputChange($event)”>’. Fornisce la funzionalità di caricamento e chiama il metodo ‘onFileInputChange(…)’ dopo ogni caricamento.

Il pulsante ‘Upload’ chiama il metodo ‘upload()’ per inviare il file al server al clic.

Il componente doc-import.component.ts ha i metodi per il template:

Questo è il componente autonomo con i suoi moduli di importazione e l’iniezione di ‘DestroyRef’.

Il metodo ‘onFileInputChange(…)’ prende il parametro evento e memorizza la proprietà ‘files’ in una costante chiamata ‘files’. Quindi viene verificato il primo file e memorizzato nella proprietà del componente chiamata ‘file’.

Il metodo ‘upload()’ verifica la proprietà ‘file’ e crea il ‘FormData()’ per il caricamento del file. La costante ‘formData’ ha il tipo di dato (‘file’), il contenuto (‘this.file’) e il nome del file (‘this.file.name’) allegati. Quindi il ‘documentService’ viene utilizzato per inviare l’oggetto ‘FormData()’ al server. La funzione ‘takeUntilDestroyed(this.destroyRef)’ annulla la pipeline Rxjs dopo la distruzione del componente. Ciò rende molto comodo annullare le pipeline in Angular.

Backend

Il backend è un’applicazione Spring Boot con il framework Spring AI. Spring AI gestisce le richieste ai modelli OpenAI e alle richieste di Vector Database.

Configurazione del database Liquibase

La configurazione del database viene eseguita con Liquibase e lo script può essere trovato nel file db.changelog-1.xml:

Nel changeset 4 viene creata la tabella per l’entità document Jpa con la chiave primaria ‘id’. Il tipo di contenuto/dimensione è sconosciuto e per questo motivo è impostato su ‘blob’. Nel cambio 5 viene creata la sequenza per l’entità Jpa con le proprietà predefinite delle sequenze Hibernate 6 utilizzate da Spring Boot 3.x.

Nel cambio 6 viene creata la tabella ‘vector_store’ con una chiave primaria ‘id’ di tipo ‘uuid’ creata dall’estensione ‘uuid-ossp’. La colonna ‘content’ è di tipo ‘text’ (‘clob’ in altri database) per avere una dimensione flessibile. La colonna ‘metadata’ memorizza i metadati in un tipo ‘json’ per i documenti AIDocument. La colonna ’embedding’ memorizza il vettore di embedding con il numero di dimensioni OpenAI.

Nel cambio 7 viene impostato l’indice per la ricerca veloce della colonna ’embeddings’. A causa dei parametri limitati di ‘<createIndex …>’ di Liquibase, viene utilizzato direttamente ‘<sql>’ per crearlo.

Implementazione di Spring Boot / Spring AI

Il DocumentController per il frontend è così:

Il metodo ‘handleDocumentUpload(…)’ gestisce il file caricato con il ‘documentService’ al percorso ‘/rest/document/upload’.

Il metodo ‘getDocumentList()’ gestisce le richieste GET per le liste di documenti e rimuove il contenuto del documento per risparmiare sulla dimensione della risposta.

Il metodo ‘getDocumentContent(…)’ gestisce le richieste GET per il contenuto del documento. Carica il documento con il ‘documentService’ e mappa il ‘DocumentType’ al ‘MediaType’. Poi restituisce il contenuto e il tipo di contenuto e il browser apre il contenuto in base al tipo di contenuto.

Il metodo ‘postDocumentSearch(…)’ inserisce il contenuto della richiesta nell’oggetto ‘SearchDto’ e restituisce il risultato generato dall’IA della chiamata ‘documentService.queryDocuments(…)’

Il metodo ‘storeDocument(…)’ di DocumentService si presenta così:

Il metodo ‘storeDocument(…)’ salva il documento nel database relazionale. Successivamente, il documento viene convertito in un ‘ByteArrayResource’ e letto con il ‘TikaDocumentReader’ di Spring AI per trasformarlo in una lista di AIDocument. Successivamente, la lista di AIDocument viene flatmappata per suddividere i documenti in frammenti con il metodo ‘splitToTokenLimit(…)’ che vengono trasformati in nuovi AIDocument con l’id del documento memorizzato nella mappa dei metadati. L’id nei metadati consente di caricare l’entità del documento corrispondente per gli AIDocument. Successivamente, gli embedding per gli AIDocument vengono creati implicitamente con chiamate al metodo ‘documentVsRepository.add(…)’ che chiama il modello di incorporamento OpenAI e memorizza gli AIDocument con gli embedding nel database vettoriale. Infine, il risultato viene restituito.

Il metodo ‘queryDocument(…)’ si presenta così:

Il metodo carica innanzitutto i documenti che corrispondono meglio a ‘searchDto.getSearchString()’ dal database vettoriale. Per farlo, viene chiamato il modello di incorporamento OpenAI per trasformare la stringa di ricerca in un embedding e con tale embedding viene effettuata la query sul database vettoriale per ottenere gli AIDocument con la distanza più bassa (la distanza tra i vettori dell’embedding di ricerca e l’embedding del database). Quindi l’AIDocument con la distanza più bassa viene memorizzato nella variabile ‘mostSimilar’. Successivamente, tutti gli AIDocument dei frammenti di documento vengono raccolti corrispondendo l’id dell’entità del documento ai loro ‘id’ dei metadati. Viene creato il ‘systemMessage’ con il contenuto dei ‘documentChunks’ o di ‘mostSimilar’. Il metodo ‘getSystemMessage(…)’ li prende e riduce i frammenti di contenuto a una dimensione che i modelli OpenAI GPT possono gestire e restituisce il ‘Message’. Successivamente il ‘systemMessage’ e il ‘userMessage’ vengono convertiti in un ‘prompt’ che viene inviato con ‘aiClient.generate(prompt)’ al modello OpenAi GPT. Dopo ciò, la risposta dell’IA è disponibile e l’entità del documento viene caricata con l’id dei metadati dell’AIDocument ‘mostSimilar’. Viene quindi creato ‘AiResult’ con la stringa di ricerca, la risposta GPT, l’entità del documento e viene restituito.

Il repository del database vettoriale DocumentVsRepositoryBean con il ‘VectorStore’ di Spring AI si presenta così:

Il repository ha la proprietà ‘vectorStore’ che viene utilizzata per accedere al database vettoriale. Viene creato nel costruttore con i parametri iniettati con la chiamata ‘new PgVectorStore(…)’. La classe PgVectorStore è fornita come estensione del database vettoriale Postgresql. Ha l”embeddingClient’ per utilizzare il modello di incorporamento OpenAI e il ‘jdbcTemplate’ per accedere al database.

Il metodo ‘add(…)’ chiama il modello di incorporamento OpenAI e aggiunge gli AIDocument al database vettoriale.

I metodi ‘retrieve(…)’ interrogano il database vettoriale per ottenere gli embedding con le distanze più basse.

Conclusioni

Angular ha reso facile la creazione del front-end. I componenti autonomi con il caricamento ritardato hanno reso l’avvio iniziale più leggero. I componenti di Angular Material hanno aiutato molto nell’implementazione ed sono facili da usare.

Spring Boot con Spring AI ha reso facile l’utilizzo dei modelli di lingua grande. Spring AI fornisce il framework per nascondere la creazione degli embedding e fornisce un’interfaccia facile da usare per archiviare gli AIDocument in un database vettoriale (sono supportati diversi tipi di database). La creazione dell’embedding per il prompt di ricerca per caricare gli AIDocument più vicini è anche fatta per voi e l’interfaccia del database vettoriale è semplice. Le classi prompt di Spring AI facilitano anche la creazione del prompt per i modelli OpenAI GPT. La chiamata al modello viene effettuata con l’oggetto ‘aiClient’ iniettato e i risultati vengono restituiti.

Spring AI è un ottimo framework del team di Spring. Non ci sono stati problemi con la versione sperimentale.

Con Spring AI, i modelli di lingua grande sono ora facili da usare sui nostri stessi documenti.