Costruire un sistema RAG multimodale basato su AI con Docling e Granite

Autori

BJ Hargrave

Open Source Developer, STSM

Erika Russi

Data Scientist

IBM

In questo tutorial, utilizzerai Docling di IBM e IBM Granite vision open source, embedding basati su testo e modelli di IA generativa per creare un sistema RAG. Questi modelli sono disponibili tramite vari framework open source. In questo tutorial, useremo Replicate per connetterci ai modelli IBM Granite vision e ai modelli di AI generativa, e HuggingFace per connetterci al modello di embedding.

Retrieval-augmented generation multimodale

La retrieval-augmented generation (RAG) è una tecnica utilizzata con modelli linguistici di grandi dimensioni (LLM) per collegare il modello a una base di conoscenza di informazioni al di fuori dei dati su cui l'LLM è stato addestrato senza dover effettuare una messa a punto. La RAG tradizionale è limitata a casi d'uso basati su testo, come la sintesi e i chatbot.

La RAG multimodale può utilizzare LLM multimodali (MLLM) per elaborare informazioni provenienti da diversi tipi di dati da includere come parte della knowledge base esterna utilizzata nella RAG. I dati multimodali possono includere testo, immagini, audio, video o altre forme. Tra i più popolari LLM multimodali troviamo Gemini di Google, Llama 3.2 di Meta e GPT-4 e GPT-4o di OpenAI.

Per questa ricetta utilizzerai un modello IBM Granite in grado di elaborare diverse modalità. Creerai un sistema AI per rispondere alle domande degli utenti in tempo reale da dati non strutturati in un PDF.

Panoramica del tutorial

Benvenuti a questo tutorial su Granite. In questo tutorial, impareremo come sfruttare la potenza di strumenti avanzati per costruire una pipeline RAG multimodale basata sull'AI. Questo tutorial ti guiderà attraverso i seguenti processi:

  • Preelaborazione dei documenti: Impara a gestire i documenti da varie fonti, ad analizzarli e a trasformarli in formati utilizzabili e a memorizzare in database vettoriale, utilizzando Docling. Utilizzerai un Granite MLLM per generare descrizioni delle immagini presenti nei documenti.
  • RAG: scopri come collegare LLM come Granite con knowledge base esterne per migliorare le risposte alle domande e generare insight preziosi.
  • LangChain per l'integrazione del workflow: scopri come utilizzare LangChain per semplificare e orchestrare i workflow di elaborazione e recupero documentali, consentendo un'interazione fluida tra i diversi componenti del sistema.

Questo tutorial utilizza tre tecnologie all'avanguardia:

  1. Docling: un toolkit open source utilizzato per analizzare e convertire documenti.
  2. Granite: un LLM all'avanguardia che offre robuste funzionalità di linguaggio naturale e un modello di linguaggio visivo che consente la generazione di immagini in testo.
  3. LangChain: un potente framework utilizzato per creare applicazioni basate su modelli linguistici, progettato per semplificare workflow e integrare senza problemi strumenti esterni.

Al termine di questo tutorial sarai in grado di:

  • Acquisisci competenze nella pre-elaborazione dei documenti, nella suddivisione in blocchi e nella comprensione delle immagini.
  • Integra database vettoriali per migliorare le funzionalità.
  • Utilizza la RAG per eseguire un recupero efficiente e preciso dei dati per le applicazioni del mondo reale.

Questo tutorial è pensato per sviluppatori, ricercatori ed appassionati di AI che desiderano approfondire le proprie conoscenze sulla gestione documentale e sulle tecniche avanzate di elaborazione del linguaggio naturale (NLP). Il tutorial è disponibile anche nel Granite Snack Cookbook GitHub della IBM Granite Community sotto forma di Jupyter Notebook.

Prerequisiti

  • Familiarità con la programmazione Python.
  • Comprensione di base degli LLM, concetti di NLP e computer vision.

Passaggi

Passaggio 1: Installa le dipendenze

! echo "::group::Install Dependencies"
%pip install uv
! uv pip install git+https://github.com/ibm-granite-community/utils.git \
    transformers \
    pillow \
    langchain_classic \
    langchain_core \
    langchain_huggingface sentence_transformers \
    langchain_milvus 'pymilvus[milvus_lite]' \
    docling \
    'langchain_replicate @ git+https://github.com/ibm-granite-community/langchain-replicate.git'
! echo "::endgroup::"

Passaggio 2: Selezione dei modelli AI

