Indexer les images dans un RAG : guide production
5 juin 2026 · 7 min de lecture · Guides
Freelance intégration IA · Spécialiste LLM, RAG · 11+ réalisations clients
La majorité des pipelines RAG ignorent les images. C'est une erreur pour la documentation technique : tableaux de données, diagrammes d'architecture, captures d'écran annotées, matrices de comparaison. Ces éléments contiennent des informations que le texte environnant ne répète pas.
Réponse directe : la bonne approche n'est pas de traiter les images avec un modèle de vision à chaque requête (trop cher, trop lent), mais de les décrire une seule fois en langage naturel au moment de l'indexation, et de stocker ces descriptions comme des chunks texte ordinaires. Le surcoût par requête passe de 27-51% à 1-6%.
Deux types d'images dans la documentation
Toutes les images n'ont pas la même valeur dans un RAG. Il faut distinguer :
Images illustratives : elles clarifient le texte adjacent (une capture montrant quel bouton cliquer), mais n'apportent pas d'information indépendante. Si vous les ignorez, l'utilisateur peut quand même obtenir une réponse correcte depuis le texte.
Images structurantes : elles contiennent des informations essentielles non redondantes avec le texte. Exemples : tableaux de données renderisés en image, diagrammes d'architecture, matrices de comparaison, captures d'écran annotées. Si vous les ignorez, vous perdez de l'information réelle.
Les tests de kapa.ai sur leur système de documentation technique montrent que les images structurantes ont été citées dans 10 à 64% des réponses selon le projet, avec une amélioration statistiquement significative de la qualité des réponses.
Pourquoi le traitement des images à la requête ne scale pas
L'approche naïve : passer les images brutes à un modèle de vision (GPT-4o, Claude) à chaque requête. Trois problèmes structurels :
Coût. Selon les mesures de kapa.ai :
- Passer des images brutes augmente les coûts GPT de 27% par requête
- Sur Claude, le surcoût monte à 51% par requête
C'est un coût récurrent sur chaque requête, pas ponctuel.
Capacité. Un retrieval typique remonte 20-30 chunks. Si ces chunks contiennent des images, on approche ou dépasse la fenêtre de contexte de la plupart des modèles. Vous devez choisir entre couverture des images et couverture du texte.
Qualité du retrieval. Les embeddings multimodaux CLIP "diluent exactement le niveau de détail qui compte dans les graphiques, tableaux et captures annotées". Ils sont bons pour trouver des images visuellement similaires, pas pour capturer le contenu textuel ou numérique à l'intérieur des diagrammes.
L'approche correcte : captioning à l'indexation
Générer une description en langage naturel pour chaque image une seule fois, au moment de l'indexation. Stocker cette description comme un chunk texte séparé. La récupérer comme n'importe quel texte.
from openai import OpenAI
import base64
from pathlib import Path
client = OpenAI()
def describe_image(image_path: str, surrounding_text: str, page_title: str) -> str:
"""
Génère une description d'image en incluant le contexte textuel environnant.
Le contexte améliore significativement la qualité de la description.
"""
with open(image_path, "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
suffix = Path(image_path).suffix.lstrip(".")
mime = f"image/{suffix}" if suffix != "jpg" else "image/jpeg"
response = client.chat.completions.create(
model="gpt-4o-mini", # modèle bon marché, suffisant pour le captioning
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": f"""Tu es un assistant de documentation technique.
Page : {page_title}
Texte environnant : {surrounding_text[:500]}
Décris cette image de façon précise et structurée.
Si c'est un tableau, retranscris les données en markdown.
Si c'est un diagramme, décris les composants et leurs relations.
Si c'est une capture d'écran, décris l'interface et les éléments pertinents."""
},
{
"type": "image_url",
"image_url": {"url": f"data:{mime};base64,{image_data}"}
}
]
}
],
max_tokens=500
)
return response.choices[0].message.contentPourquoi inclure le contexte ? Une capture d'un panneau de paramètres n'a pas de sens sans savoir quel produit ni ce que le paragraphe précédent explique. Le contexte produit des descriptions nettement plus précises.
Pourquoi GPT-4o mini ? Les modèles frontier ne sont pas nécessaires pour du captioning. GPT-4o mini produit des résultats presque équivalents à GPT-4o sur cette tâche, à une fraction du prix. Et comme c'est un coût d'indexation (ponctuel par image), le tarif importe moins que pour les requêtes.
Étape 1 : filtrer les images parasites
Les sites de documentation sont remplis d'images non informationnelles : logos, bannières décoratives, icônes, séparateurs. Les passer à un modèle de vision gaspille de l'argent et pollue l'index.
def is_informational_image(image_path: str) -> bool:
"""
Classifieur zero-shot pour distinguer les images informatives des décorations.
"""
with open(image_path, "rb") as f:
image_data = base64.b64encode(f.read()).decode("utf-8")
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": """Cette image contient-elle des informations techniques utiles ?
Réponds uniquement par OUI ou NON.
Réponds OUI si : tableau de données, diagramme, capture d'écran annotée, graphique, schéma d'architecture.
Réponds NON si : logo, icône, illustration décorative, bannière, photo générique."""
},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
}
]
}
],
max_tokens=5
)
return response.choices[0].message.content.strip().upper() == "OUI"Étape 2 : stocker les descriptions comme chunks indépendants
Les descriptions ne doivent pas être intégrées dans le chunk texte parent. Elles doivent être stockées comme chunks indépendants avec des métadonnées liant à la page source.
from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct
import uuid
def index_image_description(
description: str,
source_page: str,
image_path: str,
position_in_page: int,
embedder
) -> str:
"""
Indexe une description d'image comme chunk indépendant.
Retourne l'ID du chunk créé.
"""
chunk_id = str(uuid.uuid4())
embedding = embedder.embed(description)
client = QdrantClient(url="http://localhost:6333")
client.upsert(
collection_name="docs",
points=[
PointStruct(
id=chunk_id,
vector=embedding,
payload={
"content": description,
"content_type": "image_description",
"source_page": source_page,
"image_path": image_path,
"position": position_in_page,
}
)
]
)
return chunk_idStocker les descriptions comme chunks séparés permet au retriever de les remonter sur leur propre mérite, même si le texte environnant n'a pas été récupéré.
Pipeline complet d'indexation
def index_document_with_images(doc_path: str, embedder):
"""
Pipeline complet : parse document, filtre les images, génère les descriptions,
indexe texte et images séparément.
"""
doc = parse_document(doc_path) # retourne texte + images avec positions
# Indexation du texte standard
for chunk in chunk_text(doc.text):
embedding = embedder.embed(chunk.content)
index_text_chunk(embedding, chunk)
# Indexation des images
for image in doc.images:
# Étape 1 : filtrer les images décoratives
if not is_informational_image(image.path):
continue
# Étape 2 : générer la description avec contexte
surrounding = doc.get_text_around(image.position, chars=300)
description = describe_image(image.path, surrounding, doc.title)
# Étape 3 : indexer comme chunk indépendant
index_image_description(
description=description,
source_page=doc.url,
image_path=image.path,
position_in_page=image.position,
embedder=embedder
)Résultats mesurés
Sur trois projets de documentation technique différents, kapa.ai mesure :
| Métrique | Traitement à la requête | Captioning à l'indexation |
|---|---|---|
| Surcoût par requête (GPT) | +27% | +1-2% |
| Surcoût par requête (Claude) | +51% | +1-2% |
| Images citées dans les réponses | N/A | 10-64% |
| Amélioration qualité réponses | N/A | Significative |
| Latence ajoutée | +300-800 ms | 0 ms |
TL;DR
Pour intégrer les images dans un RAG de documentation technique : décrire à l'indexation, pas à la requête. Filtrer les images décoratives avant le captioning. Inclure le contexte textuel dans le prompt de description. Stocker les descriptions comme chunks indépendants.
GPT-4o mini est suffisant pour le captioning, et comme c'est un coût ponctuel par image (pas par requête), l'économie est structurelle.
Vous souhaitez ajouter le support des images à votre pipeline RAG ? Parlons de votre projet.
À propos de l'auteur
Pierre KasparianÉtudiant ingénieur en fin de cursus à l'UTT (Université de Technologie de Troyes) et freelance en intégration IA. Il déploie des LLM, pipelines RAG et agents IA pour des PME françaises et européennes, avec une attention sur le RGPD et hébergement européen. 11+ réalisations clients, dont Pretto et LiveSession.