
---
- Extraire et préparer les schémas depuis les PDF
    
- Traduire les légendes et contextualiser les images pour LLM vision
    
- Utiliser les bons LLM aux bonnes étapes (Vision, Traduction, Résumé, Rerank, etc.)
    
- Intégrer efficacement les chunks enrichis dans Ragflow (structure, métadonnées, lien logique avec les autres chunks)
    
- Fournir un tutoriel détaillé étape par étape, avec du code Python lorsque pertinent
    
# Traitement Multimodal de PDFs Réglementaires avec Ragflow

## Contexte et Enjeux

Les documents PDF complexes, mêlant texte réglementaire en français et schémas annotés, posent un défi pour les pipelines RAG (Retrieval-Augmented Generation). Il s’agit d’extraire fidèlement le contenu textuel **et** visuel afin d’alimenter un moteur de recherche sémantique et de permettre à un LLM de répondre aux questions de l’utilisateur de manière précise et sourcée. Nous allons explorer toutes les méthodes pour **extraire et contextualiser les schémas** dans de tels PDF, puis détailler le choix des modèles d’IA (vision, résumé, traduction, rerank, etc.) à chaque étape, en précisant la langue de travail appropriée (anglais vs français). Nous fournirons ensuite un **tutoriel pas-à-pas** illustré de code Python pour mettre en œuvre ces étapes dans un pipeline RAGFlow. Enfin, nous proposerons des **conseils de structuration** de la base de connaissances Ragflow et de l’orchestration des assistants afin de maximiser la qualité des réponses.

## Extraction et Contextualisation des Schémas dans un PDF

Traiter les schémas (diagrammes, figures, graphiques) d’un PDF nécessite de combiner plusieurs techniques. L’objectif est de **capturer l’information visuelle** contenue dans ces schémas et de la lier au texte environnant (ex. légendes, références dans le corps du texte). Voici les stratégies principales :

