Creazione di un flusso di dati semi-strutturati RAG con Langchain

Creazione di un flusso di dati semi-strutturati RAG con Langchain come ottimizzare la gestione dei dati per un'eccellente performance

Introduzione

Il Recupero del Generatore potenziato esiste da un po’ di tempo. Molti strumenti e applicazioni sono stati costruiti attorno a questo concetto, come i vettori di memorizzazione, i framework di recupero e LLM, facilitando il lavoro con documenti personalizzati, soprattutto con dati semistrutturati con Langchain. Lavorare con testi lunghi e densi non è mai stato così facile e divertente. Il convenzionale RAG funziona bene con file pesanti di testo non strutturato come DOC, PDF, ecc. Tuttavia, questo approccio non funziona bene con dati semistrutturati, come tabelle incorporate in PDF.

Mentre si lavora con dati semistrutturati, solitamente ci sono due preoccupazioni.

  • I metodi convenzionali di estrazione e suddivisione del testo non tengono conto delle tabelle nei PDF. Di solito le tabelle vengono spezzate, causando una perdita di informazioni.
  • L’inclusione di tabelle potrebbe non tradursi in una ricerca semantica precisa.

Quindi, in questo articolo, costruiremo una pipeline di generazione del recupero per dati semistrutturati con Langchain per affrontare queste due preoccupazioni con i dati semistrutturati.

Obiettivi di apprendimento

  • Comprendere la differenza tra dati strutturati, non strutturati e semistrutturati.
  • Un piccolo ripasso su Recupero Generato Potenziato e Langchain.
  • Imparare come costruire un recupero multi-vettoriale per gestire dati semistrutturati con Langchain.

Questo articolo è stato pubblicato come parte del Data Science Blogathon.

Tipi di Dati

Solitamente ci sono tre tipi di dati. Strutturati, semistrutturati e non strutturati.

  • Dati Strutturati: I dati strutturati sono dati standardizzati. I dati seguono uno schema predefinito, come righe e colonne. Database SQL, fogli di calcolo, frame di dati, ecc.
  • Dati Non Strutturati: I dati non strutturati, a differenza dei dati strutturati, non seguono alcun modello di dati. I dati sono casuali. Ad esempio, PDF, testi, immagini, ecc.
  • Dati Semistrutturati: È la combinazione dei tipi di dati precedenti. A differenza dei dati strutturati, non ha uno schema rigido predefinito. Tuttavia, i dati mantengono comunque un ordine gerarchico basato su alcuni indicatori, a differenza dei tipi non strutturati. Ad esempio, CSV, HTML, tabelle incorporate in PDF, XML, ecc.

Cosa è RAG?

RAG sta per Recupero Generato Potenziato. È il modo più semplice per fornire informazioni nuove ai grandi modelli di linguaggio. Quindi, facciamo un breve ripasso su RAG.

In una tipica pipeline di RAG, abbiamo fonti di conoscenza come file locali, pagine Web, database, ecc., un modello di incorporazione, un database vettoriale e un LLM. Raccogliamo i dati da varie fonti, suddividiamo i documenti, otteniamo le rappresentazioni dei pezzi di testo e le archiviamo in un database vettoriale. Ora, passiamo le rappresentazioni delle query al deposito vettoriale, recuperiamo i documenti dal deposito vettoriale e infine generiamo le risposte con LLM.

Questa è una panoramica di una RAG convenzionale e funziona bene con dati non strutturati come i testi. Tuttavia, quando si tratta di dati semistrutturati, ad esempio, tabelle incorporate in un PDF, spesso non riesce a funzionare bene. In questo articolo, impareremo come gestire queste tabelle incorporate.

Cosa è Langchain?

Langchain è un framework open-source per la creazione di applicazioni basate su LLM. Sin dal suo lancio, il progetto ha guadagnato ampia adozione tra gli sviluppatori di software. Fornisce una vasta gamma di strumenti e tecnologie unificate per costruire applicazioni di intelligenza artificiale più velocemente. Langchain comprende strumenti come archivi vettoriali, caricatori di documenti, recuperatori, modelli di incorporazione, separatori di testo, ecc. È una soluzione completa per la costruzione di applicazioni di intelligenza artificiale. Ma ci sono due valori fondamentali che lo distinguono.

  • LLM chains: Langchain fornisce catene multiple. Queste catene concatenano diversi strumenti per compiere un singolo compito. Ad esempio, ConversationalRetrievalChain concatena un LLM, un recuperatore di archivio vettoriale, un modello di embedding e un oggetto di cronologia chat per generare risposte per una query. Gli strumenti sono codificati rigidamente e devono essere definiti in modo esplicito.
  • Agenti LLM: A differenza delle catene LLM, gli agenti AI non hanno strumenti codificati rigidamente. Invece di concatenare un’offerta dopo l’altra, lasciamo che il LLM decida quale selezionare e quando in base alle descrizioni testuali degli strumenti. Questo lo rende ideale per la creazione di applicazioni LLM complesse che coinvolgono ragionamento e processo decisionale.

