Vai al contenuto principale

RAG multimodale: guida pratica per imparare dai documenti

Impara a costruire una pipeline RAG multimodale per un sistema di Q&A visivo sulle istruzioni di montaggio IKEA usando l'API OpenAI e il database vettoriale ChromaDB.
· 11 min leggi

Scopri con l'IA

Immagina di montare un mobile IKEA. Guardi il passaggio 14 delle istruzioni, ma non capisci quale vite va dove. Il diagramma mostra qualcosa, ma non ti è chiaro. 

Se chiedessi: "A cosa serve il pezzetto di metallo nel passaggio 14?", un sistema RAG standard basato solo su testo qui non può aiutarti. La risposta è nel diagramma stesso, non nel testo sulla pagina. Le istruzioni IKEA sono famosamente senza parole.

Qui entra in gioco il RAG multimodale.

In questo tutorial ti mostrerò come costruire un sistema RAG multimodale che sappia rispondere a domande su documenti con immagini, diagrammi e contenuti misti. Useremo le istruzioni di montaggio IKEA come caso di test perché sono una buona prova di stress: perlopiù visuali, testo minimo e il classico esempio in cui serve davvero aiuto.

Useremo i modelli di OpenAI: GPT-4o per comprendere le immagini e generare risposte, e text-embedding-3-small per creare vettori ricercabili. Se vuoi imparare a costruire pipeline RAG con un framework diverso, ti consiglio il nostro corso RAG con LangChain.

Alla fine avrai un sistema funzionante che puoi eseguire in locale e adattare ai tuoi documenti.

Cos'è il RAG multimodale?

Il RAG multimodale estende il pattern standard RAG per gestire più del semplice testo. L'idea di base resta la stessa, ma il "contesto" ora include immagini, tabelle, diagrammi e altri contenuti non testuali.

Perché è importante? Perché i documenti reali non sono solo testo.

Pensa a ciò con cui lavori davvero: 

  • Manuali tecnici con schemi elettrici 
  • Articoli scientifici con grafici sperimentali 
  • Report finanziari con grafici dei ricavi
  • Specifiche di prodotto con immagini dei componenti 

Un sistema che elabora solo testo lavora con informazioni incomplete. È come provare a capire una ricetta leggendo solo la lista degli ingredienti e ignorando le foto che mostrano cosa significa "mescolare delicatamente".

Il RAG tradizionale tratta un PDF come un flusso di caratteri. Estrae il testo, lo suddivide, ne crea gli embedding e finisce lì. Le immagini vengono saltate del tutto o magari convertite in segnaposto [IMAGE]. Le tabelle diventano righe di testo sconnesse che perdono la loro struttura.

Il RAG multimodale adotta un approccio diverso: le immagini vengono descritte o incorporate direttamente, e le tabelle vengono convertite in formati strutturati. Il sistema di retrieval può collegare una query come "diagramma di topologia di rete" sia alle descrizioni testuali dell'architettura di rete sia ai diagrammi visivi effettivi. Il modello di generazione può esaminare un'immagine e ragionare su ciò che rappresenta.

Aspetto

RAG solo testo

RAG multimodale

Tipi di input

Testo semplice, magari markdown

Testo, immagini, tabelle, diagrammi, documenti misti

Cosa viene incorporato

Chunk di testo

Chunk di testo più descrizioni delle immagini o embedding di immagini

Modello di generazione

Qualsiasi LLM

Modello visione-linguaggio (GPT-4o, Claude, Gemini)

Ideale per

Documenti ricchi di testo

Manuali, report, slide, qualsiasi contenuto visivo

Se "multimodale" ti sembra ancora un termine ombrello un po' vago, questo blog su IA multimodale è una spiegazione sintetica con esempi concreti.

Come funziona il RAG multimodale?

