Stel, je bent IKEA-meubels aan het monteren. Je staart naar stap 14 in de handleiding, maar je krijgt niet helder welke schroef waar hoort. Het diagram laat iets zien, maar het kwartje valt niet.
Als je zou vragen: "Waar is het kleine metalen onderdeel in stap 14 voor?", dan schiet een standaard tekstgebaseerd retrieval-augmented generation (RAG)-systeem tekort. Het antwoord staat in het diagram zelf, niet in tekst op de pagina. IKEA-handleidingen zijn berucht woordloos.
Daar komt multimodale RAG om de hoek kijken.
In deze tutorial laat ik je stap voor stap zien hoe je een multimodaal RAG-systeem bouwt dat vragen kan beantwoorden over documenten met afbeeldingen, diagrammen en gemengde inhoud. We gebruiken IKEA-montagehandleidingen als testcase omdat die een goede stresstest zijn: vooral visueel, minimale tekst, en precies het soort materiaal waarbij mensen hulp nodig hebben.
We gebruiken overal de modellen van OpenAI: GPT-4o voor beeldbegrip en het genereren van antwoorden, en text-embedding-3-small voor het maken van doorzoekbare vectoren. Wil je RAG-pijplijnen bouwen met een ander framework, bekijk dan onze cursus RAG met LangChain.
Aan het eind heb je een werkend systeem dat je lokaal kunt draaien en kunt aanpassen voor je eigen documenten.
Wat is multimodale RAG?
Multimodale RAG breidt het standaard RAG-patroon uit om meer dan alleen tekst te verwerken. Het kernidee blijft hetzelfde, maar "context" omvat nu ook afbeeldingen, tabellen, diagrammen en andere niet-tekstuele content.
Waarom is dat belangrijk? Omdat echte documenten niet alleen uit tekst bestaan.
Denk aan wat je in de praktijk gebruikt:
Technische handleidingen met bedradingsschema’s
Onderzoeksartikelen met experimentele grafieken
Financiële rapporten met omzetgrafieken
Productspecificaties met componentafbeeldingen
Een systeem dat alleen tekst verwerkt, werkt met onvolledige informatie. Het is alsof je een recept probeert te begrijpen door alleen de ingrediëntenlijst te lezen en de foto’s over te slaan die laten zien hoe "voorzichtig omscheppen" eruitziet.
Traditionele RAG ziet een PDF als een stroom tekens. Het extraheert tekst, hakt die in stukken, embedt de stukken en klaar. Afbeeldingen worden volledig overgeslagen, of hooguit omgezet naar [IMAGE]-placeholdertokens. Tabellen veranderen in verminkte tekstregels die hun structuur verliezen.
Multimodale RAG pakt het anders aan: afbeeldingen worden beschreven of direct ge-embed, en tabellen worden geparsed naar gestructureerde formaten. Het retrievalsysteem kan een query als "netwerk-topologiediagram" koppelen aan zowel tekstuele beschrijvingen van netwerkarchitectuur als echte visuele diagrammen. Het generatiemodel kan een afbeelding bekijken en redeneren over wat erop staat.
Tekstchunks plus afbeeldingsbeschrijvingen of afbeeldingsembeddings
Generatiemodel
Elke LLM
Vision-language model (GPT-4o, Claude, Gemini)
Beste voor
Tekstzware documenten
Handleidingen, rapporten, slides, alles met visuele content
Als "multimodaal" nog vaag voelt als parapluterm, dan is deze blog over Multimodale AI een heldere uitleg met concrete voorbeelden.
Hoe werkt multimodale RAG?
De pijplijn heeft meer bewegende delen dan RAG met alleen tekst, maar de structuur is vergelijkbaar. Data wordt verwerkt en ge-embed, komt in een vectorstore terecht en wordt opgehaald zodra iemand een vraag stelt.
Zo loopt het proces:
Documentinname en parsing
Ruwe documenten (PDF’s, afbeeldingen, slides) worden opgesplitst in onderdelen. Je haalt tekst eruit, identificeert afbeeldingen, parseert tabellen en bepaalt hoe alles ruimtelijk samenhangt. Een diagram op pagina 5 kan een bijschrift eronder hebben en een verwijzing in de tekst op pagina 4. Die relaties moet je vastleggen.
Contentvoorbewerking
Verschillende contenttypen vragen om verschillende behandeling. Voor afbeeldingen heb je twee hoofdopties: tekstbeschrijvingen genereren met een visionmodel (zoals GPT-4o), of afbeeldingen direct embedden met een multimodale encoder (zoals CLIP). De beschrijvingsaanpak is eenvoudiger en werkt met standaard tekstembeddings. Tabellen zet je om naar gestructureerde formaten of vat je samen in natuurlijke taal.
Daarna wordt alles omgezet naar vectoren. Gebruik je de beschrijvingsaanpak, dan embed je simpelweg alle tekst (originele tekst plus gegenereerde afbeeldingsbeschrijvingen) met een standaard tekstembeddingmodel. Het belangrijkste is dat queries over visuele content nu op die beschrijvingen kunnen matchen.
Indexering en opslag
Vectoren worden opgeslagen in een database met metadata, inclusief het brondocument, paginanummer, contenttype en, voor afbeeldingen, het pad naar het originele bestand. Dat afbeeldingspad heb je later nodig wanneer je het aan het generatiemodel wilt tonen.
Retrieval
Als er een query binnenkomt, embed je die met hetzelfde model, zoek je naar de dichtstbijzijnde buren en retourneer je de beste matches. De opgehaalde chunks kunnen verwijzen naar tekst, afbeeldingen of beide.
Generatie
Geef de query, samen met de opgehaalde context, aan een vision-language model. Als de opgehaalde chunks naar afbeeldingen verwijzen, laad die afbeeldingen dan en voeg ze toe aan de prompt. Het model ziet zowel de tekstcontext als de echte afbeeldingen, en genereert vervolgens een antwoord.
Bij de generatiestap blinkt GPT-4o echt uit. Het kan naar een IKEA-diagram met schroefplaatsing kijken en in gewone taal uitleggen wat er gebeurt. Het kan een grafiek lezen en de trend samenvatten. Het kan details in technische tekeningen opmerken die een tekstbeschrijving kan missen.
Multimodale RAG implementeren
We gaan bouwen. We maken een systeem dat vragen kan beantwoorden over IKEA-montagehandleidingen, stap voor stap van setup tot een werkende query-interface.
Stap 0: Framework en tools kiezen
Voor deze tutorial houden we het simpel:
Component
Keuze
Waarom
Embeddings
text-embedding-3-small
Goedkoop, goede kwaliteit, dezelfde API
Vectorstore
ChromaDB
Draait lokaal, geen setup, persistent
Visionmodel
GPT-4o
Topklasse voor beeldbegrip
Generatie
GPT-4o
Zelfde model, kan beide taken aan
Je kunt alternatieven inwisselen. Pinecone of Weaviate voor de vectorstore als je wilt opschalen. Claude of Gemini zijn goede alternatieven voor de generatie. De algemene architectuur blijft in die gevallen hetzelfde.
Waarom geen CLIP of ColPali voor embeddings? Dat kan. Daarmee embed je afbeeldingen direct zonder eerst tekstbeschrijvingen te genereren. Maar het voegt complexiteit toe en vereist vaak een GPU. De beschrijvingsaanpak werkt goed voor documentachtige content waarbij je semantische details wilt vastleggen, niet alleen visuele gelijkenis.
Stap 1: De omgeving opzetten
Laten we beginnen met dependencies en omgevingsvariabelen om onze projectomgeving op te zetten.
Windows: Download van de releasepagina en voeg toe aan PATH
Installeer daarna de Python-packages. Naast ChromaDB hebben we pdf2image nodig om PDF’s naar afbeeldingen te converteren, pillow voor beeldbewerking en requests om de PDF-bestanden te downloaden.
Laten we nu de projectstructuur opzetten en de voorbeelddata ophalen. Het script bevat al de download-URL’s, maar als je de PDF’s eerst wilt bekijken, hier zijn de directe links naar zowel het MALM-bureau als het MALM-bedframe.
Maak een bestand genaamd step1_setup.py:
"""
Step 1: Setup and Download Sample Data
"""import os
import requests
# Create directoriesfor 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}"ifnot os.path.exists(filepath):print(f"Downloading {filename}...")
response = requests.get(url, timeout=30)withopen(filepath,"wb")as f:
f.write(response.content)print(f" ✓ Saved ({len(response.content)/1024:.1f} KB)")else:print(f"✓ {filename} already exists")Mogelijk gemaakt door
Voer het script uit om de initiële setup af te ronden:
IKEA-handleidingen zijn lastig. Ze bestaan vooral uit diagrammen met minimale tekst. We moeten PDF-pagina’s converteren naar afbeeldingen en dan GPT-4o gebruiken om te beschrijven wat elke pagina laat zien.
Maak step2_preprocess.py aan:
"""
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"defconvert_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 inenumerate(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
defdescribe_page(image_path):"""Use GPT-4o to describe an instruction page."""withopen(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
defmain():# Load cache if exists
cache ={}if os.path.exists(CACHE_FILE):withopen(CACHE_FILE,"r")as f:
cache = json.load(f)
all_pages =[]# Process each PDFfor pdf_file in os.listdir(DATA_DIR):ifnot pdf_file.endswith(".pdf"):continueprint(f"\nProcessing {pdf_file}...")
pdf_path =f"{DATA_DIR}/{pdf_file}"# Convert to imagesprint(" Converting to images...")
image_paths = convert_pdf_to_images(pdf_path)print(f" ✓ {len(image_paths)} pages")# Generate descriptionsprint(" Generating descriptions with GPT-4o...")for i, image_path inenumerate(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 pagewithopen(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 datawithopen("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()Mogelijk gemaakt door
Dit doen de functies:
convert_pdf_to_images(pdf_path): rendert elke PDF-pagina als een PNG-afbeelding op 150 DPI met pdf2image. Zo haal je diagrammen zonder selecteerbare tekst eruit, met consistente bestandsnamen zoals malm_desk_page_001.png.
describe_page(image_path): codeert de afbeelding als base64 en stuurt die naar GPT-4o met een gerichte system prompt. Het model geeft een gedetailleerde tekstbeschrijving terug met stappen, onderdelen en aantallen voor semantisch zoeken.
main(): regisseert alles: laadt gecachete beschrijvingen om herverwerking te vermijden, converteert PDF’s naar afbeeldingen, genereert waar nodig nieuwe beschrijvingen, slaat incrementele cache op en schrijft all_pages.json met metadata voor indexering.
Deze stap duurt enkele minuten. GPT-4o analyseert elke pagina en genereert een gedetailleerde beschrijving. Beschrijvingen worden gecachet, dus als je het opnieuw draait, slaat het reeds verwerkte pagina’s over.
Wat gebeurt hier? We zetten visuele content om in tekst die doorzoekbaar is. Als iemand vraagt: "Hoe bevestig ik de poten?", dan zoekt de retriever in deze beschrijvingen en vindt pagina’s die het bevestigen van poten noemen, ook al staat er op de originele pagina geen tekst.
Heb je de basispijplijn eenmaal staan, dan komen de volgende winstpunten uit retrievaltrucs, zoals hybrid search, reranking of query rewriting. Wil je daarmee kennismaken, lees dan deze blog over Geavanceerde RAG-technieken.
Stap 3: Multimodale data opslaan en indexeren
Nu embedden we de beschrijvingen en slaan we ze op in ChromaDB.
Maak step3_index.py aan:
"""
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"defget_embedding(text):"""Get embedding from OpenAI."""
response = client.embeddings.create(
model="text-embedding-3-small",input=text
)return response.data[0].embedding
defmain():# Load page descriptionswithopen(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 presenttry:
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 pageprint("\nCreating embeddings...")for i, page inenumerate(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==0or i ==len(pages)-1:print(f" Indexed {i +1}/{len(pages)} pages")print(f"\n✓ Index created with {collection.count()} documents")# Quick retrieval testprint("\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()Mogelijk gemaakt door
Hier zet de functie get_embedding(text) tekst uit de beschrijving en broninfo om naar een dichte vector met OpenAI’s text-embedding-3-small-model. Met 1536 dimensies kunnen die vectoren semantische betekenis vangen voor similarity search.
main() laadt verwerkte pagina’s uit all_pages.json van de vorige stap, initialiseert ChromaDB met persistente opslag, verwijdert/maakt de collectie opnieuw aan, genereert embeddings voor elke pagina, slaat ze op met rijke metadata (bron, paginanummer, afbeeldingspad) en draait een snelle retrievaltest om te verifiëren dat de indexering werkt.
De metadata is hier belangrijk. We slaan het image_path op zodat we later de echte afbeelding kunnen ophalen en aan GPT-4o kunnen tonen tijdens de generatie. De beschrijving wordt doorzocht, maar de afbeelding wordt getoond.
Stap 4: De RAG-pijplijn samenstellen
Hier is de volledige multimodale RAG-pijplijn. Een query wordt ge-embed, relevante pagina’s worden opgehaald, afbeeldingen geladen, en GPT-4o genereert een antwoord op basis van tekstbeschrijvingen en visuals.
Maak step4_query.py aan:
"""
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)defget_embedding(text):"""Get embedding from OpenAI."""
response = client.embeddings.create(
model="text-embedding-3-small",input=text
)return response.data[0].embedding
defretrieve(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
defgenerate_answer(query, retrieved_results):"""Generate answer using GPT-4o with retrieved images."""ifnot retrieved_results["ids"][0]:return"No relevant pages found.",[]# Build image content for GPT-4o
image_content =[]
sources =[]for doc, metadata inzip(retrieved_results["documents"][0], retrieved_results["metadatas"][0]):
image_path = metadata["image_path"]ifnot os.path.exists(image_path):continuewithopen(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
defanswer_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
defmain():# Check index existstry:
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 lineiflen(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 modeprint("\nAsk questions about IKEA assembly. Type 'quit' to exit.\n")whileTrue:try:
query =input("Question: ").strip()except KeyboardInterrupt:breakifnot 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()Mogelijk gemaakt door
Uitleg van de kernfuncties:
get_embedding(text): genereert een vectorembedding voor de query met text-embedding-3-small, voor semantische similarity search in ChromaDB.
retrieve(query, top_k=TOP_K): embedt de query, bevraagt de collectie op de top-k dichtste buren en retourneert documenten en metadata (bijv. afbeeldingspaden) via include=["documents", "metadatas"].
generate_answer(query, retrieved_results): laadt topafbeeldingen als base64, bouwt een multimodale GPT-4o-prompt met afwisselend paginalabels en afbeeldingen, en genereert een gedetailleerd antwoord met IKEA-specifieke instructies.
answer_question(query): regisseert retrieval en generatie, met voortgangslog.
main(): controleert of de index bestaat, ondersteunt CLI-single queries of interactieve chatmodus ("quit", "exit" of Ctrl+C om te stoppen).
Stap 5: De RAG-pijplijn draaien
Je kunt het script draaien en je vraag intypen, of het direct uitvoeren met een specifieke vraag, zo:
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 7Mogelijk gemaakt door
Ziet er goed uit! Het systeem haalt pagina’s op op basis van de beschrijvingen, laadt de daadwerkelijke afbeeldingen en GPT-4o genereert een antwoord door direct naar de diagrammen te kijken.
Evaluatie van multimodale RAG
Een multimodaal RAG-systeem bouwen is één ding. Weten of het goed werkt is iets anders. En het betrouwbaar in productie draaien voegt nog een laag uitdagingen toe.
De standaard RAG-metrics blijven gelden, maar je moet ze uitbreiden voor visuele content.
Retrievalkwaliteit
Precision@k meet hoeveel van je opgehaalde pagina’s daadwerkelijk relevant waren. Het bestraft irrelevante pagina’s in top-k, maar voor generatie is het hebben van de sleutelafbeelding belangrijker dan perfecte top-k.
Recall@k meet hoe vaak de juiste pagina’s in je top k-resultaten verschijnen. Dit is relevanter voor onze IKEA-RAG usecase, omdat we willen dat het systeem minstens één relevante pagina vindt in de top-k (bijv. k=3) resultaten. Zo krijgt GPT-4o visuele context om correct te antwoorden, zelfs als andere resultaten ruis zijn.
Voor ons IKEA-systeem bouw je een testset met bijvoorbeeld 50 vragen en label je de "juiste" pagina’s per vraag. Vervolgens kun je evalueren met de volgende functie:
defevaluate_retrieval(test_cases, top_k=3):"""
test_cases: [{"query": str, "relevant_pages": ["image_path1", ...]}]
"""
hits =0for test in test_cases:
results = retrieve(test["query"], top_k=top_k)
retrieved =[m["image_path"]for m in results["metadatas"][0]]ifany(p in retrieved for p in test["relevant_pages"]):
hits +=1return hits /len(test_cases)Mogelijk gemaakt door
Generatiekwaliteit
Iets wat lastiger automatisch te meten is, is de kwaliteit van de generatie.
Je kunt mensen antwoorden laten beoordelen op juistheid en behulpzaamheid, of een LLM als jurylid gebruiken om te evalueren of antwoorden kloppen gegeven de bronafbeeldingen.
Controleer bij diagrammen met genummerde stappen of het model de juiste stapnummers toekent.
Multimodaal-specifieke checks
Verdere evaluaties kunnen de volgende vragen beantwoorden:
Vindt het systeem beeldgebaseerde antwoorden wanneer het antwoord visueel is?
Als het model iets in een afbeelding aanhaalt, is die verwijzing dan accuraat?
Deze vereisen handmatige review, in elk geval voor een steekproef aan queries.
Best practices voor multimodale RAG
Wil je een productieklare multimodale RAG-pijplijn bouwen, houd dan het volgende in gedachten:
Voorbewerkingskosten lopen op: Vision-calls voor afbeeldingsbeschrijving zijn het dure deel. Voor een grote documentencorpus: batchverwerken buiten piekuren en beschrijvingen agressief cachen. Overweeg een goedkoper model (in ons geval bijv. GPT-4o-mini) voor beschrijvingsgeneratie als de kwaliteit voldoet aan je usecase.
Query-latency telt: GPT-4o met afbeeldingen kost 5 tot 10 seconden per query. Streaming helpt gebruikers snel deelresultaten zien, en het cachen van veelgestelde vragen helpt bij herhaalde queries. Twee pagina’s ophalen in plaats van vijf kan schelen qua tokens als dat al genoeg context is.
Monitor wat ertoe doet: Volg retrieval-latency (moet onder 100 ms liggen voor kleine collecties), generatie-latency (reken op 5 tot 10 seconden met afbeeldingen), API-kosten per query en gebruikersfeedback als je die verzamelt.
Foutafhandeling redt je: Er gaat wel eens iets mis. Bouw fallbacks, zoals in de onderstaande code.
defanswer_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]returnf"Based on the instructions: {context}...",[]return"Sorry, couldn't process that question.",[]Mogelijk gemaakt door
Als je pijplijn meerdere stappen moet nemen (zoeken → verifiëren → opnieuw zoeken → antwoorden), dan helpen agentic RAG-patronen je om dat netjes te structureren.
Conclusie
Multimodale RAG breidt retrieval-augmented generation uit om het volledige spectrum aan content in real-world documenten aan te kunnen, inclusief afbeeldingen, diagrammen, tabellen en grafieken.
We hebben het succesvol gebruikt om IKEA-handleidingen te bevragen, die notoir visueel zijn, waar een aanpak met alleen tekst niet had gewerkt. De hele pijplijn draait lokaal behalve de API-calls.
De kerninzichten: je hebt geen complexe multimodale embeddings nodig om dit te laten werken. Door goede tekstbeschrijvingen van visuele content te genereren, kun je standaard tekstretrieval gebruiken om relevante afbeeldingen te vinden en vervolgens een vision-language model het zware werk laten doen tijdens de generatie.
Wil je voortbouwen op wat we hier hebben behandeld? Start dan vandaag nog met de AI Engineer-carrièreroute!
Multimodal RAG FAQs
What types of data can multimodal RAG handle?⌄
Afbeeldingen, PDF’s met grafieken, tabellen, diagrammen, audio (via transcripties) en videokeyframes. Kortom: alle content waar je wilt dat AI naar kan verwijzen bij het beantwoorden van vragen.
Do I need separate vector databases for different modalities?⌄
Niet per se. De meeste productiesystemen gebruiken één enkele vectorstore met één embeddingruimte. Je kunt alles in dezelfde dimensionale ruimte embedden zodat retrieval werkt over contenttypes heen.
Can I use OpenAI for the entire pipeline?⌄
Ja. GPT-4o verwerkt zowel beeldbegrip als antwoordgeneratie, terwijl text-embedding-3-small de vectorskant afhandelt. Je kunt een complete multimodale RAG bouwen binnen het OpenAI-ecosysteem.
What's the biggest challenge in multimodal RAG?⌄
Afstemming. Tekst, afbeeldingen en gestructureerde data in dezelfde embeddingruimte krijgen zodat retrieval echt werkt over modaliteiten heen. De chunkingstrategie komt op een goede tweede plaats.
How much does it cost to run?⌄
Voor de IKEA-tutorial hier, reken op zo’n $2 totaal. Het dure deel zijn de GPT-4o-calls voor afbeeldingsbeschrijving tijdens de voorbewerking. Eenmaal geïndexeerd zijn queries goedkoop.
Josep is een freelance Data Scientist die zich richt op Europese projecten, met expertise in dataopslag, -verwerking, geavanceerde analyses en impactvolle data storytelling.
Als docent geeft hij Big Data in de masteropleiding aan de Universiteit van Navarra en deelt hij inzichten via artikelen op platforms als Medium, KDNuggets en DataCamp. Josep schrijft ook over Data en Tech in zijn nieuwsbrief Databites (databites.tech).
Hij heeft een bachelor in Engineering Physics van de Polytechnische Universiteit van Catalonië en een master in Intelligent Interactive Systems van de Pompeu Fabra-universiteit.
Leer hoe je AI in softwareapplicaties kunt integreren met behulp van API's en open-sourcebibliotheken. Begin vandaag nog aan je reis om AI-ingenieur te worden!
Word een expert in RAG met Weaviate! Voeg tekst en afbeeldingen toe om ze later terug te vinden en probeer vector-, BM25- en hybride zoekopdrachten uit.
Stel, je bent IKEA-meubels aan het monteren. Je staart naar stap 14 in de handleiding, maar je krijgt niet helder welke schroef waar hoort. Het diagram laat iets zien, maar het kwartje valt niet.
Als je zou vragen: "Waar is het kleine metalen onderdeel in stap 14 voor?", dan schiet een standaard tekstgebaseerd retrieval-augmented generation (RAG)-systeem tekort. Het antwoord staat in het diagram zelf, niet in tekst op de pagina. IKEA-handleidingen zijn berucht woordloos.
Daar komt multimodale RAG om de hoek kijken.
In deze tutorial laat ik je stap voor stap zien hoe je een multimodaal RAG-systeem bouwt dat vragen kan beantwoorden over documenten met afbeeldingen, diagrammen en gemengde inhoud. We gebruiken IKEA-montagehandleidingen als testcase omdat die een goede stresstest zijn: vooral visueel, minimale tekst, en precies het soort materiaal waarbij mensen hulp nodig hebben.
We gebruiken overal de modellen van OpenAI: GPT-4o voor beeldbegrip en het genereren van antwoorden, en
text-embedding-3-smallvoor het maken van doorzoekbare vectoren. Wil je RAG-pijplijnen bouwen met een ander framework, bekijk dan onze cursus RAG met LangChain.Aan het eind heb je een werkend systeem dat je lokaal kunt draaien en kunt aanpassen voor je eigen documenten.
Wat is multimodale RAG?
Multimodale RAG breidt het standaard RAG-patroon uit om meer dan alleen tekst te verwerken. Het kernidee blijft hetzelfde, maar "context" omvat nu ook afbeeldingen, tabellen, diagrammen en andere niet-tekstuele content.
Waarom is dat belangrijk? Omdat echte documenten niet alleen uit tekst bestaan.
Denk aan wat je in de praktijk gebruikt:
Een systeem dat alleen tekst verwerkt, werkt met onvolledige informatie. Het is alsof je een recept probeert te begrijpen door alleen de ingrediëntenlijst te lezen en de foto’s over te slaan die laten zien hoe "voorzichtig omscheppen" eruitziet.
Traditionele RAG ziet een PDF als een stroom tekens. Het extraheert tekst, hakt die in stukken, embedt de stukken en klaar. Afbeeldingen worden volledig overgeslagen, of hooguit omgezet naar
[IMAGE]-placeholdertokens. Tabellen veranderen in verminkte tekstregels die hun structuur verliezen.Multimodale RAG pakt het anders aan: afbeeldingen worden beschreven of direct ge-embed, en tabellen worden geparsed naar gestructureerde formaten. Het retrievalsysteem kan een query als "netwerk-topologiediagram" koppelen aan zowel tekstuele beschrijvingen van netwerkarchitectuur als echte visuele diagrammen. Het generatiemodel kan een afbeelding bekijken en redeneren over wat erop staat.
Aspect
RAG met alleen tekst
Multimodale RAG
Invoertypen
Platte tekst, eventueel Markdown
Tekst, afbeeldingen, tabellen, diagrammen, gemengde documenten
Wat wordt ge-embed
Tekstchunks
Tekstchunks plus afbeeldingsbeschrijvingen of afbeeldingsembeddings
Generatiemodel
Elke LLM
Vision-language model (GPT-4o, Claude, Gemini)
Beste voor
Tekstzware documenten
Handleidingen, rapporten, slides, alles met visuele content
Als "multimodaal" nog vaag voelt als parapluterm, dan is deze blog over Multimodale AI een heldere uitleg met concrete voorbeelden.
Hoe werkt multimodale RAG?
De pijplijn heeft meer bewegende delen dan RAG met alleen tekst, maar de structuur is vergelijkbaar. Data wordt verwerkt en ge-embed, komt in een vectorstore terecht en wordt opgehaald zodra iemand een vraag stelt.
Zo loopt het proces:
Documentinname en parsing
Ruwe documenten (PDF’s, afbeeldingen, slides) worden opgesplitst in onderdelen. Je haalt tekst eruit, identificeert afbeeldingen, parseert tabellen en bepaalt hoe alles ruimtelijk samenhangt. Een diagram op pagina 5 kan een bijschrift eronder hebben en een verwijzing in de tekst op pagina 4. Die relaties moet je vastleggen.
Contentvoorbewerking
Verschillende contenttypen vragen om verschillende behandeling. Voor afbeeldingen heb je twee hoofdopties: tekstbeschrijvingen genereren met een visionmodel (zoals GPT-4o), of afbeeldingen direct embedden met een multimodale encoder (zoals CLIP). De beschrijvingsaanpak is eenvoudiger en werkt met standaard tekstembeddings. Tabellen zet je om naar gestructureerde formaten of vat je samen in natuurlijke taal.
Daarna wordt alles omgezet naar vectoren. Gebruik je de beschrijvingsaanpak, dan embed je simpelweg alle tekst (originele tekst plus gegenereerde afbeeldingsbeschrijvingen) met een standaard tekstembeddingmodel. Het belangrijkste is dat queries over visuele content nu op die beschrijvingen kunnen matchen.
Indexering en opslag
Vectoren worden opgeslagen in een database met metadata, inclusief het brondocument, paginanummer, contenttype en, voor afbeeldingen, het pad naar het originele bestand. Dat afbeeldingspad heb je later nodig wanneer je het aan het generatiemodel wilt tonen.
Retrieval
Als er een query binnenkomt, embed je die met hetzelfde model, zoek je naar de dichtstbijzijnde buren en retourneer je de beste matches. De opgehaalde chunks kunnen verwijzen naar tekst, afbeeldingen of beide.
Generatie
Geef de query, samen met de opgehaalde context, aan een vision-language model. Als de opgehaalde chunks naar afbeeldingen verwijzen, laad die afbeeldingen dan en voeg ze toe aan de prompt. Het model ziet zowel de tekstcontext als de echte afbeeldingen, en genereert vervolgens een antwoord.
Bij de generatiestap blinkt GPT-4o echt uit. Het kan naar een IKEA-diagram met schroefplaatsing kijken en in gewone taal uitleggen wat er gebeurt. Het kan een grafiek lezen en de trend samenvatten. Het kan details in technische tekeningen opmerken die een tekstbeschrijving kan missen.
Multimodale RAG implementeren
We gaan bouwen. We maken een systeem dat vragen kan beantwoorden over IKEA-montagehandleidingen, stap voor stap van setup tot een werkende query-interface.
Stap 0: Framework en tools kiezen
Voor deze tutorial houden we het simpel:
Component
Keuze
Waarom
Embeddings
text-embedding-3-small
Goedkoop, goede kwaliteit, dezelfde API
Vectorstore
ChromaDB
Draait lokaal, geen setup, persistent
Visionmodel
GPT-4o
Topklasse voor beeldbegrip
Generatie
GPT-4o
Zelfde model, kan beide taken aan
Je kunt alternatieven inwisselen. Pinecone of Weaviate voor de vectorstore als je wilt opschalen. Claude of Gemini zijn goede alternatieven voor de generatie. De algemene architectuur blijft in die gevallen hetzelfde.
Waarom geen CLIP of ColPali voor embeddings? Dat kan. Daarmee embed je afbeeldingen direct zonder eerst tekstbeschrijvingen te genereren. Maar het voegt complexiteit toe en vereist vaak een GPU. De beschrijvingsaanpak werkt goed voor documentachtige content waarbij je semantische details wilt vastleggen, niet alleen visuele gelijkenis.
Stap 1: De omgeving opzetten
Laten we beginnen met dependencies en omgevingsvariabelen om onze projectomgeving op te zetten.
Dependencies installeren
Je hebt eerst
popplernodig voor PDF-verwerking.Ubuntu/Debian:
sudo apt-get install poppler-utilsmacOS (via Homebrew):
brew install popplerWindows: Download van de releasepagina en voeg toe aan PATH
Installeer daarna de Python-packages. Naast ChromaDB hebben we pdf2image nodig om PDF’s naar afbeeldingen te converteren, pillow voor beeldbewerking en requests om de PDF-bestanden te downloaden.
API-sleutel en bestanden instellen
Om de modellen te gebruiken, stel je je OpenAI API-sleutel in als omgevingsvariabele.
Unix/Linux/macOS:
Windows:
Laten we nu de projectstructuur opzetten en de voorbeelddata ophalen. Het script bevat al de download-URL’s, maar als je de PDF’s eerst wilt bekijken, hier zijn de directe links naar zowel het MALM-bureau als het MALM-bedframe.
Maak een bestand genaamd
step1_setup.py:Voer het script uit om de initiële setup af te ronden:
Je zou de IKEA-PDF’s moeten zien in je
data/-map.Stap 2: Documenten voorbewerken
IKEA-handleidingen zijn lastig. Ze bestaan vooral uit diagrammen met minimale tekst. We moeten PDF-pagina’s converteren naar afbeeldingen en dan GPT-4o gebruiken om te beschrijven wat elke pagina laat zien.
Maak
step2_preprocess.pyaan:Dit doen de functies:
convert_pdf_to_images(pdf_path): rendert elke PDF-pagina als een PNG-afbeelding op 150 DPI metpdf2image. Zo haal je diagrammen zonder selecteerbare tekst eruit, met consistente bestandsnamen zoalsmalm_desk_page_001.png.describe_page(image_path): codeert de afbeelding alsbase64en stuurt die naar GPT-4o met een gerichte system prompt. Het model geeft een gedetailleerde tekstbeschrijving terug met stappen, onderdelen en aantallen voor semantisch zoeken.main(): regisseert alles: laadt gecachete beschrijvingen om herverwerking te vermijden, converteert PDF’s naar afbeeldingen, genereert waar nodig nieuwe beschrijvingen, slaat incrementele cache op en schrijftall_pages.jsonmet metadata voor indexering.Voer het script zo uit:
Deze stap duurt enkele minuten. GPT-4o analyseert elke pagina en genereert een gedetailleerde beschrijving. Beschrijvingen worden gecachet, dus als je het opnieuw draait, slaat het reeds verwerkte pagina’s over.
Wat gebeurt hier? We zetten visuele content om in tekst die doorzoekbaar is. Als iemand vraagt: "Hoe bevestig ik de poten?", dan zoekt de retriever in deze beschrijvingen en vindt pagina’s die het bevestigen van poten noemen, ook al staat er op de originele pagina geen tekst.
Heb je de basispijplijn eenmaal staan, dan komen de volgende winstpunten uit retrievaltrucs, zoals hybrid search, reranking of query rewriting. Wil je daarmee kennismaken, lees dan deze blog over Geavanceerde RAG-technieken.
Stap 3: Multimodale data opslaan en indexeren
Nu embedden we de beschrijvingen en slaan we ze op in ChromaDB.
Maak
step3_index.pyaan:Hier zet de functie
get_embedding(text)tekst uit de beschrijving en broninfo om naar een dichte vector met OpenAI’stext-embedding-3-small-model. Met 1536 dimensies kunnen die vectoren semantische betekenis vangen voor similarity search.main()laadt verwerkte pagina’s uitall_pages.jsonvan de vorige stap, initialiseert ChromaDB met persistente opslag, verwijdert/maakt de collectie opnieuw aan, genereert embeddings voor elke pagina, slaat ze op met rijke metadata (bron, paginanummer, afbeeldingspad) en draait een snelle retrievaltest om te verifiëren dat de indexering werkt.Voer uit:
De metadata is hier belangrijk. We slaan het
image_pathop zodat we later de echte afbeelding kunnen ophalen en aan GPT-4o kunnen tonen tijdens de generatie. De beschrijving wordt doorzocht, maar de afbeelding wordt getoond.Stap 4: De RAG-pijplijn samenstellen
Hier is de volledige multimodale RAG-pijplijn. Een query wordt ge-embed, relevante pagina’s worden opgehaald, afbeeldingen geladen, en GPT-4o genereert een antwoord op basis van tekstbeschrijvingen en visuals.
Maak
step4_query.pyaan:Uitleg van de kernfuncties:
get_embedding(text): genereert een vectorembedding voor de query mettext-embedding-3-small, voor semantische similarity search in ChromaDB.retrieve(query, top_k=TOP_K): embedt de query, bevraagt de collectie op de top-k dichtste buren en retourneert documenten en metadata (bijv. afbeeldingspaden) viainclude=["documents", "metadatas"].generate_answer(query, retrieved_results): laadt topafbeeldingen alsbase64, bouwt een multimodale GPT-4o-prompt met afwisselend paginalabels en afbeeldingen, en genereert een gedetailleerd antwoord met IKEA-specifieke instructies.answer_question(query): regisseert retrieval en generatie, met voortgangslog.main(): controleert of de index bestaat, ondersteunt CLI-single queries of interactieve chatmodus ("quit", "exit" of Ctrl+C om te stoppen).Stap 5: De RAG-pijplijn draaien
Je kunt het script draaien en je vraag intypen, of het direct uitvoeren met een specifieke vraag, zo:
Ziet er goed uit! Het systeem haalt pagina’s op op basis van de beschrijvingen, laadt de daadwerkelijke afbeeldingen en GPT-4o genereert een antwoord door direct naar de diagrammen te kijken.
Evaluatie van multimodale RAG
Een multimodaal RAG-systeem bouwen is één ding. Weten of het goed werkt is iets anders. En het betrouwbaar in productie draaien voegt nog een laag uitdagingen toe.
De standaard RAG-metrics blijven gelden, maar je moet ze uitbreiden voor visuele content.
Retrievalkwaliteit
Precision@k meet hoeveel van je opgehaalde pagina’s daadwerkelijk relevant waren. Het bestraft irrelevante pagina’s in top-k, maar voor generatie is het hebben van de sleutelafbeelding belangrijker dan perfecte top-k.
Recall@k meet hoe vaak de juiste pagina’s in je top k-resultaten verschijnen. Dit is relevanter voor onze IKEA-RAG usecase, omdat we willen dat het systeem minstens één relevante pagina vindt in de top-k (bijv. k=3) resultaten. Zo krijgt GPT-4o visuele context om correct te antwoorden, zelfs als andere resultaten ruis zijn.
Voor ons IKEA-systeem bouw je een testset met bijvoorbeeld 50 vragen en label je de "juiste" pagina’s per vraag. Vervolgens kun je evalueren met de volgende functie:
Generatiekwaliteit
Iets wat lastiger automatisch te meten is, is de kwaliteit van de generatie.
Je kunt mensen antwoorden laten beoordelen op juistheid en behulpzaamheid, of een LLM als jurylid gebruiken om te evalueren of antwoorden kloppen gegeven de bronafbeeldingen.
Controleer bij diagrammen met genummerde stappen of het model de juiste stapnummers toekent.
Multimodaal-specifieke checks
Verdere evaluaties kunnen de volgende vragen beantwoorden:
Vindt het systeem beeldgebaseerde antwoorden wanneer het antwoord visueel is?
Als het model iets in een afbeelding aanhaalt, is die verwijzing dan accuraat?
Deze vereisen handmatige review, in elk geval voor een steekproef aan queries.
Best practices voor multimodale RAG
Wil je een productieklare multimodale RAG-pijplijn bouwen, houd dan het volgende in gedachten:
Voorbewerkingskosten lopen op: Vision-calls voor afbeeldingsbeschrijving zijn het dure deel. Voor een grote documentencorpus: batchverwerken buiten piekuren en beschrijvingen agressief cachen. Overweeg een goedkoper model (in ons geval bijv. GPT-4o-mini) voor beschrijvingsgeneratie als de kwaliteit voldoet aan je usecase.
Query-latency telt: GPT-4o met afbeeldingen kost 5 tot 10 seconden per query. Streaming helpt gebruikers snel deelresultaten zien, en het cachen van veelgestelde vragen helpt bij herhaalde queries. Twee pagina’s ophalen in plaats van vijf kan schelen qua tokens als dat al genoeg context is.
Monitor wat ertoe doet: Volg retrieval-latency (moet onder 100 ms liggen voor kleine collecties), generatie-latency (reken op 5 tot 10 seconden met afbeeldingen), API-kosten per query en gebruikersfeedback als je die verzamelt.
Foutafhandeling redt je: Er gaat wel eens iets mis. Bouw fallbacks, zoals in de onderstaande code.
Als je pijplijn meerdere stappen moet nemen (zoeken → verifiëren → opnieuw zoeken → antwoorden), dan helpen agentic RAG-patronen je om dat netjes te structureren.
Conclusie
Multimodale RAG breidt retrieval-augmented generation uit om het volledige spectrum aan content in real-world documenten aan te kunnen, inclusief afbeeldingen, diagrammen, tabellen en grafieken.
We hebben het succesvol gebruikt om IKEA-handleidingen te bevragen, die notoir visueel zijn, waar een aanpak met alleen tekst niet had gewerkt. De hele pijplijn draait lokaal behalve de API-calls.
De kerninzichten: je hebt geen complexe multimodale embeddings nodig om dit te laten werken. Door goede tekstbeschrijvingen van visuele content te genereren, kun je standaard tekstretrieval gebruiken om relevante afbeeldingen te vinden en vervolgens een vision-language model het zware werk laten doen tijdens de generatie.
Wil je voortbouwen op wat we hier hebben behandeld? Start dan vandaag nog met de AI Engineer-carrièreroute!