Costruzione del pipeline RAG

Ora che abbiamo una panoramica dei concetti. Discutiamo l’approccio alla costruzione del pipeline. Lavorare con dati semistrutturati può essere complicato in quanto non segue uno schema convenzionale per l’archiviazione delle informazioni. E per lavorare con dati non strutturati, abbiamo bisogno di strumenti specializzati appositamente per l’estrazione delle informazioni. Quindi, in questo progetto, utilizzeremo uno strumento chiamato “non strutturato”; è uno strumento open source per l’estrazione di informazioni da diversi formati di dati non strutturati, come tabelle in PDF, HTML, XML, ecc. Non strutturato utilizza Tesseract e Poppler sotto al cofano per elaborare più formati di dati in file. Quindi, impostiamo il nostro ambiente e installiamo le dipendenze prima di passare alla parte di codice.

Imposta l’ambiente di sviluppo

Come qualsiasi altro progetto Python, apri un ambiente Python e installa Poppler e Tesseract.

!sudo apt install tesseract-ocr!sudo apt-get install poppler-utils

Ora, installa le dipendenze di cui avremo bisogno nel nostro progetto.

!pip install "unstructured[all-docs]" Langchain openai

Estrarre utilizzando “non strutturato”

Ora che abbiamo installato le dipendenze, estraiamo i dati da un file PDF.

from unstructured.partition.pdf import partition_pdfpdf_elements = partition_pdf(    "mistral7b.pdf",     chunking_strategy="by_title",     extract_images_in_pdf=True,     max_characters=3000,    new_after_n_chars=2800,    combine_text_under_n_chars=2000,    image_output_dir_path="./"    )

Eseguendo questo comando, installeremo diverse dipendenze come YOLOx che sono necessarie per l’OCR e restituirà tipi di oggetti in base ai dati estratti. Abilitando ​​”extract_images_in_pdf”, consentiremo a “non strutturato” di estrarre le immagini incorporate dai file. Questo può aiutare nell’implementazione di soluzioni multimodali.

Ora, esploriamo le categorie di elementi dal nostro PDF.

# Crea un dizionario per memorizzare il conteggio di ogni tipocategory_counts = {}for element in pdf_elements:    category = str(type(element))    if category in category_counts:        category_counts[category] += 1    else:        category_counts[category] = 1# unique_categories conterrà gli elementi uniciunique_categories = set(category_counts.keys())category_counts

Eseguendo questo comando verranno visualizzate le categorie degli elementi e il loro conteggio.

Ora, separiamo gli elementi per una gestione più semplice. Creiamo un tipo Element che eredita dal tipo Document di Langchain. Questo serve per garantire dati più organizzati, che sono più facili da gestire.

from unstructured.documents.elements import CompositeElement, Tablefrom langchain.schema import Documentclass Element(Document):    type: str# Categorizza per tipocategorized_elements = []for element in pdf_elements:    if isinstance(element, Table):        categorized_elements.append(Element(type="table", page_content=str(element)))    elif isinstance(element, CompositeElement):        categorized_elements.append(Element(type="text", page_content=str(element)))# Tabellestable_elements = [e for e in categorized_elements if e.type == "table"]# Testitext_elements = [e for e in categorized_elements if e.type == "text"]

Recovered multi-vettoriale

Abbiamo elementi di tabelle e testo. Ora, ci sono due modi per gestirli. Possiamo archiviare gli elementi grezzi in un archivio di documenti o archiviare le sintesi dei testi. Le tabelle potrebbero rappresentare una sfida per la ricerca semantica; in tal caso, creiamo le sintesi delle tabelle e le archiviamo in un archivio di documenti insieme alle tabelle grezze. Per realizzare questo, utilizzeremo “Recovered multi-vettoriale”. Questo recuperatore gestirà un archivio vettoriale in cui archiviamo gli embedding di testi di sintesi e un semplice archivio di documenti in memoria per archiviare i documenti grezzi.

Prima di tutto, costruisci una catena di sintesi per riassumere i dati di tabella e di testo che abbiamo estratto in precedenza.

