Construire un système multimodal RAG alimenté par l’IA avec Docling et Granite

Auteurs

BJ Hargrave

Open Source Developer, STSM

Erika Russi

Data Scientist

IBM

Dans ce tutoriel, vous utiliserez Docling d’IBM et la solution open source IBM Granite Vision, des embeddings textuels et des modèles d’IA générative pour créer un système RAG. Ces modèles sont disponibles via des frameworks open source. Dans ce tutoriel, nous utiliserons Replicate pour nous connecter à IBM Granite Vision et aux modèles d’IA générative, ainsi que Hugging Face pour nous connecter au modèle d’embeddings.

Génération augmentée par récupération multimodale

La génération augmentée par récupération est une technique qui relie les grands modèles de langage (LLM) à une base de connaissances autre que les données sur lesquelles ils ont été entraînés, sans avoir à effectuer un réglage fin. La RAG traditionnelle se limite aux cas d’utilisation textuels tels que la synthèse et les chatbots.

La RAG multimodale peut utiliser des LLM multimodaux (MLLM) pour traiter les informations provenant de plusieurs types de données à inclure dans la base de connaissances externe utilisée. Les données multimodales peuvent comprendre des textes, des images, des fichiers audio et vidéo, entre autres. Les LLM multimodaux les plus connus sont Gemini de Google, Llama 3.2 de Meta et GPT-4 et GPT-4o d’OpenAI.

Pour cette recette, vous utiliserez un modèle IBM Granite capable de traiter différentes modalités. Vous allez créer un système d’IA pour répondre aux requêtes utilisateur en temps réel à partir de données non structurées dans un PDF.

Aperçu du tutoriel

Bienvenue ! Dans ce tutoriel Granite, vous apprendrez à exploiter la puissance des outils avancés pour construire un pipeline RAG multimodal alimenté par l’IA. Ce tutoriel vous guidera tout au long des processus suivants :

  • Prétraitement des documents : vous apprendrez à gérer des documents provenant de diverses sources, à les analyser, à les transformer en formats utilisables et à les stocker dans des bases de données vectorielles grâce à Docling. Vous utiliserez un MLLM Granite pour générer la description des images contenues dans les documents.
  • RAG : vous découvrirez comment connecter des LLM tels que Granite à des bases de connaissances externes, afin d’améliorer les réponses aux requêtes et de générer des informations utiles.
  • LangChain pour l’intégration des workflows : vous découvrirez comment utiliser LangChain pour rationaliser et orchestrer les workflows de traitement et de récupération des documents, permettant une interaction fluide entre les différents composants du système.

Ce tutoriel utilise trois technologies de pointe :

  1. Docling : une boîte à outils open source utilisée pour analyser et convertir les documents.
  2. Granite : un LLM de pointe qui offre de solides capacités en matière de langage naturel et un modèle de langage-vision permettant la génération d’images à partir de textes.
  3. LangChain : un cadre utilisé pour construire des applications alimentées par des modèles de langage, conçu pour simplifier les workflows et intégrer des outils externes de façon fluide.

À la fin de ce tutoriel, vous pourrez accomplir les tâches suivantes :

  • Maîtriser le prétraitement des documents, le chunking et la compréhension d’images.
  • Intégrer des bases de données vectorielles pour améliorer les capacités de récupération.
  • Utiliser la RAG pour effectuer une récupération de données efficace et précise dans le cadre d’applications réelles.

Ce tutoriel est conçu pour les développeurs d’IA, les chercheurs et les passionnés qui souhaitent approfondir leurs connaissances en matière de gestion documentaire et de traitement automatique du langage naturel (TAL). Le tutoriel est également disponible sur le GitHub Granite Snack Cookbook de la communauté IBM Granite, sous forme de Jupyter Notebook.

Prérequis

  • Connaissances en programmation Python.
  • Connaissances de base en LLM, TAL et vision par ordinateur.

Étapes

Étape 1 : Installer les dépendances

! 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::"

Étape  : Sélectionner les modèles d’IA

Journalisation

Pour afficher certaines informations de journalisation, nous pouvons configurer le niveau de journalisation INFO.

REMARQUE : vous pouvez ignorer l'exécution de cette cellule.

import logging

logging.basicConfig(level=logging.INFO)


Charger les modèles Granite

Spécifiez le modèle d’embeddings à utiliser pour générer des embeddings vectoriels de texte. Nous utiliserons ici l’un des modèles d’embeddings Granite

Pour utiliser un modèle d’embeddings différent, remplacez cette cellule de code par une cellule de cette recette de modèle d’embeddings.

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)

 

Spécifiez le MLLM à utiliser pour la compréhension d’images. Nous utiliserons le modèle de vision Granite. 

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)

 

Spécifiez le modèle de langage à utiliser pour l’opération de génération RAG.  Ici, nous utilisons le client Replicate LangChain pour nous connecter à un modèle Granite depuis l’organisation ibm-granite sur Replicate.

Pour configurer Replicate, consultez la section Premiers pas avec Replicate. Pour vous connecter à un modèle sur un fournisseur autre que Replicate, remplacez cette cellule de code par une cellule de la recette du composant LLM.

Pour vous connecter à un modèle sur un fournisseur autre que Replicate, remplacez cette cellule de code par une cellule de la recette du composant 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.
    },
)