- **Extraction directe d’images intégrées** : Si le PDF comporte des images matricielles intégrées (formats JPEG/PNG), on peut les extraire via une bibliothèque PDF. Par exemple, avec PyMuPDF on peut parcourir chaque page et extraire les blocs de type _image_ via `page.get_text("dict")` (qui renvoie un dict contenant texte et images) ([Images - PyMuPDF 1.25.4 documentation](https://pymupdf.readthedocs.io/en/latest/recipes-images.html#:~:text=2,images%20shown%20on%20a%20page)). Chaque bloc image fournit le _binary_ de l’image (qu’on peut sauvegarder) et sa position (_bbox_) dans la page ([Images - PyMuPDF 1.25.4 documentation](https://pymupdf.readthedocs.io/en/latest/recipes-images.html#:~:text=,0.0%2C%20228.2460174560547%2C%20100.0)). Cette approche est rapide et conserve la résolution originale de l’image.
    
- **Capture raster des zones graphiques** : Si le schéma n’est pas une image distincte (par ex., un dessin vectoriel dans le PDF), on peut **rasteriser la page ou la zone** correspondante. PyMuPDF permet de rendre une page ou un _rectangle_ de page en image via `page.get_pixmap(matrix=..., clip=...)`. On peut d’abord identifier la zone du schéma (par ex. via la position de la légende ou par une analyse de la mise en page), puis générer une image de cette zone. Des outils comme pdf2image (basé sur Poppler) peuvent aussi convertir chaque page en image haute résolution à analyser.
    
- **Détection automatique de la mise en page** : Pour localiser les figures et leurs légendes de manière fiable, on peut utiliser des modèles de _Document Layout Analysis_. Par exemple, Ragflow intègre un module de reconnaissance de layout qui classifie les éléments d’une page en catégories (texte courant, titre, **figure**, **légende de figure**, table, etc.) ([ragflow/deepdoc/README.md at main · infiniflow/ragflow · GitHub](https://github.com/infiniflow/ragflow/blob/main/deepdoc/README.md#:~:text=,components%20which%20covers%20most%20cases)) ([ragflow/deepdoc/README.md at main · infiniflow/ragflow · GitHub](https://github.com/infiniflow/ragflow/blob/main/deepdoc/README.md#:~:text=,Equation)). Ce type de modèle (semblable à LayoutParser ou PubLayNet) peut détecter qu’une zone est une _figure_ et la zone en dessous comme _Figure caption_. En combinant ces informations, on peut automatiquement **découper** chaque schéma et l’associer à sa légende. Ragflow DeepDoc fournit par exemple 10 composants de base dont _Figure_ et _Figure caption_, ce qui permet de savoir qu’“une image X est décrite par telle légende Y” ([ragflow/deepdoc/README.md at main · infiniflow/ragflow · GitHub](https://github.com/infiniflow/ragflow/blob/main/deepdoc/README.md#:~:text=Recognition,components%20which%20covers%20most%20cases)).
    
- **OCR des textes dans l’image** : Les schémas « annotés » contiennent souvent du texte (labels, commentaires dans le diagramme). Un OCR est nécessaire pour extraire ces textes **imbriqués dans l’image**. On peut utiliser Tesseract (via `pytesseract`) en spécifiant la langue française (`lang='fra'`) pour reconnaître les mots en français sur le schéma. Par exemple : ```python texte_image = pytesseract.image_to_string(Image.open("schema.png"), lang="fra")``` 

- **Contextualisation manuelle ou semi-automatique** : Dans certains cas complexes, un **découpage manuel** peut être nécessaire. Par exemple, si le PDF est très hétérogène, l’intervenant peut ouvrir le PDF, repérer les pages contenant des schémas d’importance, et définir manuellement des zones de capture. Cela peut être fait via un outil ou en coordonnant des *bounding boxes* à passer à la fonction de rasterisation (PyMuPDF ou autre). Cette méthode garantit de capturer exactement ce qui est pertinent, mais elle demande un effort humain. Idéalement, on la combine avec les méthodes automatiques (layout/OCR) pour gagner du temps – par exemple, vérifier et ajuster les zones détectées automatiquement.

**Association au texte réglementaire** : Une fois l’image du schéma extraite et son texte OCRisé, il faut la **lier au contexte textuel**. Cela se fait en récupérant la légende (souvent présente juste en-dessous ou au-dessus du schéma dans le PDF) et/ou en repérant les renvois dans le texte principal (ex. “voir Figure 5”). En pratique :

- La **légende** (caption) est généralement extraite par le parser PDF comme un bloc de texte à part. Il convient de l’isoler. Par exemple, Ragflow indique extraire pour chaque figure *“la figure elle-même avec sa légende et le texte inclus dans la figure”* ([ragflow/deepdoc/README.md at main · infiniflow/ragflow · GitHub](https://github.com/infiniflow/ragflow/blob/main/deepdoc/README.md#:~:text=,and%20text%20in%20the%20figures)). Si l’outil ne l’a pas fait, on peut rechercher dans le texte brut des motifs du type “Figure X : …” ou bien utiliser la détection de layout mentionnée plus haut.

- Le **texte environnant** qui fait référence à la figure peut être utile. Par exemple, un paragraphe qui introduit le schéma (“Le schéma ci-dessous illustre…”). On peut décider d’inclure ce paragraphe dans le même *chunk* que la description du schéma, ou au moins de s’y référer via des métadonnées (ex : “related_to=Figure 5”).

En résumé, l’extraction efficace des schémas combine **outils PDF** (pour isoler images et textes), **OCR** (pour lire le contenu interne des images) et éventuellement **analyse de layout** (pour automatiser l’identification des figures et légendes). L’objectif final est de produire pour chaque schéma une **description textuelle riche** (contenant la légende originale, les textes OCR, et une explication globale) qui pourra être indexée et utilisée par le LLM. 

## Choix des Modèles LLM à Chaque Étape  
Un pipeline RAG multimodal s’appuie sur divers modèles spécialisés. Voici les types de modèles intervenant et lesquels choisir pour ce cas d’usage, en précisant la langue d’entrée/sortie optimale pour chacun :

- **Modèle de Vision (LLM Vision)** : C’est le cœur de l’interprétation des schémas. Un LLM vision combine un encodeur d’image et un grand modèle de langue pour **décrire l’image** ou répondre à des questions sur celle-ci. Exemples appropriés : *LLaVA* (Large Language and Vision Assistant) ou *LLaMA-3 Vision*. LLaVA est un modèle open-source entraîné en instruction-following visuel (Vicuna + encodeur CLIP) apte à fournir des descriptions détaillées d’images ([Analyzing Images with LLaVA - Medium](https://medium.com/@natsunoyuki/analyzing-images-with-llava-f3ac169cbecf#:~:text=Analyzing%20Images%20with%20LLaVA%20,source%20chatbot%20to%20create)) ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=Vision%20models%20%C2%B7%20Ollama%20Blog,support%20higher%20resolution%20images%2C)). Les LLMs vision actuels ont été majoritairement entraînés en anglais, et **ont tendance à répondre en anglais** par défaut. En effet, des études ont montré qu’avec une image en entrée, même si la question est en français, un modèle LLaVA a de fortes chances de répondre en anglais ([](https://aclanthology.org/2024.findings-emnlp.783.pdf#:~:text=We%20uncover%20a%20surprising%20multilingual,a%20mechanistic%20analysis%20of%20the)) ([](https://aclanthology.org/2024.findings-emnlp.783.pdf#:~:text=to%20the%20query%20to%20a,First%20we)). Pour exploiter au mieux ces modèles, il est donc conseillé de **travailler en anglais pendant l’étape vision** (décrire l’image en anglais) puis de traduire en français le résultat. Nous aborderons ce point de langue plus loin. – En termes de choix, *LLaVA 1.5/1.6* en 7B ou 13B de paramètres (disponible via Ollama par ex.) fait partie des meilleurs candidats open-source. La version 1.6 a amélioré la **lecture de texte dans les images** et la compréhension de diagrammes ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=The%20LLaVA%20%28Large%20Language,supporting)), ce qui est précieux pour nos schémas annotés. D’autres alternatives comme *MiniGPT-4*, *BLIP-2* (pour du captioning plus simple) ou *Pix2Struct/Pixtral* (modèles récents de Mistral AI avec 12B+ paramètres) pourraient être considérées, mais LLaVA offre un bon compromis maturité/poids. **Langue d’utilisation** : fournir les consignes en anglais (par ex. “Describe this diagram…”) et s’attendre à une réponse en anglais (que l’on traduira ensuite). 

- **Modèle de Résumé (LLM de résumé)** : Un modèle de résumé n’est pas toujours indispensable, mais il peut intervenir pour **condenser des textes longs** ou structurer l’information. Dans notre pipeline, on pourrait l’utiliser pour : (1) résumer un long passage réglementaire en un chunk plus synthétique (si on craint de dépasser les limites de tokens ou de diluer la pertinence), ou (2) reformuler la description brute générée par l’OCR + LLM vision en un texte plus concis. Un LLM de résumé en français serait ici indiqué si l’on souhaite une sortie en français directement. Par exemple, on pourrait utiliser *GPT-3.5* ou *GPT-4* via API pour une grande qualité de résumé multilingue, ou opter pour un modèle open-source instruct finetuned, tel que *Mistral 7B* (ou sa déclinaison fine-tunée sur des données FR, ex. *Claire-Mistral-7B* ([OpenLLM-France/Claire-Mistral-7B-0.1 - Hugging Face](https://huggingface.co/OpenLLM-France/Claire-Mistral-7B-0.1#:~:text=Claire,7B%20on%20French%20conversational%20data))). Mistral 7B, bien que plus petit, a été salué pour ses capacités multilingues (notamment en français) et peut être utilisé localement. On pourrait lui demander : *« Résume le texte suivant en gardant les points clés… »*. **Langue** : si on résume du texte réglementaire français, rester en français pour ne pas perdre les nuances juridiques dans une traduction inutile. En revanche, si on résume une description d’image générée en anglais par LLaVA, on pourrait en profiter pour directement la traduire/résumer en français via un prompt (de type *« Traduits et résume ... »*).  

- **Traducteur (LLM ou API de traduction)** : La traduction est un composant crucial pour concilier l’anglais et le français dans le pipeline. Deux besoins : **FR → EN** (pour fournir au LLM vision un contexte compréhensible le cas échéant) et **EN → FR** (pour réinjecter les résultats dans la base en français). La solution de haute qualité est d’utiliser l’API DeepL, reconnue pour ses traductions précises y compris dans le domaine technique/réglementaire. Par exemple, l’endpoint `/translate` de DeepL permet de traduire un texte fourni en spécifiant la langue source et cible ([Translate text | DeepL API Documentation](https://developers.deepl.com/docs/api-reference/translate#:~:text=Example%20request%3A%20text%20translation%20,glossary)) ([Translate text | DeepL API Documentation](https://developers.deepl.com/docs/api-reference/translate#:~:text=,)). Un appel POST avec `text="Schéma de l'architecture logicielle"` et `target_lang="EN"` renverra *“Diagram of the software architecture”*. Inversement, on peut traduire la description anglaise d’une figure vers le français (*target_lang="FR"*). DeepL assure une **haute fidélité sémantique**, ce qui est essentiel dans un contexte réglementaire. En alternative open-source, on pourrait utiliser un modèle comme *NLLB (No Language Left Behind)* ou *M2M100* de Facebook pour la traduction automatique, ou même solliciter un LLM généraliste (par ex. *GPT-4*, ou *Mistral 7B* avec un prompt de traduction). Cependant, la consistance terminologique offerte par DeepL Pro est souvent un atout dans ce type de documents. **Langue** : on traduit bien sûr vers la langue requise par l’étape suivante (vers l’anglais pour l’étape vision, vers le français pour enrichir la KB).

- **Reranker (Modèle de re-classement)** : Un reranker sert à **affiner l’ordre des résultats de recherche** en évaluant la pertinence contextualisée. Dans Ragflow, la recherche initiale se fait via les **vecteurs d’embedding** (similarité cosinus), mais on peut activer un modèle de rerank pour re-trier les top résultats selon un critère plus précis ([HTTP API | RAGFlow](https://ragflow.io/docs/dev/http_api_reference#:~:text=match%20at%20L1565%20%2A%20%60,Default%20to%201024)) ([HTTP API | RAGFlow](https://ragflow.io/docs/dev/http_api_reference#:~:text=%2A%20%60,Default%20to%201024)). Typiquement, un reranker est un modèle de type *cross-encoder* qui prend la requête et un chunk en entrée et produit un score de pertinence. Par exemple, on pourrait utiliser un modèle **multilingue** comme `camembert/msmarco` (spécialisé QA FR) ou un modèle anglais multilingue comme `sentence-transformers/paraphrase-multilingual-mpnet-base-v2` en cross-encodage. Si on conserve tout en français, un modèle *french cross-encoder* serait idéal, sinon on pourrait traduire la requête et les chunks en anglais et utiliser un reranker anglais (mais c’est lourd). Ragflow propose un champ *rerank_model* dans la config de l’assistant ([HTTP API | RAGFlow](https://ragflow.io/docs/dev/http_api_reference#:~:text=%2A%20%60,Default%20to%201024)), on pourrait y brancher un modèle comme **OpenAI re-rank** ou un modèle local. En pratique, on choisira un reranker si l’on constate que de nombreux chunks bruités remontent et que le LLM final peine à choisir. Ce modèle n’a pas besoin d’être gros; même un MiniLM ou E5-large en cross-encoder peut suffire. **Langue** : à adapter selon le modèle choisi – idéalement français natif pour éviter de devoir tout traduire pendant la phase de retrieval.

- **Modèle QA final (Assistant Ragflow)** : Bien que non listé explicitement, il faut rappeler que dans le chat Ragflow, l’assistant lui-même est un LLM qui exploite les documents retrouvés. Ce modèle doit **bien maîtriser le français** et le style de réponse attendu (p. ex., rédiger de manière professionnelle et justifier par les sources). On peut configurer l’assistant Ragflow pour utiliser un modèle open-source hébergé localement (*via* Ollama ou autre). Par exemple, on pourrait utiliser *Llama-2 13B Chat* (qui a une bonne compréhension du français), ou une variante fine-tunée sur du français juridique. Mistral 7B peut également être utilisé s’il a été affiné sur des instructions (des initiatives comme Mistral/INRIA ont probablement amélioré la sortie française ([OpenLLM-France/Claire-Mistral-7B-0.1 - Hugging Face](https://huggingface.co/OpenLLM-France/Claire-Mistral-7B-0.1#:~:text=Claire,7B%20on%20French%20conversational%20data))). L’important est que ce modèle final saura **intégrer les extraits** (texte réglementaire et descriptions de schémas) pour formuler une réponse pertinente. On veillera à ce que la **sortie soit en français** (on peut le garantir via le prompt système de l’assistant). Ragflow permet de configurer ces paramètres dans l’interface Chat (description du rôle, ouverture de conversation, etc.) ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=The%20figure%20above%20shows%20the,configure%20the%20assistant%E2%80%99s%20settings%2C%20including)) ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=5,base%20that%20I%20created%20earlier)). Notamment, en activant *“Show Quote”* l’assistant pourra inclure les citations des documents dans sa réponse ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=5,base%20that%20I%20created%20earlier)), ce qui est souhaitable ici pour appuyer les réponses par des extraits du PDF (page, figure, etc.). **Langue** : français impératif en sortie, avec potentiellement l’anglais compris en entrée aussi (si l’utilisateur posait une question en anglais ou sur du contenu anglais, ce qui est peu probable ici).

En synthèse, chaque tâche du pipeline utilise le meilleur de l’IA spécialisé : Vision en anglais avec LLaVA (exploité via Ollama localement), résumé/traduction éventuellement via des services ou LLM dédiés, et un LLM final aligné sur le français. Cette combinaison garantit performance et cohérence linguistique.

## Langue de Traitement : Anglais vs Français  
Il est crucial de **distinguer les contenus à traiter en anglais ou en français** afin d’utiliser chaque modèle de façon optimale tout en maintenant une base de connaissance homogène. Résumons comment gérer la langue à chaque étape du flux :

- **Extraction du texte brut du PDF** : Ici, pas de question de langue, on extrait tel quel. Le texte réglementaire est en français et le reste en l’état.

- **Traitement des schémas par le LLM Vision** : Comme expliqué, ces modèles sont plus fiables en anglais. Il est donc recommandé de **convertir en amont tout ce qui peut les aider en anglais**. Cela inclut : la légende du schéma (si elle est en français, la traduire en anglais), ainsi que les éventuels textes OCRisés de l’image en français. En pratique, on peut construire une *prompt* pour le LLM vision du style : *“Here is an image. Legend: <légende traduite>. Please describe the image in detail.”*. Le LLM vision (LLaVA par ex.) va comprendre la légende car elle est en anglais, et produire une description cohérente en anglais. Cette astuce évite qu’il “bute” sur des mots français non vus pendant son entraînement (même si LLaVA 1.6 a fait des progrès en lecture de texte multilingue ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=model%20to%20grasp%20more%20details,the%20LLaMA%202%20Community%20License))).

- **Indexation et stockage dans la base Ragflow** : **Français privilégié**. Toutes les données textuelles que l’on va indexer pour la recherche sémantique devraient idéalement être en français, puisque les questions utilisateurs le seront. Cela concerne le texte réglementaire lui-même (déjà en français), **enrichi éventuellement des descriptions de schémas que l’on ajoutera en français**. Autrement dit, après avoir obtenu la description d’un schéma en anglais (via l’LLM vision), on la **traduit en français** pour l’intégrer au chunk correspondant. De même, si on ajoute des métadonnées ou titres, on peut les laisser en français pour cohérence. L’objectif est que l’espace sémantique de la base soit uniforme : un vecteur de requête en français devra être comparé à des vecteurs de documents en français. (📌 *Remarque*: si pour une raison quelconque on indexait des morceaux en anglais, il faudrait alors soit traduire la requête de l’utilisateur en anglais avant recherche, soit utiliser un embedder cross-langue – solutions moins simples que de tout garder en français).

- **Enrichissement et métadonnées** : Les opérations d’enrichissement (ajout de résumés, de mots-clés, de catégories) doivent aussi suivre la logique de langue. Par exemple, si on tagge un chunk avec des mots-clés thématiques, autant les mettre en français pour pouvoir filtrer ou booster avec des termes que l’utilisateur pourrait employer. Si on utilise un LLM pour générer un résumé de section, autant qu’il le fasse en français directement (on peut configurer un LLM open-source en français pour cela, ou GPT-3.5 avec `“Please summarize this French text in French:”`). En revanche, pour la partie *vision*, l’enrichissement se fait en anglais puis est converti.

- **Phase de questions/réponses utilisateur** : L’utilisateur pose ses questions en français (étant donné le contexte). Le système de retrieval de Ragflow cherchera donc les chunks pertinents en français. Les réponses générées par l’assistant final seront en français. Si par extraordinaire un utilisateur posait une question en anglais, on pourrait détecter la langue et la traduire vers le français pour la recherche (puis traduire la réponse finale en anglais si on voulait répondre dans la langue de la question). Mais dans notre usage, on peut supposer le français uniquement.

En somme, la règle est : **anglais uniquement là où les modèles l’exigent** (principalement pour la vision et éventuellement pour le rerank si on utilisait un modèle non francophone), **français partout ailleurs**. Cette séparation garantit d’une part des performances optimales (chaque modèle opère dans la langue où il est le plus compétent), et d’autre part une cohérence de la connaissance (tous les contenus indexés et fournis à l’utilisateur final sont en français). 

Notons que cette stratégie est confirmée par l’expérience : par exemple, *Pamela Fox* (Microsoft) a souligné ce biais des modèles multimodaux à répondre en anglais et l’importance de le gérer en amont dans un pipeline RA ([](https://aclanthology.org/2024.findings-emnlp.783.pdf#:~:text=We%20uncover%20a%20surprising%20multilingual,a%20mechanistic%20analysis%20of%20the)) ([](https://aclanthology.org/2024.findings-emnlp.783.pdf#:~:text=to%20the%20query%20to%20a,First%20we))】. Il est plus simple de **post-traduire** une bonne description anglaise que de forcer un modèle vision à tout sortir directement en français (ce qui souvent aboutirait à un texte de moindre qualité ou à un mélange de langues). 

## Tutoriel Étape par Étape du Pipeline Ragflow  
Passons à la pratique : comment mettre en place ce pipeline de traitement de PDF avec schémas, depuis la préparation du document jusqu’à l’ingestion dans Ragflow. Nous détaillerons chaque étape avec des exemples de code Python et des outils concrets.

### 1. Préparation du PDF et extraction initiale  
**But** : Obtenir le texte et les images brutes du PDF. Pour cela, on utilise des bibliothèques Python de parsing PDF.

- **Installation des outils requis** : Assurez-vous d’avoir installé `PyMuPDF` (alias `fitz`), `pdfplumber`, `pytesseract` (et le binaire Tesseract + le data fr), ainsi que éventuellement `pdf2image` si vous comptez convertir les pages en images. Par exemple :  
```bash
pip install pymupdf pdfplumber pytesseract pdf2image Pillow
sudo apt-get install tesseract-ocr-fra   # installation du pack langue française
````

- **Extraction du texte avec PyMuPDF ou pdfplumber** : Vous pouvez opter pour l’une ou l’autre. PyMuPDF a l’avantage d’offrir à la fois texte et images. Pdfplumber excelle pour le texte (et tables) mais n’extrait pas les images incorporées facilemen ([How to extract image · jsvine pdfplumber · Discussion #496 - GitHub](https://github.com/jsvine/pdfplumber/discussions/496#:~:text=GitHub%20github,with%20the%20pdfminer%20object))】. Nous utiliserons ici PyMuPDF.
    

Exemple de code pour parcourir les pages et extraire contenu textuel et images :

```python
import fitz  # PyMuPDF
doc = fitz.open("mon_document.pdf")
for page in doc:
    # Extraire texte
    texte = page.get_text("text")  
    # Extraire images
    blocks = page.get_text("dict")["blocks"]
    images = [b for b in blocks if b["type"] == 1]  # type 1 => imag ([Images - PyMuPDF 1.25.4 documentation](https://pymupdf.readthedocs.io/en/latest/recipes-images.html#:~:text=%3E%3E%3E%20d%20%3D%20page.get_text%28,3%2C%20%27ext%27%3A%20%27jpeg%27%2C%20%27height%27%3A%20501))】
    for img in images:
        xref = img.get("xref")  # identifiant d'objet de l'image
        pix = fitz.Pixmap(doc, xref)  # extraire l'image
        pix.save(f"page{page.number+1}_img{xref}.png")
        # stocker aussi la position si besoin:
        bbox = img["bbox"]
        # ... éventuellement enregistrer texte OCR plus tard
```

Ici, `page.get_text("dict")` fournit un dictionnaire où chaque _block_ a un champ `"type"` (0 pour texte, 1 pour image ([Images - PyMuPDF 1.25.4 documentation](https://pymupdf.readthedocs.io/en/latest/recipes-images.html#:~:text=%3E%3E%3E%20d%20%3D%20page.get_text%28,8%2C%20%27colorspace%27%3A%203%2C%20%27ext%27%3A%20%27jpeg))】. On filtre les images et on les sauvegarde sur le disque avec `Pixmap`. On conserve aussi `bbox` (x,y de coin haut gauche et bas droite) au cas où on souhaite associer l’image à une portion de texte par la position. Le texte de la page est récupéré simplement en format brut.

- **Extraction des légendes et repérage des figures** : Dans le texte extrait, on peut chercher des motifs de légende. Souvent, les PDF scientifiques/réglementaires ont des légendes qui commencent par “Figure” ou “Fig.” suivi d’un numéro. Par exemple:
    

```python
import re
legendes = re.findall(r'Figure\s*\d+[^.:]*?:\s*([^\.]+)\.', texte)
```

Cela peut capturer des phrases descriptives. Une autre approche consiste à repérer que la légende est souvent un block de texte court situé juste en dessous d’une image. Si on a les `bbox`, on peut imaginer que la légende est le block texte dont y est juste plus grand que l’y max de l’image. Toutefois, cela devient complexe à coder entièrement. On peut se contenter de l’approche regex manuelle et vérifier les résultats.

- **OCR sur les images** : Pour chaque image extraite, on applique Tesseract afin de lire d’éventuels textes. On peut utiliser pytesseract ainsi:
    

```python
from PIL import Image
import pytesseract
pytesseract.pytesseract.tesseract_cmd = r'/usr/bin/tesseract'  # chemin vers binaire si nécessaire
for img_path in extracted_images_paths:
    text_in_img = pytesseract.image_to_string(Image.open(img_path), lang="fra")
    print(f"Texte détecté dans {img_path}: {text_in_img}")
```

Veillez à bien préciser `lang="fra"` (ou `"fra+eng"` si le schéma contient un mélange de français et d’anglais). L’OCR permettra par exemple de récupérer des mots clés du schéma (titres de boîtes, etc.). Ces termes pourront être ajoutés à la description du schéma pour améliorer la recherche (ils agiront comme des _keywords_ très pertinents).

À ce stade, on dispose potentiellement pour chaque page du PDF :

- du **texte principal** en français (`texte`),
    
- d’une ou plusieurs **images de schémas** (fichiers PNG extraits),
    
- du **texte OCR** de chaque image (`text_in_img`),
    
- des **légendes** identifiées (`legendes`).
    

Il faut maintenant associer ces éléments entre eux. Par exemple, si page 5 contient une image et qu’on a trouvé “Figure 3: Schéma du processus X.” dans le texte, on peut lier l’image extraite de la page 5 à cette légende. On peut créer une structure Python pour représenter un _schéma_ avec : son image, son texte OCR, sa légende.

### 2. Traduction des légendes et textes utiles en anglais

Avant d’appeler le modèle visuel, on prépare son _input_. Comme discuté, on veut fournir une **description de contexte en anglais**.

Pour chaque schéma identifié, construisons un _prompt_ qui inclura (si disponibles) : la légende traduite et le texte OCR traduit. Par exemple :

```python
import deepl
translator = deepl.Translator("YOUR_DEEPL_API_KEY")  # utiliser votre clé API DeepL

for schema in schemas:  # supposons une liste d'objets schéma avec properties
    legende_fr = schema.legende  # ex: "Schéma du processus X..."
    ocr_fr = schema.ocr_text    # texte détecté dans l’image
    content_to_translate = legende_fr
    if ocr_fr:
        content_to_translate += " " + ocr_fr
    result = translator.translate_text(content_to_translate, target_lang="EN-US")
    schema.context_en = result.text
    print(f"Légende FR: {legende_fr}\nLégende+OCR EN: {schema.context_en}")
```

Ici on utilise l’API DeepL via son SDK Pytho ([Translate text | DeepL API Documentation](https://developers.deepl.com/docs/api-reference/translate#:~:text=curl%20,DE))】. On envoie la légende (et éventuellement le texte OCR concaténé) et on récupère `schema.context_en`, une phrase ou deux en anglais. _Note_: On peut aussi traduire séparément la légende et le texte OCR, et les garder distincts dans le prompt (par ex: _Legend: ..._ / _Labels: ..._).

Si on n’a pas accès à DeepL, on pourrait utiliser un modèle local de traduction. Par exemple, avec HuggingFace Transformers :

```python
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
tokenizer = AutoTokenizer.from_pretrained("Helsinki-NLP/opus-mt-fr-en")
model = AutoModelForSeq2SeqLM.from_pretrained("Helsinki-NLP/opus-mt-fr-en")
inputs = tokenizer(legende_fr, return_tensors="pt")
outputs = model.generate(**inputs)
english = tokenizer.decode(outputs[0], skip_special_tokens=True)
```

Ce modèle _opus-mt_ est basique mais peut faire l’affaire pour de courtes légendes.

Quoiqu’il en soit, on obtient en sortie une chaîne anglaise du style : _“Diagram of the X process illustrating step A, B, C…”_. Cela servira de complément contextuel pour le LLM vision.

### 3. Appel d’un LLM Vision pour générer une description du schéma

On peut maintenant interroger un modèle vision et lui fournir : l’image du schéma + le contexte en anglais produit à l’étape 2.

**Choix du déploiement** : Supposons que nous utilisions _Ollama_ pour déployer localement LLaVA 1.5/1.6. Ollama permet de charger des modèles multimodaux (par ex. `ollama pull llava:13b`) et expose une API locale. On a deux moyens d’appeler le modèle : via l’API HTTP ou via le SDK Python d’Ollama.

- **Via l’API HTTP** : Faire un POST sur `http://localhost:11434/api/generate` avec un JSON contenant `"model": "llava"` et `"prompt": "Describe this image: <chemin>"`. D’après la doc, on peut passer le chemin d’une image dans le prompt CL ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=To%20use%20a%20vision%20model,files%20using%20file%20paths))】, mais en JSON on envoie plutôt l’image encodée en base64 dans un champ `"images" ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=console)) ([ollama/docs/api.md at main · ollama/ollama · GitHub](https://github.com/jmorganca/ollama/blob/main/docs/api.md#:~:text=))】.
    
- **Via le SDK Python** (plus simple ici) : On utilise la librairie `ollama` pour Python. Par exemple :
    

```python
import ollama

image_path = "./page5_img17.png"
prompt = f"Here is an image.\n{schema.context_en}\nDescribe this image in detail."
response = ollama.chat(model="llava", messages=[
    {"role": "user", "content": prompt, "images": [image_path]}
])
description_en = response['message']['content']
print("Description générée (EN):", description_en)
```

Ce snippet utilise `ollama.chat` en spécifiant le modèle LLaVA et en fournissant la requête utilisateur contenant du texte et l’imag ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=import%20ollama)) ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=))】. Concrètement, la librairie va lire le fichier image, l’encoder en base64 et l’envoyer à l’API Ollama en arrière-pla ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=console))】. Le modèle retourne une réponse textuelle que l’on récupère.

Veillez à ce que _schema.context_en_ (la légende traduite) soit bien incluse pour guider le modèle. On pourrait formater le prompt différemment selon les préférences du modèle (certains modèles attendent un token `<image>` dans le texte, mais d’après la doc Ollama ce n’est pas nécessaire car on passe l’image dans un param séparé).

**Exemple de résultat** : Le modèle LLaVA pourrait répondre (en anglais) quelque chose comme : _“The diagram shows a three-step process. Step 1 (Initialization) leads to Step 2 (Processing), followed by Step 3 (Verification). Each step is represented by a box with a label... The arrows indicate the flow from one step to the next.”_. Ce texte correspond à la description détaillée du schéma. L’avantage est qu’il formule l’information visuelle de manière fluide et exhaustive (incluant probablement le contenu OCRisé qu’il aura détecté aussi de son côté, confirmant nos données).

Si on constate que la réponse est partielle ou confuse, on peut affiner le prompt (par ex. poser une question spécifique: _“What does the diagram illustrate? Describe all labeled components.”_). Avec un bon modèle vision, généralement un prompt générique _“Describe the image”_ suffit.

Répétez cette étape pour chaque schéma identifié. On associera chaque _description_en_ à l’objet _schema_ correspondant.

### 4. Réinjection de la description dans le chunk original (en français)

Nous avons maintenant pour chaque schéma une description en anglais. Il faut la convertir en français et l’intégrer au contenu indexé.

- **Traduire la description en français** : On peut réutiliser l’API DeepL pour cela, ou un LLM. C’est l’opération inverse de l’étape 2. Par exemple :
    

```python
description_fr = translator.translate_text(description_en, target_lang="FR").text
schema.description_fr = description_fr
```

Après traduction, on obtient par ex. : _“Le schéma montre un processus en trois étapes. L’étape 1 (Initialisation) conduit à l’étape 2 (Traitement), suivie de l’étape 3 (Vérification)...”_. On vérifie rapidement que les termes techniques sont bien traduits (DeepL est généralement fiable sur ce point).

- **Construire le chunk fusionné** : L’idée est d’intégrer cette _description_fr_ au côté du texte original du PDF de manière cohérente. Deux approches possibles :
    
    1. **Insérer la description dans le texte à l’emplacement du schéma** – Par exemple, remplacer la légende d’origine par une version augmentée : _“Figure 3 : Schéma du processus X (Description générée : Ce schéma montre que… )”_. Cette méthode garde tout dans un même bloc, pratique pour que le LLM final ait tout d’un coup.
        
    2. **Créer un chunk séparé dédié à la figure** – Par exemple, un chunk qui contient uniquement _“[Figure 3] Schéma du processus X – Ce schéma illustre…”_, distinct du chunk de texte principal. On relie ce chunk via métadonnées (même page, même figure number) à la partie texte. Cette méthode permet de cibler spécifiquement la figure lors de la recherche.
        

La seconde est souvent plus propre, car elle isole bien la description d’image. Ragflow, lors du parsing, segmente souvent par type de contenu (il a des _chunk templates_ spécifiques _“picture”_ pour images, _“table”_ pour tableaux, etc. ([HTTP API | RAGFlow](https://ragflow.io/docs/dev/http_api_reference#:~:text=match%20at%20L308%20,object%20contains%20the%20following%20attributes))】). **Nous pouvons imiter ce fonctionnement** : créer manuellement un chunk de type “picture” pour chaque schéma.

Concrètement, on peut construire un petit JSON ou Markdown pour chaque chunk. Par exemple, supposons qu’on décide de faire un format JSON avec champs: _id_, _page_, _type_, _content_, _metadata_. Un chunk image pourrait ressembler à :

```json
{
  "id": "fig3",
  "page": 5,
  "type": "figure",
  "content": "Figure 3 : Schéma du processus X.\nCe schéma illustre un processus en trois étapes : ... (notre description_fr)...",
  "metadata": {
      "source": "MonDocument.pdf",
      "figure_id": 3,
      "titre": "Processus X"
   }
}
```

Le _content_ ici comprend la légende originale suivie de la description détaillée en français. On peut la formater avec un saut de ligne ou une phrase distincte introduite par _“Description : …”_ pour bien la démarquer.

Le chunk texte environnant, lui, reste inchangé sauf qu’on pourrait éventuellement supprimer la légende pour éviter la redondance. Ou le laisser, ce n’est pas bien grave d’avoir la légende en double. L’important est d’avoir la description dans au moins un chunk.

- **Ajout des métadonnées adéquates** : Utilisez les métadonnées pour conserver la provenance et faciliter la requête. Les métadonnées utiles pourraient être :
    
    - _page_ (numéro de page du PDF) – Ragflow le fait déjà pour les chunks extraits, donc indiquez la page du schéma.
        
    - _figure_id_ ou un identifiant interne – utile si on veut plus tard filtrer ou manipuler spécifiquement les figures.
        
    - _source_ (le nom du document, ou son ID) – permet à l’assistant de formuler la citation.
        
    - éventuellement _type=figure_ – dans Ragflow il n’y a pas forcément un champ type explicite dans l’index, mais on peut l’ajouter en mot-clé dans le contenu ou en tag si l’API le permet. Au minimum, inclure le mot “Figure” dans le content garantit qu’une requête cherchant “figure” le remontera.
        

À ce stade, nous avons enrichi le contenu. Par exemple, là où initialement on n’avait que le texte réglementaire brut, on a maintenant en plus, pour chaque figure, un paragraphe explicatif en français qui sera **indexé**.

### 5. Intégration des chunks dans Ragflow (interface ou API)

La dernière étape consiste à **charger ces données enrichies dans Ragflow** pour qu’elles soient utilisées lors des questions/réponses. Il y a deux façons principales : via l’interface web (Knowledge Base) de Ragflow, ou via les APIs (HTTP/Python).

**Option A – Interface graphique** :

1. Démarrez le serveur Ragflow (Docker ou local) et accédez à l’UI. Créez une **Knowledge Base** dédiée (par exemple nommée “Docs Réglementaires”). Configurez-la avec un _chunking method_ approprié, par exemple _“Laws”_ si disponible (template optimisé pour documents juridiques ([Configure knowledge base | RAGFlow](https://ragflow.io/docs/dev/configure_knowledge_base#:~:text=Paper%20PDF%20Book%20DOCX%2C%20PDF%2C,XLSX%2C%20CSV%2FTXT))】 ou _“Manual”_ si on veut contrôler nous-même. Pour éviter que Ragflow ne segmente tout automatiquement, vous pouvez choisir _Manual_ pour le PDF en questio ([Configure knowledge base | RAGFlow](https://ragflow.io/docs/dev/configure_knowledge_base#:~:text=Resume%20Enterprise%20edition%20only,JPEG%2C%20JPG%2C%20PNG%2C%20TIF%2C%20GIF))】 – cela signifie qu’il ne coupera pas le fichier en chunks tout seul.
    
2. **Upload du PDF** via _Add file_. Une fois uploadé, cliquez sur _Parse_ (le bouton _Play_). Comme on est en Manual, il va peut-être créer un chunk par page ou tout mettre en un seul chunk (d’après la doc _Manual PDF_ est un template qui semble garder la structure page par page ([Configure knowledge base | RAGFlow](https://ragflow.io/docs/dev/configure_knowledge_base#:~:text=Resume%20Enterprise%20edition%20only,JPEG%2C%20JPG%2C%20PNG%2C%20TIF%2C%20GIF))】.
    
3. Allez dans la vue _Chunks_ du document (en cliquant sur le fichier puis _View chunks_ ([Get started | RAGFlow](https://ragflow.io/docs/dev/#:~:text=match%20at%20L347%201,to%20view%20the%20chunking%20results))】. Là vous verrez la liste des chunks extraits. C’est ici qu’on peut **intervenir** (d’où _Intervene with file parsing_) pour éditer le conten ([Get started | RAGFlow](https://ragflow.io/docs/dev/#:~:text=1,to%20view%20the%20chunking%20results))】. Vous pouvez soit modifier un chunk existant, soit en ajouter un nouveau. Par exemple, créez un nouveau chunk pour la figure 3, copiez-collez le contenu _content_ que vous avez préparé (légende + description française). Assurez-vous de remplir les champs de métadonnées (il y a souvent un champ “title” ou similaire où on peut mettre “Figure 3”). Si Ragflow n’offre pas de champ spécifique, vous pouvez simplement inclure en début de chunk “Figure 3 : ...” comme on a fait.
    
4. Enregistrez les modifications. Vous verrez alors vos chunks mis à jour. Si on avait supprimé la légende du chunk principal, on peut coller notre description au bon endroit, etc. **En résumé, via l’UI, on utilise la fonction d’édition pour injecter nos descriptions de schémas.**
    

**Option B – API/Automatisation** :  
Si vous avez de nombreux documents à traiter, il est préférable d’automatiser l’ingestion. Ragflow propose une API REST (et SDK Python). Quelques points clés :

- Vous pouvez créer une Knowledge Base via l’API ou trouver son `dataset_id` depuis l’UI.
    
- Ensuite, utilisez l’endpoint **Upload documents** (`POST /api/v1/datasets/{dataset_id}/documents`) pour envoyer le fichier PD ([HTTP API | RAGFlow](https://ragflow.io/docs/dev/http_api_reference#:~:text=match%20at%20L610%20The%20ID,A%20document%20to%20upload))】. Cela créera une entrée de document.
    
- Lancez le parsing via `POST /api/v1/documents/{doc_id}/parse` (vérifiez la doc exacte, il y a possiblement un appel ou un flag).
    
- Une fois parsé, vous pouvez récupérer la liste des chunks avec `GET /api/v1/documents/{doc_id}/chunks` ou similaire.
    
- Pour **mettre à jour un chunk**, l’API propose peut-être un endpoint (les erreurs 1001/1002 font référence à *Invalid Chunk ID / Chunk Update Failed ([Python API | RAGFlow](https://ragflow.io/docs/dev/python_api_reference#:~:text=Code%20Message%20Description%20400%20Bad,Update%20Failed%20Chunk%20update%20failed))】, ce qui suggère qu’on peut appeler une méthode PUT/PATCH sur un chunk spécifique). Vérifiez la documentation _Python SDK_ : par exemple, `ragflow_sdk.Document.update_chunk(chunk_id, content="new content", metadata={...})`. Si l’API directe n’est pas évidente, une alternative est de **supprimer tous les chunks** de ce document (il y a un appel DELETE dataset maybe) puis de **réinsérer nos propres chunks**. On peut en effet créer un document de type JSON et l’uploader comme si c’était un texte. Une astuce serait de **convertir notre PDF en un fichier Markdown** contenant toutes les parties enrichies, puis de le faire ingérer par Ragflow comme un document texte normal.
    

Pour illustrer, imaginons qu’on veuille tout faire par code avec le SDK Python (après `pip install ragflow-sdk`). On pourrait avoir :

```python
from ragflow_sdk import Client
client = Client(api_key="YOUR_API_KEY")
kb = client.create_knowledge_base("DocsRegl", embedding_model="sentence-transformers/all-MiniLM-L6-v2")
ds = kb.create_dataset("Doc1", chunk_method="manual")  # create dataset with manual chunking
doc = ds.upload_file("mon_document.pdf")
doc.parse()  # trigger parsing
chunks = doc.get_chunks()
# Find the chunk corresponding to figure 3's original legend, or decide where to insert
doc.add_chunk(content=new_content, metadata={"page":5, "figure":3})
doc.delete_chunk(chunk_id=...)  # if needed to remove duplicates
```

(Les noms de méthodes sont imaginés, l’idée est qu’on pilote la KB via le SDK.)

Pour les besoins de documentation, on ne détaillera pas toute l’API, mais sachez que Ragflow est pensé pour ce genre d’intervention programmatique.

**Validation** : Après ingestion, que ce soit via UI ou API, testez dans la section _Retrieval testing_. Tapez quelques mots clés liés au schéma pour voir si la description ressort. Par exemple, si le schéma concerne “processus X”, cherchez _“processus X étapes”_. Vous devriez voir dans les résultats le chunk de la figure avec la descriptio ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=You%20can%20test%20retrieval%20by,to%20the%20%E2%80%9CRetrieval%20testing%E2%80%9D%20section))】. Si ce n’est pas le cas, vérifiez que l’embedding a bien été mis à jour (peut-être faut-il re-parsing après modifs manuelles, ou cliquer _Save_ dans l’UI).

### 6. Exemples Concrets d’Utilisation

Maintenant que tout est en place, interrogez l’assistant Ragflow en conditions réelles. Par exemple : _“Quelles sont les différentes étapes du processus X illustré dans le document ?”_. Le système devrait :

- retrouver le chunk texte où sont listées les étapes (dans le réglementaire, ou possiblement dans notre description de figure),
    
- retrouver le chunk figure qui décrit ces étapes visuellement (car notre description FR contient ces mots clés),
    
- passer le tout au modèle de chat (par ex. Llama-2 ou autre) configuré en assistant.
    

L’assistant combinera ces sources. Grâce à l’option _Show Quote_, il pourra inclure dans sa réponse des extraits du chunk figure ou du texte origina ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=5,base%20that%20I%20created%20earlier))】. On pourrait obtenir une réponse du genre : _“Le document décrit un processus en trois étapes (voir Figure 3). **Étape 1** : Initialisation… **Étape 2** : Traitement… **Étape 3** : Vérification ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=,Michel%20Basquiat))】. Ce schéma illustre le flux allant de l’étape 1 à 3 par des flèches.”_. On remarque que la réponse inclut potentiellement la référence _Figure 3_ et reprend le contenu de notre chunk enrichi (ici mention des flèches par ex provenant de la description de l’image).

Pour s’assurer de la qualité :

- **Tester plusieurs questions** sur les schémas, mais aussi des questions purement textuelles, pour vérifier que l’ajout des schémas n’a pas perturbé l’indexation. Normalement non, chaque chunk a son vecteur.
    
- Si certaines réponses sont hors-sujet, ajuster le _Similarity Threshold_ ou _Top N_ dans la config de l’assistan ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=Similarity%20Threshold%20)) ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=Top%20N%20))】, ou entraîner le reranker. Un bon réglage est un _similarity_threshold_ assez bas (0.2 par défaut) pour ne pas louper de chunk pertinent, et un _top_n_ modéré (5-6) pour ne pas surcharger le context ([HTTP API | RAGFlow](https://ragflow.io/docs/dev/http_api_reference#:~:text=,)) ([HTTP API | RAGFlow](https://ragflow.io/docs/dev/http_api_reference#:~:text=match%20at%20L1715%20or%20reranking,number%20of%20top%20chunks%20with))】.
    

## Conseils de Structuration de la Base de Connaissances et de l’Architecture

Pour clôturer, voici quelques recommandations globales afin de tirer le maximum de ce pipeline RAGFlow multimodal :

- **Utilisez des chunk templates adaptés** : Ragflow offre plusieurs modèles de chunking (général, Q&A, laws, presentation, picture, etc. ([Configure knowledge base | RAGFlow](https://ragflow.io/docs/dev/configure_knowledge_base#:~:text=Template%20Description%20File%20format%20General,DOCX%2C%20PDF%2C%20TXT%20Manual%20PDF))】. Dans le cas de documents réglementaires, le template _Laws_ peut améliorer la segmentation (il est conçu pour repérer articles, sections, etc. afin d’éviter de couper en plein milieu d’un article de loi). Vous pouvez aussi utiliser _Manual_ pour tout contrôler, mais cela nécessite alors de fournir soi-même la logique d’extraction (ce que nous avons fait manuellement). Une approche hybride : laissez Ragflow parser en _Laws_, puis **intervenez** pour insérer les descriptions de figures là où il faut.
    
- **Granularité des chunks** : Trouvez le bon équilibre de taille. En général, vise ~100-200 tokens par chun ([HTTP API | RAGFlow](https://ragflow.io/docs/dev/http_api_reference#:~:text=match%20at%20L295%20%2A%20%60,false))】. Pour le texte réglementaire, on peut chunker par paragraphes ou sections numérotées. Pour les figures, comme on l’a fait, un chunk par figure. Évitez de coller un énorme bloc de plusieurs pages – moins précis au retrieva ([Breaking up is hard to do: Chunking in RAG applications - Stack Overflow](https://stackoverflow.blog/2024/12/27/breaking-up-is-hard-to-do-chunking-in-rag-applications/#:~:text=The%20size%20of%20the%20chunked,the%20context%20of%20the%20data)) ([Breaking up is hard to do: Chunking in RAG applications - Stack Overflow](https://stackoverflow.blog/2024/12/27/breaking-up-is-hard-to-do-chunking-in-rag-applications/#:~:text=%E2%80%9CThe%20reason%20to%20start%20thinking,%E2%80%9D))】. A contrario, ne coupez pas trop fin (une phrase isolée peut perdre le contexte). Notre approche de lier légende + description image dans un chunk est idéale : c’est concis et autonome sémantiquement.
    
- **Mise en place des métadonnées** : Profitez des métadonnées pour améliorer la recherche et le filtrage. Par exemple, dans Ragflow vous pourriez tagger tous les chunks de figures avec un mot-clé “figure”. Ainsi, si l’assistant détecte que l’utilisateur pose une question de type “Montrez-moi un schéma…”, vous pourriez (via un mécanisme plus avancé) filtrer pour ne récupérer que les chunks taggés figure. Ragflow ne supporte pas encore les requêtes booléennes complexes en interface, mais on peut imaginer le faire au niveau de l’orchestration (ex : un _tool_ qui d’abord identifie si la question concerne une image). Pour l’instant, un bon usage des métadonnées sert surtout à **la traçabilité** (source, page) afin que l’assistant inclue une référence précise dans sa réponse.
    
- **Assistants multiples ou spécialisés** : Si votre cas d’utilisation le justifie, vous pourriez configurer plusieurs assistants dans Ragflow reliés à la même base. Par exemple, un assistant “Explicateur de schémas” et un assistant “Analyste réglementaire”. Le premier pourrait avoir un prompt système orienté pédagogie visuelle (_“Tu es un expert qui explique des schémas techniques simplement”_), le second un prompt plus juridique. Tous deux utiliseraient la même KB, mais formuleraient les réponses différemment. Ragflow facilite la création de multiples assistants avec des personnalités distinctes, tout en pointant vers la ou les mêmes bases de connaissance ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=Creating%20Assistants)) ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=The%20%E2%80%9CPrompt%20Engine%E2%80%9D%20tab%20of,prompts%20and%20configure%20other%20parameters))】.
    
- **Workflow de traitement automatisé** : Intégrer l’extraction de schémas dans votre pipeline d’ingestion de documents. Si vous avez un grand nombre de PDF, scriptez tout ce qu’on a décrit (comme dans le tutoriel) pour que chaque nouveau document ajouté passe par : extraction PyMuPDF -> OCR -> LLM vision -> enrichissement -> upload Ragflow. Cela peut être orchestré avec des tâches offline. Ragflow est pensé pour la **mise à jour continue** des connaissances, donc on peut imaginer ré-exécuter le parsing si un document a été enrichi, etc. Utilisez l’API REST de Ragflow dans vos scripts d’ingestion pour éviter le travail manuel.
    
- **Qualité des réponses** : Pour maximiser la qualité, veillez à la **pertinence des données fournies au LLM final**. Nos descriptions de schémas enrichissent la base, mais il faut que l’assistant sache les utiliser. Vous pouvez le guider via le prompt système. Par exemple : _“Lorsque des schémas ou figures sont pertinents, intègre leur description dans la réponse.”_. De même, incitez-le à citer la source (_“Cite systématiquement le document et la figure lorsque tu t’appuies sur eux.”_). Ainsi, l’assistant combinera texte et image de façon transparente dans ses explications.
    
- **Tests et ajustements** : Enfin, testez le pipeline avec des questions variées, notamment :
    
    - Des questions purement textuelles (pour vérifier que le texte réglementaire seul est bien géré, et que l’ajout des images n’a pas introduit de bruit).
        
    - Des questions directement sur un schéma (_“Que montre la figure X ?”_ ou _“Expliquez le schéma du processus X”_).
        
    - Des questions croisées (_“Selon le document, quelle étape suit l’étape 2 du processus X et qu’indique le schéma à ce propos ?”_). Ce type de question oblige à utiliser les deux sources (texte et schéma). Idéalement, le LLM va puiser l’ordre des étapes dans le texte réglementaire, et les détails visuels dans la description du schéma.
        

En évaluant ces réponses, vous pourrez itérer : par ex., si vous constatez que le LLM ignore la description du schéma, peut-être faut-il ajouter un peu plus de poids (ex. répéter certains termes dans la description pour qu’elle ressorte plus dans la similarité, ou abaisser _similarity_threshold_). Si au contraire il cite trop l’image même quand ce n’est pas demandé, assurez-vous que _Show Quote_ n’oblige pas à tout citer, etc.

En suivant ces étapes et conseils, vous disposerez d’une **base de connaissances Ragflow multimodale** bien structurée, où les schémas annotés de vos PDF sont traités efficacement. Cela permettra à l’assistant IA de fournir des réponses précises, complètes et appuyées à la fois par le texte réglementaire et par l’interprétation des éléments graphiques, offrant ainsi à l’utilisateur le meilleur des deux mondes dans ses réponses.

**Sources :** Extraction PDF (PyMuPDF ([Images - PyMuPDF 1.25.4 documentation](https://pymupdf.readthedocs.io/en/latest/recipes-images.html#:~:text=2,images%20shown%20on%20a%20page)) ([Images - PyMuPDF 1.25.4 documentation](https://pymupdf.readthedocs.io/en/latest/recipes-images.html#:~:text=,0.0%2C%20228.2460174560547%2C%20100.0))】, Ragflow DeepDoc (mise en page, figures ([ragflow/deepdoc/README.md at main · infiniflow/ragflow · GitHub](https://github.com/infiniflow/ragflow/blob/main/deepdoc/README.md#:~:text=Recognition,components%20which%20covers%20most%20cases)) ([ragflow/deepdoc/README.md at main · infiniflow/ragflow · GitHub](https://github.com/infiniflow/ragflow/blob/main/deepdoc/README.md#:~:text=,and%20text%20in%20the%20figures))】, Ollama (LLM vision LLaVA ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=To%20use%20a%20vision%20model,files%20using%20file%20paths)) ([Vision models · Ollama Blog](https://ollama.com/blog/vision-models#:~:text=import%20ollama))】, Biais langue LLM visio ([](https://aclanthology.org/2024.findings-emnlp.783.pdf#:~:text=We%20uncover%20a%20surprising%20multilingual,a%20mechanistic%20analysis%20of%20the))】, Config Ragflow & assistant ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=5,base%20that%20I%20created%20earlier)) ([RAGFlow an Open-Source Retrieval-Augmented Generation (RAG) Engine | by Omar Santos | Medium](https://becomingahacker.org/ragflow-an-open-source-retrieval-augmented-generation-rag-engine-6b903005a032#:~:text=The%20%E2%80%9CPrompt%20Engine%E2%80%9D%20tab%20of,prompts%20and%20configure%20other%20parameters))】, API Deep ([Translate text | DeepL API Documentation](https://developers.deepl.com/docs/api-reference/translate#:~:text=curl%20,DE))】.