La pipeline ha più componenti rispetto al RAG solo testo, ma la struttura è simile. I dati vengono elaborati e incorporati, finiscono in uno store vettoriale e vengono recuperati quando qualcuno fa una domanda.

Multimodal RAG Pipeline

Ecco il flusso:

Ingestione e parsing dei documenti

I documenti grezzi (PDF, immagini, slide) vengono scomposti nelle loro parti. Estrai il testo, identifichi le immagini, analizzi le tabelle e capisci come tutto è correlato nello spazio. Un diagramma a pagina 5 potrebbe avere una didascalia sotto e un riferimento nel testo a pagina 4. Devi catturare queste relazioni.

Preprocessing dei contenuti

Tipi diversi di contenuto richiedono trattamenti diversi. Per le immagini, hai due opzioni principali: generare descrizioni testuali usando un modello di visione (come GPT-4o), oppure creare embedding delle immagini direttamente con un encoder multimodale (come CLIP). L'approccio delle descrizioni è più semplice e funziona con embedding testuali standard. Le tabelle vengono convertite in formati strutturati o riassunte in linguaggio naturale.

Dopo questo passaggio, tutto viene convertito in vettori. Se usi l'approccio delle descrizioni, ti basta incorporare tutto il testo (testo originale più descrizioni generate delle immagini) con un modello di embedding testuale standard. La chiave è che ora le query su contenuti visivi possono corrispondere a quelle descrizioni.

Indicizzazione e storage 

I vettori vengono archiviati in un database con metadati, inclusi documento sorgente, numero di pagina, tipo di contenuto e, per le immagini, il percorso al file originale. Ti servirà quel percorso più avanti quando vorrai mostrarlo al modello di generazione.

Retrieval

Quando arriva una query, creane l'embedding con lo stesso modello, cerca i vicini più prossimi e restituisci i migliori risultati. I chunk recuperati possono fare riferimento a testo, immagini o entrambi.

Generazione

Passa la query, insieme al contesto recuperato, a un modello visione-linguaggio. Se i chunk recuperati fanno riferimento a immagini, carica quelle immagini e includile nel prompt. Il modello vede sia il contesto testuale sia le immagini reali e poi genera una risposta.

La fase di generazione è dove GPT-4o brilla davvero. Può guardare un diagramma IKEA che mostra la posizione delle viti e spiegare in linguaggio semplice cosa succede. Può leggere un grafico e riassumere l'andamento. Può notare dettagli in disegni tecnici che una descrizione testuale potrebbe tralasciare.

Implementazione del RAG multimodale

Costruiamolo. Creeremo un sistema che sappia rispondere a domande sulle istruzioni di montaggio IKEA, passo dopo passo dall'impostazione a un'interfaccia di query funzionante.

Step 0: Scelta di framework e strumenti

Per questo tutorial, restiamo sul semplice:

Componente

Scelta

Perché

Embeddings

text-embedding-3-small

Economico, buona qualità, stessa API

Vector store

ChromaDB

Gira in locale, nessun setup, persistente

Modello di visione

GPT-4o

Il migliore per la comprensione delle immagini

Generazione

GPT-4o

Stesso modello, gestisce entrambi i compiti

Puoi sostituire con alternative. Pinecone o Weaviate per lo store vettoriale se ti serve scalabilità. Claude o Gemini sono buone alternative per la generazione. L'architettura generale resta la stessa.

Perché non usare CLIP o ColPali per gli embedding? Puoi farlo. Permettono di incorporare direttamente le immagini senza generare prima descrizioni testuali. Ma aggiungono complessità e spesso richiedono una GPU. L'approccio delle descrizioni funziona bene per contenuti in stile documentale, dove vuoi catturare dettagli semantici, non solo somiglianza visiva.

Step 1: Configurazione dell'ambiente

Iniziamo con dipendenze e variabili d'ambiente per impostare il progetto.

Installazione delle dipendenze