Registrazione

Per visualizzare alcune informazioni di registrazione, possiamo configurare il livello di registro INFO.

NOTA: se vuoi, puoi saltare l'esecuzione di questa cella.

import logging

logging.basicConfig(level=logging.INFO)


Carica i modelli Granite

Specifica il modello di embedding da utilizzare per generare vettori di embedding di testo. Qui useremo uno dei modelli di Granite Embeddings

Per usare un modello di embedding diverso, sostituisci questa cella di codice con una di queste istruzioni del modello di Embedding.

from langchain_huggingface import HuggingFaceEmbeddings
from transformers import AutoTokenizer

embeddings_model_path = “ibm-granite/granite-embedding-30m-english”
embeddings_model = HuggingFaceEmbeddings(
    model_name=embeddings_model_path,
)
embeddings_tokenizer = AutoTokenizer.from_pretrained(embeddings_model_path)

 

Specifica l'MLLM da usare per la comprensione delle immagini. Useremo il modello Granite Vision. 

from ibm_granite_community.notebook_utils import get_env_var
from langchain_community.llms import Replicate
from transformers import AutoProcessor

vision_model_path = “ibm-granite/granite-vision-3.2-2b”
vision_model = Replicate(
    model=vision_model_path,
    replicate_api_token=get_env_var(“REPLICATE_API_TOKEN”),
    model_kwargs={
        “max_tokens”: embeddings_tokenizer.max_len_single_sentence, # Set the maximum number of tokens to generate as output.
        “min_tokens”: 100, # Set the minimum number of tokens to generate as output.
    },
)
vision_processor = AutoProcessor.from_pretrained(vision_model_path)

 

Specifica il modello linguistico da utilizzare per l'operazione di generazione RAG.  Qui usiamo il client Replicate LangChain per collegarci a un modello Granite dall'org ibm-granite su Replicate.

Per la configurazione con Replicate, vedi Guida introduttiva a Replicate. Per connettersi a un modello su un provider diverso da Replicate, sostituisci questa cella di codice con una delle istruzioni del componente LLM.

Per connettersi a un modello su un provider diverso da Replicate, sostituisci questa cella di codice con una delle istruzioni del componente LLM.

from langchain_replicate import ChatReplicate

model_path = "ibm-granite/granite-4.0-h-small"
model = ChatReplicate(
    model=model_path,
    replicate_api_token=get_env_var("REPLICATE_API_TOKEN"),
    model_kwargs={
        "max_tokens": 1000, # Set the maximum number of tokens to generate as output.
        "min_tokens": 100, # Set the minimum number of tokens to generate as output.
    },
)

Passaggio 3: Preparare i documenti per il database vettoriale

In questo esempio, a partire da un insieme di documenti sorgente, utilizziamo Docling per convertire i documenti in testo e immagini. Il testo viene poi suddiviso in parti. Le immagini vengono elaborate dall'MLLM per generare riepiloghi delle immagini.

Usa Docling per scaricare i documenti e convertire in testo e immagini

Docling scaricherà i documenti PDF e li elaborerà così da poter ottenere il testo e le immagini contenuti nei documenti. Nel PDF sono presenti diversi tipi di dati, tra cui testo, tabelle, grafici e immagini.

from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions

pdf_pipeline_options = PdfPipelineOptions(
    do_ocr=False,
    generate_picture_images=True,
)
format_options = {
    InputFormat.PDF: PdfFormatOption(pipeline_options=pdf_pipeline_options),
}
converter = DocumentConverter(format_options=format_options)

sources = [
    “https://midwestfoodbank.org/images/AR_2020_WEB2.pdf”,
]
conversions = { source: converter.convert(source=source).document for source in sources }

 

Una volta elaborati i documenti, elaboriamo ulteriormente gli elementi di testo presenti nei documenti. Li suddividiamo in dimensioni appropriate per il modello di embedding che stiamo usando. Un elenco di documenti LangChain viene creato dai blocchi di testo.

from docling_core.transforms.chunker.hybrid_chunker import HybridChunker
from docling_core.types.doc.document import TableItem
from langchain_core.documents import Document

doc_id = 0
texts: list[Document] = []
for source, docling_document in conversions.items():
    for chunk in HybridChunker(tokenizer=embeddings_tokenizer).chunk(docling_document):
        items = chunk.meta.doc_items
        if len(items) == 1 and isinstance(items[0], TableItem):
            continue # we will process tables later
        refs = “ “.join(map(lambda item: item.get_ref().cref, items))
        print(refs)
        text = chunk.text
        document = Document(
            page_content=text,
            metadata={
                “doc_id”: (doc_id:=doc_id+1),
                “source”: source,
                “ref”: refs,
            },
        )
        texts.append(document)