Étape 3 : préparer les documents pour la base de données vectorielle

Dans cet exemple, à partir d’un ensemble de documents sources, nous utilisons Docling pour convertir les documents en texte et en images. Le texte est ensuite divisé en morceaux. Les images sont traitées par le MLLM pour générer des synthèses d’images.

Utiliser Docling pour télécharger les documents et les convertir en texte et images

Docling téléchargera les documents PDF et les traitera afin que nous puissions obtenir le texte et les images qu’ils contiennent. Le fichier PDF contient différents types de données, notamment des textes, des tableaux, des graphiques et des images.

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 }

 

Une fois les documents traités, nous poursuivons le traitement des éléments textuels qu’ils contiennent. Nous les découpons en morceaux de taille appropriée pour le modèle d’embedding que nous utilisons. Une liste de documents LangChain est créée à partir des morceaux de texte.

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”)

 

Ensuite, nous traitons tous les tableaux contenus dans les documents. Nous convertissons les données des tableaux au format Markdown pour les transmettre au modèle de langage. Une liste de documents LangChain est créée à partir des rendus Markdown des tableaux.

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”)

 

Enfin, nous traitons toutes les images contenues dans les documents. Nous utilisons ici le modèle de langage-vision pour comprendre le contenu d’une image. Dans cet exemple, nous nous intéressons à toute information textuelle contenue dans l’image. Vous pouvez modifier le texte du prompt pour voir si cela peut améliorer les résultats.

REMARQUE : le traitement peut prendre beaucoup de temps, selon le nombre d’images et le service qui exécute le modèle de langage-vision.

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”)

 

Nous pouvons ensuite afficher les documents LangChain créés à partir des documents d’entrée.

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

Alimenter la base de données vectorielle

En utilisant le modèle d’embedding, nous chargeons les documents à partir des morceaux de texte et des légendes d’images générées dans une base de données vectorielle. Cette base de données vectorielle nous permet d’effectuer facilement une recherche de similarité sémantique entre nos documents.

REMARQUE : Le remplissage de la base de données vectorielle peut prendre un certain temps selon votre modèle d’embedding et votre service.

Choisir votre base de données vectorielle

Spécifiez la base de données à utiliser pour stocker et récupérer les vecteurs d’embedding.

Pour vous connecter à une base de données vectorielle autre que Milvus, remplacez cette cellule de code par une cellule issue de cette recette de base de données vectorielle.

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”},
)

 

Nous ajoutons maintenant tous les documents LangChain (textes, tableaux et descriptions d’images) à la base de données vectorielles.

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”)

Étape 4 : RAG avec Granite

Maintenant que nous avons converti et vectorisé nos documents, nous pouvons mettre en place notre pipeline RAG.

Récupérer les chunks pertinents

Ici, nous testons la base de données vectorielle en recherchant dans l’espace vectoriel les morceaux contenant des informations pertinentes pour notre requête. Nous affichons les documents associés à la description d’image récupérée.

N'hésitez pas à formuler d'autres requêtes.

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

 

Le document renvoyé doit répondre à la requête. Commençons donc à construire notre pipeline RAG.


Créer le pipeline RAG pour Granite

Nous commençons par créer les prompts permettant à Granite d’effectuer la requête RAG. Nous utilisons le template de chat Granite et fournissons les valeurs d’espace réservé que le pipeline LangChain RAG remplacera.

Ensuite, nous construisons le pipeline RAG en utilisant les templates de prompts Granite créés précédemment.

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,
)

Générer une réponse augmentée de récupération à une question

Le pipeline utilise la requête pour localiser les documents de la base de données vectorielle et les utiliser comme contexte pour la requête.

from ibm_granite_community.notebook_utils import wrap_text

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

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

Parfait ! Nous avons créé une application d’IA capable d’exploiter les connaissances contenues dans le texte et les images des documents sources.

Étapes suivantes

  • Découvrir des workflows RAG avancés pour d’autres secteurs.
  • Expérimenter avec d’autres types de documents et des jeux de données plus volumineux.
  • Optimiser le prompt engineering pour obtenir de meilleures réponses Granite.
Solutions connexes
IBM watsonx.ai

Entraînez, validez, réglez et déployez une IA générative, des modèles de fondation et des capacités de machine learning avec IBM watsonx.ai, un studio d’entreprise nouvelle génération pour les générateurs d’IA. Créez des applications d’IA en peu de temps et avec moins de données.

Découvrir watsonx.ai
Solutions d’intelligence artificielle

Mettez l’IA au service de votre entreprise en vous appuyant sur l’expertise de pointe d’IBM dans le domaine de l’IA et sur son portefeuille de solutions.

Découvrir les solutions d’IA
Services d’IA

Réinventez les workflows et les opérations critiques en ajoutant l’IA pour optimiser les expériences, la prise de décision et la valeur métier en temps réel.

Découvrir les services d’IA
Passez à l’étape suivante

Bénéficiez d’un accès centralisé aux fonctionnalités couvrant le cycle de développement de l’IA. Produisez des solutions IA puissantes offrant des interfaces conviviales, des workflows et un accès à des API et SDK conformes aux normes du secteur.

  1. Découvrir watsonx.ai
  2. Réserver une démo en direct