Per prima cosa, ti serve poppler installato per gestire l'elaborazione dei PDF.

  • Ubuntu/Debian: sudo apt-get install poppler-utils

  • macOS (con Homebrew): brew install poppler

  • Windows: Scarica dalla pagina dei rilasci e aggiungi al PATH

Poi installa i pacchetti Python. Oltre a ChromaDB, ci servono pdf2image per convertire i PDF in immagini, pillow per l'elaborazione delle immagini e requests per scaricare i file PDF.

pip install openai chromadb pdf2image pillow requests

Impostazione della chiave API e dei file

Per usare i modelli, imposta la tua chiave API di OpenAI come variabile d'ambiente.

Unix/Linux/macOS:

export OPENAI_API_KEY="your-api-key-here"

Windows: 

setx OPENAI_API_KEY="your-api-key-here"

Ora configuriamo la struttura del progetto e scarichiamo i dati di esempio. Lo script include già gli URL di download, ma se vuoi dare un'occhiata ai PDF prima, ecco i link diretti sia per la scrivania MALM sia per la struttura letto MALM. 

Crea un file chiamato step1_setup.py:

"""
Step 1: Setup and Download Sample Data
"""
import os
import requests

# Create directories
for d in ["data", "data/images", "data/cache"]:
    os.makedirs(d, exist_ok=True)
print("✓ Created directories")

# Download IKEA assembly instructions
SAMPLE_PDFS = {
    "malm_desk.pdf": "https://www.ikea.com/us/en/assembly_instructions/malm-desk-white__AA-516949-7-2.pdf",
    "malm_bed.pdf": "https://www.ikea.com/us/en/assembly_instructions/malm-bed-frame-low__AA-75286-15_pub.pdf",
}

for filename, url in SAMPLE_PDFS.items():
    filepath = f"data/{filename}"
    if not os.path.exists(filepath):
        print(f"Downloading {filename}...")
        response = requests.get(url, timeout=30)
        with open(filepath, "wb") as f:
            f.write(response.content)
        print(f"  ✓ Saved ({len(response.content) / 1024:.1f} KB)")
    else:
        print(f"✓ {filename} already exists")

Per completare l'impostazione iniziale, esegui lo script:

python step1_setup.py

Dovresti vedere i PDF IKEA scaricati nella cartella data/.

Step 2: Preprocessing dei documenti

Le istruzioni IKEA sono insidiose. Sono perlopiù diagrammi con testo minimo. Dobbiamo convertire le pagine PDF in immagini, poi usare GPT-4o per descrivere cosa mostra ogni pagina.

Crea step2_preprocess.py:

"""
Step 2: Preprocess Documents
Convert PDFs to images and generate descriptions with GPT-4o
"""
import os
import json
import base64
from pdf2image import convert_from_path
from openai import OpenAI

client = OpenAI()

DATA_DIR = "data"
IMAGES_DIR = "data/images"
CACHE_FILE = "data/cache/descriptions.json"


def convert_pdf_to_images(pdf_path):
    """Convert a PDF to page images."""
    pdf_name = os.path.splitext(os.path.basename(pdf_path))[0]
    images = convert_from_path(pdf_path, dpi=150)
    
    image_paths = []
    for i, image in enumerate(images):
        image_path = f"{IMAGES_DIR}/{pdf_name}_page_{i+1:03d}.png"
        image.save(image_path, "PNG")
        image_paths.append(image_path)
    
    return image_paths