print(f”{len(texts)} text document chunks created”)

 

Successivamente elaboriamo tutte le tabelle nei documenti. Convertiamo i dati della tabella in formato markdown per passarli nel modello linguistico. Un elenco di documenti LangChain viene creato a partire dai rendering markdown della tabella.

from docling_core.types.doc.labels import DocItemLabel

doc_id = len(texts)
tables: list[Document] = []
for source, docling_document in conversions.items():
    for table in docling_document.tables:
        if table.label in [DocItemLabel.TABLE]:
            ref = table.get_ref().cref
            print(ref)
            text = table.export_to_markdown()
            document = Document(
                page_content=text,
                metadata={
                    “doc_id”: (doc_id:=doc_id+1),
                    “source”: source,
                    “ref”: ref
                },
            )
            tables.append(document)


print(f”{len(tables)} table documents created”)

 

Infine elaboriamo tutte le immagini nei documenti. Qui utilizziamo il modello linguistico della visione per comprendere il contenuto di un'immagine. In questo esempio, ci interessa qualsiasi informazione testuale presente nell'immagine. Puoi provare a sperimentare prompt diversi per vedere come migliorare i risultati.

NOTA: L'elaborazione delle immagini può richiedere molto tempo a seconda del numero di immagini e del servizio che il modello linguistico visivo esegue.

import base64
import io
import PIL.Image
import PIL.ImageOps
from IPython.display import display

def encode_image(image: PIL.Image.Image, format: str = “png”) -> str:
    image = PIL.ImageOps.exif_transpose(image) or image
    image = image.convert(“RGB”)

    buffer = io.BytesIO()
    image.save(buffer, format)
    encoding = base64.b64encode(buffer.getvalue()).decode(“utf-8”)
    uri = f”data:image/{format};base64,{encoding}”
    return uri

# Feel free to experiment with this prompt
image_prompt = “If the image contains text, explain the text in the image.”
conversation = [
    {
        “role”: “user”,
        “content”: [
            {“type”: “image”},
            {“type”: “text”, “text”: image_prompt},
        ],        
    },
]
vision_prompt = vision_processor.apply_chat_template(
    conversation=conversation,
    add_generation_prompt=True,
)
pictures: list[Document] = []
doc_id = len(texts) + len(tables)
for source, docling_document in conversions.items():
    for picture in docling_document.pictures:
        ref = picture.get_ref().cref
        print(ref)
        image = picture.get_image(docling_document)
        if image:
            text = vision_model.invoke(vision_prompt, image=encode_image(image))
            document = Document(
                page_content=text,
                metadata={
                    “doc_id”: (doc_id:=doc_id+1),
                    “source”: source,
                    “ref”: ref,
                },
            )
            pictures.append(document)

print(f”{len(pictures)} image descriptions created”)

 

Possiamo quindi visualizzare i documenti LangChain creati dai documenti di input.

import itertools
from docling_core.types.doc.document import RefItem

# Print all created documents
for document in itertools.chain(texts, tables):
    print(f”Document ID: {document.metadata[‘doc_id’]}”)
    print(f”Source: {document.metadata[‘source’]}”)
    print(f”Content:\n{document.page_content}”)
    print(“=” * 80) # Separator for clarity

for document in pictures:
    print(f”Document ID: {document.metadata[‘doc_id’]}”)
    source = document.metadata[‘source’]
    print(f”Source: {source}”)
    print(f”Content:\n{document.page_content}”)
    docling_document = conversions[source]
    ref = document.metadata[‘ref’]
    picture = RefItem(cref=ref).resolve(docling_document)
    image = picture.get_image(docling_document)
    print(“Image:”)
    display(image)
    print(“=” * 80) # Separator for clarity

Compilare il database vettoriale

Utilizzando il modello di embedding, carichiamo i documenti dai blocchi di testo e generiamo la didascalia delle immagini in un database vettoriale. Creare questo database vettoriale ci permette di condurre facilmente una ricerca di similarità semantica tra i nostri documenti.

NOTA: la popolazione del database vettoriale può richiedere un po' di tempo a seconda del modello di embedding e del servizio.

Scegliere il tuo database vettoriale

