In soli sei mesi, ChatGPT di OpenAI è diventato parte integrante delle nostre vite. Non è più solo limitato alla tecnologia; persone di tutte le età e professioni, dagli studenti agli scrittori, lo utilizzano ampiamente. Questi modelli di chat eccellono per accuratezza, velocità e conversazioni simili a quelle umane. Sono pronti a svolgere un ruolo significativo in vari campi, non solo nella tecnologia.
Sono emersi strumenti open source come AutoGPTs, BabyAGI e Langchain, sfruttando la potenza dei modelli linguistici. Automatizza le attività di programmazione con i prompt, collega i modelli linguistici alle origini dati e crea applicazioni AI più velocemente che mai. Langchain è uno strumento di domande e risposte abilitato per ChatGPT per i PDF, che lo rende uno sportello unico per la creazione di applicazioni AI.
Obiettivi formativi
Crea un'interfaccia chatbot utilizzando Gradio
Estrai testi da pdf e crea embedding
Memorizza gli embedding nel database dei vettori Chroma
Invia query al back-end (catena Langchain)
Esegui ricerche semantiche sui testi per trovare fonti di dati pertinenti
Invia dati a LLM (ChatGPT) e ricevi risposte sul chatbot
Il Langchain semplifica l'esecuzione di tutti questi passaggi in poche righe di codice. Dispone di wrapper per più servizi, inclusi modelli di embedding, modelli di chat e database vettoriali.
Cos'è Langchain?
Langchain è uno strumento open source scritto in Python che aiuta a connettere dati esterni a Large Language Models. Rende i modelli di chat come GPT-4 o GPT-3.5 più agentici e sensibili ai dati. Quindi, in un certo senso, Langchain fornisce un modo per fornire agli LLM nuovi dati su cui non è stato addestrato. Langchain fornisce molte catene che astraggono le complessità nell'interazione con i modelli linguistici. Abbiamo anche bisogno di molti altri strumenti, come i modelli per la creazione di incorporamenti di vettori e database di vettori per memorizzare i vettori. Prima di procedere oltre, diamo una rapida occhiata agli Embedding di testo. Cosa sono e perché sono importanti?
Serie di articoli su LangChain e python
Embedding di testo.
Gli Embedding di testo sono il cuore e l'anima di Large Language Operations. Tecnicamente, possiamo lavorare con i modelli linguistici con il linguaggio naturale, ma l'archiviazione e il recupero del linguaggio naturale è altamente inefficiente. Ad esempio, in questo progetto, dovremo eseguire operazioni di ricerca ad alta velocità su grandi blocchi di dati. È impossibile eseguire tali operazioni sui dati in linguaggio naturale. Per renderlo più efficiente, dobbiamo trasformare i dati di testo in forme vettoriali. Esistono modelli ML dedicati per la creazione di incorporamenti o embedding da testi. I testi vengono convertiti in vettori multidimensionali. Una volta incorporati, possiamo raggruppare, ordinare, cercare e altro su questi dati. Possiamo calcolare la distanza tra due frasi per sapere quanto sono strettamente correlate. E la parte migliore è che queste operazioni non si limitano solo a parole chiave come le tradizionali ricerche nel database, ma catturano piuttosto la vicinanza semantica di due frasi. Questo lo rende molto più potente, grazie al Machine Learning.
Strumenti Langchain
Langchain ha wrapper per tutti i principali database vettoriali come Chroma, Redis, Pinecone, Alpine db e altri. E lo stesso vale per gli LLM, insieme ai modelli OpeanAI, supporta anche i modelli di Cohere, GPT4ALL, un'alternativa open source per i modelli GPT. Per gli incorporamenti, fornisce wrapper per gli incorporamenti OpeanAI, Cohere e HuggingFace. Puoi anche utilizzare i tuoi modelli di incorporamento personalizzati.
Quindi, in breve, Langchain è un meta-strumento che elimina molte complicazioni dell'interazione con le tecnologie sottostanti, il che rende più facile per chiunque creare rapidamente applicazioni AI.
In questo articolo, utilizzeremo il modello di incorporamento OpeanAI per la creazione di incorporamenti. Se desideri distribuire un'app AI per gli utenti finali, considera l'utilizzo di qualsiasi modello Opensource, come i modelli Huggingface o il codificatore di frasi universale di Google.
Per archiviare i vettori, utilizzeremo Chroma DB , un database di archiviazione di vettori open source. Sentiti libero di esplorare altri database come Alpine, Pinecone e Redis. Langchain ha wrapper per tutti questi negozi vettoriali.
Per creare una catena Langchain, utilizzeremo ConversationalRetrievalChain (), ideale per conversazioni con modelli di chat con cronologia (per mantenere il contesto della conversazione). Controlla la loro documentazione ufficiale relativa alle diverse catene LLM.
Serie di articoli su LangChain e python
Configurare l'ambiente di sviluppo per langchain
Ci sono parecchie librerie che useremo. Quindi, installali in anticipo. Per creare un ambiente di sviluppo fluido e ordinato, utilizza gli ambienti virtuali o Docker .
gradio = "^3.27.0"
openai = "^0.27.4"
langchain = "^0.0.148"
chromadb = "^0.3.21"
tiktoken = "^0.3.3"
pypdf = "^3.8.1"
pymupdf = "^1.22.2"
Ora, importa queste librerie
import gradio as gr
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chains import ConversationalRetrievalChain
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import PyPDFLoader
import os
import fitz
from PIL import Image
Costruisci l'interfaccia di chat
L'interfaccia dell'applicazione avrà due funzionalità principali, una è un'interfaccia di chat e l'altra rende la pagina pertinente del PDF come un'immagine. Oltre a questo, una casella di testo per accettare le chiavi API OpenAI dagli utenti finali. L'articolo discute gli aspetti fondamentali di Gradio. Prenderemo in prestito molte cose da questo articolo.
La classe Gradio Blocks ci consente di creare un'app Web. Le classi Row e Columns consentono di allineare più componenti nell'app Web. Li useremo per personalizzare l'interfaccia web.
with gr.Blocks() as demo:
# Create a Gradio block
with gr.Column():
with gr.Row():
with gr.Column(scale=0.8):
api_key = gr.Textbox(
placeholder='Enter OpenAI API key',
show_label=False,
interactive=True
).style(container=False)
with gr.Column(scale=0.2):
change_api_key = gr.Button('Change Key')
with gr.Row():
chatbot = gr.Chatbot(value=[], elem_id='chatbot').style(height=650)
show_img = gr.Image(label='Upload PDF', tool='select').style(height=680)
with gr.Row():
with gr.Column(scale=0.70):
txt = gr.Textbox(
show_label=False,
placeholder="Enter text and press enter"
).style(container=False)
with gr.Column(scale=0.15):
submit_btn = gr.Button('Submit')
with gr.Column(scale=0.15):
btn = gr.UploadButton("📁 Upload a PDF", file_types=[".pdf"]).style()
L'interfaccia è semplice con pochi componenti.
Esso ha:
Un'interfaccia di chat per comunicare con il PDF.
Un componente per il rendering di pagine PDF pertinenti.
Una casella di testo per accettare la chiave API e un pulsante di modifica della chiave.
Una casella di testo per porre domande e un pulsante di invio.
Un pulsante per caricare i file.
Ecco uno screshot di come dovresti vederel 'interfaccia utente web.
La parte frontend della nostra applicazione è completa. Passiamo al backend.
Backend di ChatGPT per i PDF con Langchain
Innanzitutto, delineamo i processi con cui ci occuperemo.
Gestisci il PDF caricato e la chiave API OpenAI
Estrai testi da PDF e crea incorporamenti di testo utilizzando gli incorporamenti OpenAI.
Memorizza gli incorporamenti vettoriali nell'archivio vettoriale ChromaDB.
Crea una catena di recupero conversazionale con Langchain.
Crea incorporamenti di testo interrogato ed esegui una ricerca per somiglianza su documenti incorporati.
Invia i documenti pertinenti al modello di chat OpenAI (gpt-3.5-turbo).
Recupera la risposta e riproducila in streaming sull'interfaccia utente della chat.
Renderizza la pagina PDF pertinente sull'interfaccia utente Web.
Queste sono la panoramica della nostra applicazione. Iniziamo a costruirlo.
Gestione eventi principali di ChatGPT per i PDF con Langchain
Quando viene eseguita un'azione specifica sull'interfaccia utente Web, questi eventi vengono attivati. Quindi, gli eventi rendono la web app interattiva e dinamica. Gradio ci permette di definire eventi con codici Python.
Gradio Events utilizza le variabili dei componenti che abbiamo definito in precedenza per comunicare con il backend. Definiremo alcuni eventi di cui abbiamo bisogno per la nostra applicazione. Questi sono
Invia evento chiave API : premendo Invio dopo aver incollato la chiave API si attiverà questo evento.
Cambia chiave : questo ti permetterà di fornire una nuova chiave API
Inserisci query : invia query di testo al chatbot
Carica file : consente all'utente finale di caricare un file PDF
with gr.Blocks() as demo:
# Create a Gradio block
with gr.Column():
with gr.Row():
with gr.Column(scale=0.8):
api_key = gr.Textbox(
placeholder='Enter OpenAI API key',
show_label=False,
interactive=True
).style(container=False)
with gr.Column(scale=0.2):
change_api_key = gr.Button('Change Key')
with gr.Row():
chatbot = gr.Chatbot(value=[], elem_id='chatbot').style(height=650)
show_img = gr.Image(label='Upload PDF', tool='select').style(height=680)
with gr.Row():
with gr.Column(scale=0.70):
txt = gr.Textbox(
show_label=False,
placeholder="Enter text and press enter"
).style(container=False)
with gr.Column(scale=0.15):
submit_btn = gr.Button('Submit')
with gr.Column(scale=0.15):
btn = gr.UploadButton("📁 Upload a PDF", file_types=[".pdf"]).style()
# Set up event handlers
# Event handler for submitting the OpenAI API key
api_key.submit(fn=set_apikey, inputs=[api_key], outputs=[api_key])
# Event handler for changing the API key
change_api_key.click(fn=enable_api_box, outputs=[api_key])
# Event handler for uploading a PDF
btn.upload(fn=render_first, inputs=[btn], outputs=[show_img])
# Event handler for submitting text and generating response
submit_btn.click(
fn=add_text,
inputs=[chatbot, txt],
outputs=[chatbot],
queue=False
).success(
fn=generate_response,
inputs=[chatbot, txt, btn],
outputs=[chatbot, txt]
).success(
fn=render_file,
inputs=[btn],
outputs=[show_img]
)
Finora non abbiamo definito le nostre funzioni chiamate all'interno dei gestori di eventi di cui sopra. Successivamente, definiremo tutte queste funzioni per creare un'app Web funzionale.
Gestisci le chiavi API
La gestione delle chiavi API di un utente è importante poiché l'intera cosa funziona secondo il principio BYOK (Bring Your Own Key). Ogni volta che un utente invia una chiave, la casella di testo deve diventare immutabile con un prompt che suggerisce che la chiave è impostata. E quando viene attivato l'evento "Cambia chiave", la scatola deve essere in grado di ricevere input.
Per fare ciò, definire due variabili globali.
enable_box = gr.Textbox.update(value=None,placeholder= 'Upload your OpenAI API key',interactive=True)
disable_box = gr.Textbox.update(value = 'OpenAI API key is Set',interactive=False)
Definiamo le funzioni
def set_apikey(api_key):
os.environ['OPENAI_API_KEY'] = api_key
return disable_box
def enable_api_box():
return enable_box
La funzione set_apikey accetta un input di stringa e restituisce la variabile disable_box, che rende la casella di testo immutabile dopo l'esecuzione. Nella sezione Gradio Events, abbiamo definito l'api_key Submit Event, che chiama la funzione set_apikey. Impostiamo la chiave API come variabile di ambiente utilizzando la libreria del sistema operativo.
Facendo clic sul pulsante Cambia chiave API viene restituita la variabile enable_box, che abilita nuovamente la mutabilità della casella di testo.
Creiamo la catena di Langchain
Questo è il passo più importante. Questo passaggio comporta l'estrazione di testi e la creazione di incorporamenti e la loro memorizzazione in archivi vettoriali. Grazie a Langchain, che fornisce wrapper per più servizi semplificando le cose. Quindi, definiamo la funzione.
def process_file(self,file):
loader = PyPDFLoader(file.name)
documents = loader.load()
pattern = r"/([^/]+)$"
match = re.search(pattern, file.name)
file_name = match.group(1)
return documents, file_name
def build_chain(self, file):
documents, file_name = self.process_file(file)
#Load embeddings model
embeddings = OpenAIEmbeddings(openai_api_key=self.OPENAI_API_KEY)
pdfsearch = Chroma.from_documents(documents, embeddings, collection_name= file_name,)
chain = ConversationalRetrievalChain.from_llm(ChatOpenAI(temperature=0.0, openai_api_key=self.OPENAI_API_KEY), retriever=pdfsearch.as_retriever(search_kwargs={"k": 1}),return_source_documents=True,)
return chain
Creato un controllo se la chiave API è impostata o meno. Ciò genererà un errore sul front-end se la chiave non è impostata.
Funzione Carica il file PDF usando PyPDFLoader
Funzionalità di incorporamento definita con OpenAIEmbeddings.
Creato un archivio vettoriale dall'elenco di testi dal PDF
Definita una catena con chatOpenAI (per impostazione predefinita ChatOpenAI usa gpt-3.5-turbo) e un base retriever (usa una ricerca per similarità).
Genera le risposte
Una volta creata la catena, chiameremo la catena e invieremo le nostre query. Invia una cronologia della chat insieme alle query per mantenere il contesto delle conversazioni e trasmettere le risposte all'interfaccia della chat. Definiamo la funzione.
def generate_response(history, query, btn):
if not file:
raise gr.Error(message='Upload a PDF')
chain = app(file)
result = chain({"question": query, 'chat_history':app.chat_history},return_only_outputs=True)
app.chat_history += [(query, result["answer"])]
app.N = list(result['source_documents'][0])[1][1]['page']
for char in result['answer']:
history[-1][-1] += char
yield history,''
Genera un errore se non è stato caricato alcun PDF.
Chiama la funzione process_file solo una volta.
Invia query e cronologia chat alla catena
Recupera il numero di pagina della risposta più pertinente.
Rendi le risposte al front-end.
Renderizza l'immagine di un file PDF
Il passaggio finale consiste nel rendere l'immagine del file PDF con la risposta più pertinente. Possiamo usare le librerie PyMuPdf e PIL per renderizzare le immagini del documento.
def render_file(file):
global N# Open the PDF document using fitz
doc = fitz.open(file.name)# Get the specific page to render
page = doc[N]# Render the page as a PNG image with a resolution of 300 DPI
pix = page.get_pixmap(matrix=fitz.Matrix(300/72, 300/72))# Create an Image object from the rendered pixel data
image = Image.frombytes('RGB', [pix.width, pix.height], pix.samples)# Return the rendered imagereturn image
Apri il file con Fitz di PyMuPdf.
Ottieni la pagina pertinente.
Ottieni la mappa dei pixel per la pagina.
Crea l'immagine dalla classe Image di PIL.
Questo è tutto ciò che dobbiamo fare per un'app Web funzionale per chattare con qualsiasi PDF.
Mettiamo tutto insieme
from typing import Any
import gradio as gr
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains import ConversationalRetrievalChain
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import PyPDFLoader
import fitz
from PIL import Image
import chromadb
import re
import uuid
enable_box = gr.Textbox.update(value=None,placeholder= 'Upload your OpenAI API key',interactive=True)
disable_box = gr.Textbox.update(value = 'OpenAI API key is Set',interactive=False)
def set_apikey(api_key):
app.OPENAI_API_KEY = api_key
return disable_box
def enable_api_box():
return enable_box
def add_text(history, text):
if not text:
raise gr.Error('enter text')
history = history + [(text,'')]
return history
class my_app:
def __init__(self, OPENAI_API_KEY= None ) -> None:
self.OPENAI_API_KEY = OPENAI_API_KEY
self.chain = None
self.chat_history = []
self.N = 0
self.count = 0
def __call__(self, file) -> Any:
if self.count==0:
print('This is here')
self.build_chain(file)
self.count+=1
return self.chain
def chroma_client(self):
#create a chroma client
client = chromadb.Client()
#create a collecyion
collection = client.get_or_create_collection(name="my-collection")
return client
def process_file(self,file):
loader = PyPDFLoader(file.name)
documents = loader.load()
pattern = r"/([^/]+)$"
match = re.search(pattern, file.name)
file_name = match.group(1)
return documents, file_name
def build_chain(self, file):
documents, file_name = self.process_file(file)
#Load embeddings model
embeddings = OpenAIEmbeddings(openai_api_key=self.OPENAI_API_KEY)
pdfsearch = Chroma.from_documents(documents, embeddings, collection_name= file_name,)
chain = ConversationalRetrievalChain.from_llm(ChatOpenAI(temperature=0.0, openai_api_key=self.OPENAI_API_KEY),
retriever=pdfsearch.as_retriever(search_kwargs={"k": 1}),
return_source_documents=True,)
return chain
def get_response(history, query, file):
if not file:
raise gr.Error(message='Upload a PDF')
chain = app(file)
result = chain({"question": query, 'chat_history':app.chat_history},return_only_outputs=True)
app.chat_history += [(query, result["answer"])]
app.N = list(result['source_documents'][0])[1][1]['page']
for char in result['answer']:
history[-1][-1] += char
yield history,''
def render_file(file):
doc = fitz.open(file.name)
page = doc[app.N]
#Render the page as a PNG image with a resolution of 300 DPI
pix = page.get_pixmap(matrix=fitz.Matrix(300/72, 300/72))
image = Image.frombytes('RGB', [pix.width, pix.height], pix.samples)
return image
def render_first(file):
doc = fitz.open(file.name)
page = doc[0]
#Render the page as a PNG image with a resolution of 300 DPI
pix = page.get_pixmap(matrix=fitz.Matrix(300/72, 300/72))
image = Image.frombytes('RGB', [pix.width, pix.height], pix.samples)
return image,[]
app = my_app()
with gr.Blocks() as demo:
state = gr.State(uuid.uuid4().hex)
with gr.Column():
with gr.Row():
with gr.Column(scale=0.8):
api_key = gr.Textbox(placeholder='Enter OpenAI API key', show_label=False, interactive=True).style(container=False)
with gr.Column(scale=0.2):
change_api_key = gr.Button('Change Key')
with gr.Row():
chatbot = gr.Chatbot(value=[], elem_id='chatbot').style(height=650)
show_img = gr.Image(label='Upload PDF', tool='select' ).style(height=680)
with gr.Row():
with gr.Column(scale=0.60):
txt = gr.Textbox(
show_label=False,
placeholder="Enter text and press enter",
).style(container=False)
with gr.Column(scale=0.20):
submit_btn = gr.Button('submit')
with gr.Column(scale=0.20):
btn = gr.UploadButton("📁 upload a PDF", file_types=[".pdf"]).style()
api_key.submit(fn=set_apikey, inputs=[api_key], outputs=[api_key,])
change_api_key.click(fn= enable_api_box,outputs=[api_key])
btn.upload(fn=render_first, inputs=[btn], outputs=[show_img,chatbot],)
submit_btn.click(fn=add_text, inputs=[chatbot,txt], outputs=[chatbot, ], queue=False).success(fn=get_response,inputs = [chatbot, txt, btn],
outputs = [chatbot,txt]).success(fn=render_file,inputs = [btn], outputs=[show_img])
demo.queue()
demo.launch()
Ora che abbiamo configurato tutto, lanciamo la nostra applicazione.
È possibile avviare l'applicazione in modalità debug con il seguente comando
gradio app.py
Altrimenti, puoi anche semplicemente eseguire l'applicazione con il comando Python.
Di seguito è riportato il risultato finale.
Articolo molto interessante.