def describe_page(image_path):
    """Use GPT-4o to describe an instruction page."""
    with open(image_path, "rb") as f:
        base64_image = base64.b64encode(f.read()).decode("utf-8")
    
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": """Describe this IKEA assembly instruction page in detail.
Include: step numbers, parts shown, tools needed, actions demonstrated, 
quantities (like "2x"), warnings, and part numbers if visible.
This description will help people find this page when they have questions."""
            },
            {
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_image}"}},
                    {"type": "text", "text": "Describe this instruction page."}
                ]
            }
        ],
        max_tokens=500
    )
    return response.choices[0].message.content


def main():
    # Load cache if exists
    cache = {}
    if os.path.exists(CACHE_FILE):
        with open(CACHE_FILE, "r") as f:
            cache = json.load(f)
    
    all_pages = []
    
    # Process each PDF
    for pdf_file in os.listdir(DATA_DIR):
        if not pdf_file.endswith(".pdf"):
            continue
        
        print(f"\nProcessing {pdf_file}...")
        pdf_path = f"{DATA_DIR}/{pdf_file}"
        
        # Convert to images
        print("  Converting to images...")
        image_paths = convert_pdf_to_images(pdf_path)
        print(f"  ✓ {len(image_paths)} pages")
        
        # Generate descriptions
        print("  Generating descriptions with GPT-4o...")
        for i, image_path in enumerate(image_paths):
            if image_path in cache:
                description = cache[image_path]
                status = "cached"
            else:
                description = describe_page(image_path)
                cache[image_path] = description
                # Save cache after each page
                with open(CACHE_FILE, "w") as f:
                    json.dump(cache, f, indent=2)
                status = "generated"
            
            all_pages.append({
                "source_pdf": pdf_file,
                "page_number": i + 1,
                "image_path": image_path,
                "description": description
            })
            print(f"    Page {i+1}/{len(image_paths)} [{status}]")
    
    # Save all page data
    with open("data/cache/all_pages.json", "w") as f:
        json.dump(all_pages, f, indent=2)
    
    print(f"\n✓ Processed {len(all_pages)} pages total")


if __name__ == "__main__":
    main()

Ecco cosa fanno le funzioni:

  • convert_pdf_to_images(pdf_path): rende ogni pagina del PDF come immagine PNG a 150 DPI usando pdf2image. Questo estrae i diagrammi privi di testo selezionabile, salvandoli con nomi coerenti come malm_desk_page_001.png.

  • describe_page(image_path): codifica l'immagine in base64 e la invia a GPT-4o con un prompt di sistema mirato. Il modello restituisce una descrizione testuale dettagliata che cattura passaggi, parti e quantità per la ricerca semantica.

  • main(): orchestra il tutto: carica descrizioni in cache per evitare rielaborazioni, converte i PDF in immagini, genera nuove descrizioni quando serve, salva la cache incrementale e produce all_pages.json con metadati per l'indicizzazione.

Esegui lo script così:

python step2_preprocess.py

Questo passaggio richiede alcuni minuti. GPT-4o analizza ogni pagina e genera una descrizione dettagliata. Le descrizioni vengono messe in cache, quindi se lo riesegui, salta le pagine già elaborate.

Cosa sta succedendo qui? Convertiamo il contenuto visivo in testo ricercabile. Quando qualcuno chiede, "Come attacco le gambe?", il retriever cercherà in queste descrizioni e troverà le pagine che menzionano il fissaggio delle gambe, anche se la pagina originale non ha alcun testo.

Una volta costruita la pipeline di base, i prossimi miglioramenti vengono da tecniche di retrieval, come ricerca ibrida, reranking o riscrittura della query. Per familiarizzare, leggi questo blog sulle Tecniche RAG avanzate.

Step 3: Archiviazione e indicizzazione dei dati multimodali

Ora creiamo gli embedding delle descrizioni e li memorizziamo in ChromaDB.

Crea step3_index.py:

"""
Step 3: Create Embeddings and Index
"""
import os
import json
from openai import OpenAI
import chromadb

client = OpenAI()

PAGES_FILE = "data/cache/all_pages.json"
CHROMA_DIR = "data/chroma_db"
COLLECTION_NAME = "ikea_instructions"


def get_embedding(text):
    """Get embedding from OpenAI."""
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding


def main():
    # Load page descriptions
    with open(PAGES_FILE, "r") as f:
        pages = json.load(f)
    print(f"Loaded {len(pages)} pages")
    
    # Initialize ChromaDB with persistent storage
    chroma_client = chromadb.PersistentClient(path=CHROMA_DIR)
    
    # Delete existing collection if present
    try:
        chroma_client.delete_collection(name=COLLECTION_NAME)
    except:
        pass
    
    collection = chroma_client.create_collection(
        name=COLLECTION_NAME,
        metadata={"hnsw:space": "cosine"}
    )
    print(f"Created collection: {COLLECTION_NAME}")
    
    # Index each page
    print("\nCreating embeddings...")
    for i, page in enumerate(pages):
        # Combine source info with description
        text_to_embed = f"Source: {page['source_pdf']}, Page {page['page_number']}\n\n{page['description']}"
        
        embedding = get_embedding(text_to_embed)
        
        collection.add(
            ids=[f"page_{i}"],
            embeddings=[embedding],
            documents=[page["description"]],
            metadatas=[{
                "source_pdf": page["source_pdf"],
                "page_number": page["page_number"],
                "image_path": page["image_path"]
            }]
        )
        
        if (i + 1) % 5 == 0 or i == len(pages) - 1:
            print(f"  Indexed {i + 1}/{len(pages)} pages")
    
    print(f"\n✓ Index created with {collection.count()} documents")
    
    # Quick retrieval test
    print("\nTesting retrieval...")
    test_query = "How do I attach the legs?"
    query_embedding = get_embedding(test_query)
    results = collection.query(query_embeddings=[query_embedding], n_results=2)
    
    print(f"Query: '{test_query}'")
    for meta in results["metadatas"][0]:
        print(f"  → {meta['source_pdf']}, Page {meta['page_number']}")


if __name__ == "__main__":
    main()

Qui, la funzione get_embedding(text) converte il testo della descrizione e le info sulla fonte in un vettore denso usando il modello di OpenAI text-embedding-3-small. Con 1536 dimensioni, questi vettori riescono a catturare il significato semantico per la ricerca per similarità.

main() carica le pagine elaborate da all_pages.json dallo step precedente, inizializza ChromaDB con storage persistente, elimina/ricrea la collezione, genera embedding per ogni pagina, li memorizza con metadati ricchi (sorgente, numero di pagina, percorso immagine) e esegue un rapido test di retrieval per verificare che l'indicizzazione funzioni.

Eseguilo:

python step3_index.py

Qui sono importanti i metadati. Memorizziamo image_path per poter recuperare l'immagine reale in seguito e mostrarla a GPT-4o durante la generazione. La descrizione è ciò che viene cercato, ma l'immagine è ciò che viene mostrato.

Step 4: Costruzione della pipeline RAG

Ecco la pipeline RAG multimodale completa. Una query viene incorporata, le pagine rilevanti vengono recuperate, le immagini caricate e GPT-4o genera una risposta basata su descrizioni testuali e contenuti visivi.

Crea step4_query.py:

"""
Step 4: Query the Multimodal RAG System
"""
import os
import sys
import base64
from openai import OpenAI
import chromadb

client = OpenAI()

CHROMA_DIR = "data/chroma_db"
COLLECTION_NAME = "ikea_instructions"
TOP_K = 3

# Initialize ChromaDB
chroma_client = chromadb.PersistentClient(path=CHROMA_DIR)


def get_embedding(text):
    """Get embedding from OpenAI."""
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding


def retrieve(query, top_k=TOP_K):
    """Find relevant instruction pages."""
    collection = chroma_client.get_collection(name=COLLECTION_NAME)
    query_embedding = get_embedding(query)
    
    results = collection.query(
        query_embeddings=[query_embedding],
        n_results=top_k,
        include=["documents", "metadatas"]
    )
    return results


def generate_answer(query, retrieved_results):
    """Generate answer using GPT-4o with retrieved images."""
    if not retrieved_results["ids"][0]:
        return "No relevant pages found.", []
    
    # Build image content for GPT-4o
    image_content = []
    sources = []
    
    for doc, metadata in zip(retrieved_results["documents"][0], retrieved_results["metadatas"][0]):
        image_path = metadata["image_path"]
        
        if not os.path.exists(image_path):
            continue
        
        with open(image_path, "rb") as f:
            base64_image = base64.b64encode(f.read()).decode("utf-8")
        
        image_content.append({
            "type": "image_url",
            "image_url": {"url": f"data:image/png;base64,{base64_image}"}
        })
        image_content.append({
            "type": "text",
            "text": f"[Page {metadata['page_number']} from {metadata['source_pdf']}]"
        })
        
        sources.append(f"{metadata['source_pdf']}, Page {metadata['page_number']}")
    
    # Generate with GPT-4o
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {
                "role": "system",
                "content": """You answer questions about IKEA assembly based on instruction pages.