Specifica il database da utilizzare per memorizzare e recuperare i vettori di embedding.

Per collegarti a un database vettoriale diverso da Milvus, sostituisci questa cella di codice con una di queste istruzioni di Vector Store.

import tempfile
from langchain_core.vectorstores import VectorStore
from langchain_milvus import Milvus

db_file = tempfile.NamedTemporaryFile(prefix=”vectorstore_”, suffix=”.db”, delete=False).name
print(f”The vector database will be saved to {db_file}”)

vector_db: VectorStore = Milvus(
    embedding_function=embeddings_model,
    connection_args={“uri”: db_file},
    auto_id=True,
    enable_dynamic_field=True,
    index_params={“index_type”: “AUTOINDEX”},
)

 

Ora aggiungiamo tutti i documenti LangChain per le descrizioni di testo, tabelle e immagini al database vettoriale.

import itertools
documents = list(itertools.chain(texts, tables, pictures))
ids = vector_db.add_documents(documents)
print(f”{len(ids)} documents added to the vector database”)

Passaggio 4: RAG con Granite

Ora che abbiamo convertito e vettorializzato con successo i nostri documenti, possiamo impostare la nostra pipeline RAG.

Recupera i chunk rilevanti

Qui testiamo il database vettoriale cercando blocchi con informazioni rilevanti per la nostra query nello spazio vettoriale. Mostriamo i documenti associati alla descrizione dell'immagine recuperata.

Puoi provare query diverse.

query = "How much was spent on food distribution relative to the amount of food distributed?"
for doc in vector_db.as_retriever().invoke(query):
    print(doc)
    print("=" * 80) # Separator for clarity

 

Il documento restituito deve rispondere alla richiesta. Procediamo e costruiamo la nostra pipeline RAG.


Crea la pipeline RAG per Granite

Per prima cosa creiamo i prompt con cui Granite può eseguire la query RAG. Utilizziamo il modello di chat Granite e forniamo i valori segnaposto che la pipeline LangChain RAG sostituirà.

Successivamente, costruiamo la pipeline RAG utilizzando i modelli di prompt Granite creati in precedenza.

from ibm_granite_community.langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_classic.chains.retrieval import create_retrieval_chain
from langchain_core.prompts import ChatPromptTemplate

# Create a Granite prompt for question-answering with the retrieved context
prompt_template = ChatPromptTemplate.from_template("{input}")

# Assemble the retrieval-augmented generation chain
combine_docs_chain = create_stuff_documents_chain(
    llm=model,
    prompt=prompt_template,
)
rag_chain = create_retrieval_chain(
    retriever=vector_db.as_retriever(),
    combine_docs_chain=combine_docs_chain,
)

Generare una risposta aumentata per il recupero a una domanda

La pipeline utilizza la query per individuare i documenti dal database vettoriale e utilizzarli come contesto per la query.

from ibm_granite_community.notebook_utils import wrap_text

output = rag_chain.invoke({"input": query})

print(wrap_text(output['answer']))

Fantastico! Abbiamo creato un'applicazione AI che può utilizzare con successo le conoscenze dal testo e dalle immagini dei documenti sorgenti.

Fasi successive

  • Esplora i workflow RAG avanzati per altri settori.
  • Sperimenta con altri tipi di documenti e set di dati più ampi.
  • Ottimizza il prompt engineering per ottenere risposte migliori con Granite.
Soluzioni correlate
IBM watsonx.ai

Addestra, convalida, adatta e implementa le funzionalità di AI generativa, foundation model e machine learning con IBM watsonx.ai, uno studio aziendale di nuova generazione per builder AI. Crea applicazioni AI in tempi ridotti e con una minima quantità di dati.

Scopri watsonx.ai
Soluzioni di intelligenza artificiale

Metti l'AI al servizio della tua azienda grazie all'esperienza leader di settore e alla gamma di soluzioni di IBM nel campo dell'AI.

Esplora le soluzioni AI
Servizi AI

Reinventa i flussi di lavoro e le operazioni critiche aggiungendo l'AI per massimizzare le esperienze, il processo decisionale in tempo reale e il valore di business.

Esplora i servizi AI
Fai il passo successivo

Ottieni l'accesso completo a funzionalità che coprono l'intero ciclo di vita dello sviluppo dell'AI. Crea soluzioni AI all'avanguardia con interfacce intuitive, workflow e accesso alle API e agli SDK standard di settore.

Esplora watsonx.ai Prenota una demo live