from langchain.chat_models import cohere
from langchain.prompts import ChatPromptTemplate
from langchain.schema.output_parser import StrOutputParser

prompt_text = """Sei un assistente incaricato di riassumere tabelle e testi.
Fornisci un riassunto conciso della tabella o del testo.
Chunk di tabella o testo: {element} """
prompt = ChatPromptTemplate.from_template(prompt_text)

model = cohere.ChatCohere(cohere_api_key="your_key")

summarize_chain = {"element": lambda x: x} | prompt | model | StrOutputParser()

tables = [i.page_content for i in table_elements]
table_summaries = summarize_chain.batch(tables, {"max_concurrency": 5})

texts = [i.page_content for i in text_elements]
text_summaries = summarize_chain.batch(texts, {"max_concurrency": 5})

Ho utilizzato Cohere LLM per riassumere i dati; è possibile utilizzare modelli OpenAI come GPT-4. Modelli migliori produrranno risultati migliori. A volte, i modelli potrebbero non catturare perfettamente i dettagli della tabella. Pertanto, è meglio utilizzare modelli capaci.

Ora, creiamo il MultivectorRetriever.

from langchain.retrievers import MultiVectorRetriever
from langchain.prompts import ChatPromptTemplate
import uuid
from langchain.embeddings import OpenAIEmbeddings
from langchain.schema.document import Document
from langchain.storage import InMemoryStore
from langchain.vectorstores import Chroma

# Il vectorstore da utilizzare per indicizzare i chunk figlio
vectorstore = Chroma(collection_name="collection",
                     embedding_function=OpenAIEmbeddings(openai_api_key="api_key"))

# Livello di archiviazione per i documenti genitori
store = InMemoryStore()
id_key = "id"

# Il retriever
retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    docstore=store,
    id_key=id_key,
)

# Aggiungi testi
doc_ids = [str(uuid.uuid4()) for _ in texts]
summary_texts = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})
    for i, s in enumerate(text_summaries)
]
retriever.vectorstore.add_documents(summary_texts)
retriever.docstore.mset(list(zip(doc_ids, texts)))

# Aggiungi tabelle
table_ids = [str(uuid.uuid4()) for _ in tables]
summary_tables = [
    Document(page_content=s, metadata={id_key: table_ids[i]})
    for i, s in enumerate(table_summaries)
]
retriever.vectorstore.add_documents(summary_tables)
retriever.docstore.mset(list(zip(table_ids, tables)))

Abbiamo utilizzato Chroma vector store per archiviare le sintesi delle embeddings di testi e tabelle e uno strato di archiviazione in memoria per archiviare i dati grezzi.

RAG

Ora che il nostro retriever è pronto, possiamo creare una pipeline RAG utilizzando il Linguaggio di Espressione di Langchain.

from langchain.schema.runnable import RunnablePassthrough

# Template di prompt
template = """Rispondi alla domanda basandoti solo sul contesto seguente, che può includere testo e tabelle::
Contesto: {context}
Domanda: {question}"""

prompt = ChatPromptTemplate.from_template(template)

# Modello LLM
model = ChatOpenAI(temperature=0.0, openai_api_key="api_key")

# Pipeline RAG
chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

Ora possiamo fare domande e ricevere risposte basate sulle embeddings recuperate dal vector store.

chain.invoke(input = "Qual è il punteggio MT bench di Llama 2 e Mistral 7B Instruct??")

Conclusione

Molte informazioni rimangono nascoste nel formato dei dati semi-strutturati. Ed è difficile estrarre e eseguire una RAG convenzionale su questi dati. In questo articolo, siamo passati dall’estrazione di testi e tabelle incorporate nel PDF alla creazione di un multi-vector retriever e una pipeline RAG con Langchain. Ecco quindi i punti salienti dell’articolo.

Punti chiave

  • La RAG convenzionale spesso si trova ad affrontare sfide nel trattare con dati semi-strutturati, come la suddivisione delle tabelle durante la divisione del testo e le ricerche semantiche imprecise.
  • Unstructured, uno strumento open-source per dati semi-strutturati, può estrarre tabelle incorporate da PDF o dati semi-strutturati simili.
  • Con Langchain, possiamo costruire un multi-vector retriever per archiviare tabelle, testi e riassunti in archivi documentali per una ricerca semantica migliore.

Domande Frequenti

I media mostrati in questo articolo non sono di proprietà di Analytics Vidhya e sono utilizzati a discrezione dell’autore.