Be specific, reference page numbers when helpful, and say if something isn't clear from the images."""
            },
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": f"Question: {query}\n\nRelevant instruction pages:"},
                    *image_content,
                    {"type": "text", "text": "Answer based on these pages."}
                ]
            }
        ],
        max_tokens=600
    )
    
    return response.choices[0].message.content, sources


def answer_question(query):
    """Full RAG pipeline."""
    print(f"\nQuery: {query}")
    print("-" * 50)
    
    print("Retrieving relevant pages...")
    results = retrieve(query)
    print(f"  Found {len(results['ids'][0])} pages")
    
    print("Generating answer...")
    answer, sources = generate_answer(query, results)
    
    return answer, sources


def main():
    # Check index exists
    try:
        collection = chroma_client.get_collection(name=COLLECTION_NAME)
        print(f"Connected to index ({collection.count()} documents)")
    except:
        print("ERROR: Index not found. Run step2_index.py first.")
        return
    
    # Single query from command line
    if len(sys.argv) > 1:
        query = " ".join(sys.argv[1:])
        answer, sources = answer_question(query)
        print(f"\nAnswer:\n{answer}")
        print(f"\nSources: {', '.join(sources)}")
        return
    
    # Interactive mode
    print("\nAsk questions about IKEA assembly. Type 'quit' to exit.\n")
    
    while True:
        try:
            query = input("Question: ").strip()
        except KeyboardInterrupt:
            break
        
        if not query or query.lower() in ["quit", "exit"]:
            break
        
        answer, sources = answer_question(query)
        print(f"\nAnswer:\n{answer}")
        print(f"\nSources: {', '.join(sources)}\n")


if __name__ == "__main__":
    main()

Funzioni chiave spiegate:

  • get_embedding(text): genera un embedding vettoriale per la query usando text-embedding-3-small, abilitando la ricerca per similarità semantica in ChromaDB.​

  • retrieve(query, top_k=TOP_K): crea l'embedding della query, interroga la collezione per i k vicini più prossimi e restituisce documenti e metadati (ad esempio, percorsi delle immagini) tramite include=["documents", "metadatas"].​

  • generate_answer(query, retrieved_results): carica le immagini principali in base64, costruisce un prompt multimodale GPT-4o alternando etichette di pagina e immagini e genera una risposta dettagliata con istruzioni specifiche IKEA.​

  • answer_question(query): orchestra retrieval e generazione, con log di avanzamento.

  • main(): verifica che l'indice esista, supporta query singole da CLI o modalità chat interattiva (“quit”, “exit” o Ctrl+C per uscire).

Step 5: Esecuzione della pipeline RAG

Puoi eseguirla e digitare la tua domanda, oppure lanciarla direttamente con una domanda specifica, così:

python step4_query.py "How do I attach the legs to the desk?"
Retrieving relevant pages...
Found 3 pages
Generating answer...

Answer:
To attach the legs to the MALM desk, follow these steps shown on pages 5-6:

1. First, insert the wooden dowels into the pre-drilled holes on the leg 
   pieces (you'll see "4x" indicating four dowels are needed)

2. Apply the cam locks to the desk top using the Phillips screwdriver

3. Align each leg with the corresponding holes on the underside of the desk

4. Tighten the cam locks by rotating them clockwise until the legs are 
   firmly attached

The instructions show this should be done with the desk top face-down on 
a soft surface to avoid scratching.

Sources: malm_desk.pdf, Page 5, malm_desk.pdf, Page 6, malm_desk.pdf, Page 7

Sembra buono! Il sistema recupera le pagine in base alle descrizioni, carica le immagini reali e GPT-4o genera una risposta guardando direttamente i diagrammi.

Valutazione del RAG multimodale

Costruire un sistema RAG multimodale è una cosa. Capire se funziona bene è un'altra. E farlo girare in produzione in modo affidabile aggiunge un ulteriore livello di sfide. 

Le metriche standard del RAG valgono ancora, ma vanno estese al contenuto visivo.

Qualità del retrieval

La Precision@k misura quante delle pagine recuperate erano effettivamente rilevanti. Penalizza le pagine irrilevanti nella top-k, ma per la generazione avere l'immagine chiave conta più che ottenere una top-k perfetta.

La Recall@k misura quanto spesso le pagine corrette compaiono nei primi k risultati. È più pertinente per il nostro caso IKEA perché vogliamo che il sistema trovi almeno una pagina rilevante nella top-k (ad es., k=3). Così GPT-4o ottiene il contesto visivo per rispondere correttamente, anche se altri risultati sono rumore. 

Per il nostro sistema IKEA, costruiresti un set di test con magari 50 domande e assegneresti le pagine "corrette" per ciascuna. Poi potresti valutarle usando la seguente funzione:

def evaluate_retrieval(test_cases, top_k=3):
    """
    test_cases: [{"query": str, "relevant_pages": ["image_path1", ...]}]
    """
    hits = 0
    for test in test_cases:
        results = retrieve(test["query"], top_k=top_k)
        retrieved = [m["image_path"] for m in results["metadatas"][0]]
        if any(p in retrieved for p in test["relevant_pages"]):
            hits += 1
    return hits / len(test_cases)

Qualità della generazione

Qualcosa di più difficile da misurare automaticamente è la qualità della generazione. 

Puoi far valutare alle persone accuratezza e utilità delle risposte, oppure usare un LLM come giudice per verificare se le risposte sono corrette dato il contenuto delle immagini. 

Per i diagrammi con passaggi numerati, verifica che il modello assegni i numeri corretti.

Verifiche specifiche multimodali

Ulteriori valutazioni potrebbero rispondere alle seguenti domande:

  • Il sistema trova risposte basate su immagini quando la risposta è visiva? 

  • Quando il modello fa riferimento a qualcosa in un'immagine, quel riferimento è accurato? 

Queste richiedono una revisione manuale, almeno su un campione di query.

Best practice per il RAG multimodale

Se vuoi costruire una pipeline RAG multimodale pronta per la produzione, tieni a mente alcune cose:

  • I costi di preprocessing si sommano: Le chiamate di visione per la descrizione delle immagini sono la parte costosa. Per un grande corpus di documenti, elabora in batch nelle ore di minor carico e metti in cache in modo aggressivo. Valuta un modello più economico (nel nostro caso, ad es., GPT-4o-mini) per generare descrizioni se la qualità è sufficiente per il tuo caso d'uso.

  • La latenza della query conta: GPT-4o con immagini richiede 5-10 secondi per query. Lo streaming aiuta gli utenti a vedere risultati parziali in fretta e la cache delle query comuni aiuta per le domande ripetute. Recuperare 2 pagine invece di 5 può fare differenza in termini di token se è già abbastanza contesto.

  • Monitora le cose giuste: Traccia latenza del retrieval (sotto i 100 ms per collezioni piccole), latenza della generazione (aspettati 5-10 secondi con immagini), costi API per query e feedback degli utenti se lo raccogli.

  • Il robusto handling degli errori ti salva: Le cose si rompono. Costruisci fallback, come nello snippet seguente.

def answer_question_safe(query):
    """Wrapper with error handling."""
    try:
        return answer_question(query)
    except Exception as e:
        print(f"Error: {e}")
        # Fall back to text-only using descriptions
        results = retrieve(query)
        if results["documents"][0]:
            context = results["documents"][0][0][:500]
            return f"Based on the instructions: {context}...", []
        return "Sorry, couldn't process that question.", []

Quando la tua pipeline deve fare più passi (cerca → verifica → ricerca di nuovo → rispondi), i pattern di agentic RAG ti aiutano a strutturarla in modo pulito.

Conclusione

Il RAG multimodale estende la generazione aumentata dal retrieval per gestire l'intera gamma di contenuti nei documenti reali, incluse immagini, diagrammi, tabelle e grafici. 

Lo abbiamo usato con successo per interrogare le istruzioni IKEA, che sono notoriamente visive, dove un approccio solo testo non avrebbe funzionato. L'intera pipeline gira in locale tranne che per le chiamate API.

L'intuizione chiave è che non servono embedding multimodali complessi per farlo funzionare. Generando buone descrizioni testuali del contenuto visivo, puoi usare il retrieval testuale standard per trovare immagini pertinenti e poi lasciare che un modello visione-linguaggio faccia il grosso del lavoro in fase di generazione.

Se vuoi proseguire da quanto visto qui, inizia oggi il tuo percorso con il percorso AI Engineer!

FAQ su RAG multimodale

Quali tipi di dati può gestire il RAG multimodale?

Immagini, PDF con grafici, tabelle, diagrammi, audio (tramite trascrizioni) e keyframe video. In pratica, qualsiasi contenuto che vorresti che un'IA consultasse per rispondere alle domande.

Ho bisogno di database vettoriali separati per diverse modalità?

Non necessariamente. La maggior parte dei sistemi in produzione usa un singolo vector store con uno spazio di embedding unificato. Puoi incorporare tutto nello stesso spazio dimensionale, così il retrieval funziona tra tipi di contenuto diversi.

Posso usare OpenAI per l'intera pipeline?

Sì. GPT-4o gestisce sia la comprensione delle immagini sia la generazione delle risposte, mentre text-embedding-3-small gestisce il lato vettoriale. Puoi costruire un sistema RAG multimodale completo senza uscire dall'ecosistema OpenAI.

Qual è la sfida più grande nel RAG multimodale?

Allineamento. Far convivere testo, immagini e dati strutturati nello stesso spazio di embedding in modo che il retrieval funzioni davvero tra modalità. Subito dopo viene la strategia di chunking.

Quanto costa eseguirlo?

Per il tutorial IKEA qui, aspetta circa 2 $ in totale. La parte costosa sono le chiamate GPT-4o per descrivere le immagini durante il preprocessing. Una volta indicizzato, le query costano poco.

Josep Ferrer

Autore

Josep Ferrer

Josep è un Data Scientist freelance specializzato in progetti europei, con competenze in archiviazione e processamento dei dati, analisi avanzate e data storytelling efficace. 

Come docente, insegna Big Data nel corso di laurea magistrale dell’Università di Navarra e condivide le sue idee tramite articoli su piattaforme come Medium, KDNuggets e DataCamp. Josep scrive anche di Data e Tech nella sua newsletter Databites (databites.tech). 

Ha conseguito una laurea in Ingegneria Fisica presso la Università Politecnica della Catalogna e un master in Intelligent Interactive Systems presso la Università Pompeu Fabra.

Argomenti

Correlato