Changer le thème

Guide de développement d'applications IA multimodales : de la sélection du modèle au déploiement

Easton editorial illustration: multi-tenant AI service platform

Vous utilisez peut-être GPT-4 ou Claude pour coder ou peaufiner un texte, mais dès que le besoin devient « analyser les données de cette capture d’écran » ou « comprendre la vidéo uploadée par l’utilisateur », un modèle purement textuel ne suffit plus. L’IA multimodale répond à ce problème : le modèle lit le texte et « voit » aussi les images et les vidéos.

Ces douze derniers mois, l’IA multimodale a progressé plus vite que prévu. GPT-4o, Claude Vision et Gemini 1.5 Pro se sont succédé, repoussant les limites. Pour les développeurs, la vraie question n’est pas « à quel point l’IA multimodale est puissante », mais « comment l’utiliser, quel modèle choisir et comment maîtriser les coûts ». Cet article aborde ces points de manière pratique.


I. Concepts clés de l’IA multimodale

1.1 Qu’est-ce que l’IA multimodale ?

En bref, l’IA multimodale traite plusieurs types de données en parallèle. Un modèle textuel classique ne consomme que du texte ; un modèle multimodal accepte texte, images, audio et vidéo, et produit le résultat attendu.

Exemple : vous uploadez une photo de produit et demandez « Où est l’étiquette de prix ? Combien coûte-t-il ? » Le modèle comprend l’image, localise l’étiquette, lit le montant et répond. Avec une architecture traditionnelle, il faudrait enchaîner détection d’objets, OCR et compréhension de texte — aujourd’hui, un seul appel multimodal suffit.

1.2 Évolution de l’architecture : du « Lego » à la fusion native

Les premières approches multimodales étaient souvent du « bricolage » : un encodeur visuel (CLIP, ViT) transformait l’image en vecteurs, puis alimentait un grand modèle de langage. GPT-4V suit cette logique, avec un adaptateur visuel greffé sur GPT-4.

Le problème : cette vision « ajoutée après coup » reste un peu déconnectée. Le modèle interprète l’image avec la logique du LLM, ce qui faiblit sur les tâches de raisonnement visuel profond.

Les modèles multimodaux natifs corrigent cela. GPT-4o et Gemini ont été conçus dès l’origine pour le multimodal : texte, image et audio sont traités de façon unifiée en bas niveau. Résultat visible : meilleures performances sur « comparer deux images », « tirer une conclusion d’un graphique », etc.

1.3 Tendances technologiques 2025-2026

2025 a été surnommé « l’année des agents » ; le multimodal est passé de « bonus » à « standard ». Quelques tendances marquantes :

Contexte long. Gemini 1.5 Pro supporte plus d’1M tokens de contexte et peut traiter plus d’une heure de vidéo d’un coup. Avant, il fallait analyser image par image et résumer par segments ; aujourd’hui, le modèle « regarde tout » puis répond.

Coûts en baisse. Les modèles open source rattrapent vite : Qwen2-VL, GLM-4V et d’autres approchent les modèles propriétaires sur certaines tâches. Pour les budgets serrés, le déploiement privé devient viable.

Agents multimodaux. Le modèle ne se contente plus de « décrire l’image » : il peut agir selon le contenu visuel. Exemple : « Sur cette capture, clique le bouton Connexion » — cela exige compréhension visuelle, appels d’outils et planification de tâche.


II. Comparaison et choix des modèles multimodaux

Ne vous fiez pas qu’aux benchmarks. En pratique, stabilité de l’API, coût, facilité d’intégration et conformité peuvent être décisifs.

2.1 OpenAI : GPT-4V et GPT-4o

GPT-4V a été la première offre multimodale d’OpenAI, via un adaptateur visuel sur GPT-4. GPT-4o est la version multimodale native, globalement plus performante.

Quand choisir GPT-4o ?

  • Raisonnement visuel (conclusions à partir d’images, comparaisons)
  • Dialogues multimodaux multi-tours (référence à une image plus tard dans la conversation)
  • Recherche de la meilleure précision

Quand choisir GPT-4V ?

  • Descriptions ou classifications d’images simples
  • Sensibilité à la latence (GPT-4V peut parfois répondre plus vite)
  • Compatibilité avec d’anciens systèmes

L’appel API est quasi identique pour les deux modèles :

from openai import OpenAI

client = OpenAI()

# Méthode 1 : URL d'image
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Que contient cette image ?"},
            {"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}
        ]
    }]
)

# Méthode 2 : encodage Base64
import base64

with open("image.png", "rb") as f:
    image_data = base64.b64encode(f.read()).decode("utf-8")

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Analysez cette image"},
            {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image_data}"}}
        ]
    }]
)

print(response.choices[0].message.content)

2.2 Anthropic : Claude Vision

Claude Vision excelle sur l’analyse documentaire et l’extraction de détails. Pour structurer des informations depuis PDF, graphiques ou captures, Claude est un bon choix.

Cas forts de Claude Vision :

  • Analyse de documents (PDF, scans, tableaux complexes)
  • Extraction de détails (souvent plus « minutieux » que d’autres modèles)
  • Longs documents (contexte 200K)

L’appel diffère légèrement : l’image est un bloc content distinct :

from anthropic import Anthropic
import base64

client = Anthropic()

# Lire l'image et la convertir en Base64
with open("document.png", "rb") as f:
    image_data = base64.b64encode(f.read()).decode("utf-8")

response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": [
            {
                "type": "image",
                "source": {
                    "type": "base64",
                    "media_type": "image/png",
                    "data": image_data
                }
            },
            {"type": "text", "text": "Extrayez toutes les données des tableaux du document, au format JSON"}
        ]
    }]
)

print(response.content[0].text)

2.3 Google : série Gemini

Le atout de Gemini est le contexte long. Gemini 1.5 Pro supporte plus d’1M tokens : longues vidéos, lots de documents. Dès que le volume visuel est important, Gemini mérite un essai.

Cas d’usage :

  • Analyse de longues vidéos (plus de 10 minutes)
  • Traitement par lots de documents
  • Tâches nécessitant des liens entre plusieurs contenus visuels

2.4 Open source : Qwen2-VL, GLM-4V

Pour budgets serrés, données sensibles ou déploiement privé, l’open source est une option concrète.

Qwen2-VL : open source d’Alibaba, optimisé pour le chinois, images jusqu’en 4K. Stable en entreprise ; coût d’appel environ 1/10 des modèles propriétaires.

GLM-4V : open source de Zhipu, adapté à la conformité en Chine ; architecture MoE avantageuse en coût d’inférence.

2.5 Matrice de décision

Choisissez selon le besoin réel :

ScénarioModèle recommandéRaison
Prototype rapide, MVPGPT-4oAPI mature, documentation solide, débogage facile
Analyse documentaire, extractionClaude VisionFort sur les détails, tableaux précis
Longues vidéosGemini 1.5 ProContexte très long, raisonnement multimodal
Coût serré, fort traficQwen2-VLOpen source, faible coût par appel
Données sensibles, déploiement privéGLM-4VLocal, données sans sortie du périmètre
Contexte chinois, budget limitéQwen2-VLOptimisation chinois, bon rapport qualité-prix

III. Pratique : compréhension et traitement d’images

3.1 Bases des appels API

Le cœur des API multimodales : construire le bon format de message. OpenAI ou Anthropic, même principe — texte et image comme parties distinctes du message.

Attention à la taille : les tokens visuels dépendent des pixels. GPT-4o redimensionne automatiquement, mais pour maîtriser le coût, prétraitez vous-même avant l’envoi.

3.2 Description et questions-réponses sur images

Cas de base : décrire une image ou répondre à une question. Exemple d’encapsulation complète :

from openai import OpenAI
import base64
from pathlib import Path

class ImageAnalyzer:
    def __init__(self, model="gpt-4o"):
        self.client = OpenAI()
        self.model = model

    def analyze(self, image_path: str, question: str) -> str:
        """Analyser une image et répondre à une question"""
        # Lire l'image
        with open(image_path, "rb") as f:
            image_data = base64.b64encode(f.read()).decode("utf-8")

        # Déterminer le type MIME
        suffix = Path(image_path).suffix.lower()
        media_type = {
            ".jpg": "image/jpeg",
            ".jpeg": "image/jpeg",
            ".png": "image/png",
            ".gif": "image/gif",
            ".webp": "image/webp"
        }.get(suffix, "image/jpeg")

        # Construire la requête
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[{
                "role": "user",
                "content": [
                    {"type": "text", "text": question},
                    {"type": "image_url", "image_url": {
                        "url": f"data:{media_type};base64,{image_data}"
                    }}
                ]
            }],
            max_tokens=1000
        )

        return response.choices[0].message.content

# Exemple d'utilisation
analyzer = ImageAnalyzer()
result = analyzer.analyze("product.jpg", "Quelle est la marque de ce produit ? Quel est le prix ?")
print(result)

3.3 Analyse documentaire (PDF / graphiques)

Pour un PDF, convertissez chaque page en image puis analysez page par page. Parseur documentaire pratique :

import fitz  # PyMuPDF
from PIL import Image
import io
import base64
from openai import OpenAI

def pdf_to_images(pdf_path: str, dpi: int = 150) -> list:
    """Convertir un PDF en liste d'images"""
    doc = fitz.open(pdf_path)
    images = []

    for page_num in range(len(doc)):
        page = doc[page_num]
        # Rendre la page en image
        mat = fitz.Matrix(dpi / 72, dpi / 72)
        pix = page.get_pixmap(matrix=mat)

        # Convertir en PIL Image
        img_data = pix.tobytes("png")
        img = Image.open(io.BytesIO(img_data))
        images.append(img)

    doc.close()
    return images

def extract_table_from_page(image: Image.Image, client: OpenAI) -> dict:
    """Extraire les données de tableaux d'une page"""
    # Convertir en base64
    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    image_data = base64.b64encode(buffer.getvalue()).decode("utf-8")

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": """
                Extrayez les données des tableaux de l'image, au format JSON.
                S'il y a plusieurs tableaux, utilisez un tableau.
                Exemple : {"tables": [{"headers": [...], "rows": [...]}]}
                """},
                {"type": "image_url", "image_url": {
                    "url": f"data:image/png;base64,{image_data}"
                }}
            ]
        }],
        response_format={"type": "json_object"}
    )

    import json
    return json.loads(response.choices[0].message.content)

# Flux complet
images = pdf_to_images("report.pdf")
for i, img in enumerate(images):
    print(f"Traitement de la page {i+1}...")
    tables = extract_table_from_page(img, OpenAI())
    print(f"{len(tables.get('tables', []))} tableaux extraits")

3.4 Traitement d’images en lot

Sur de gros volumes, le contrôle de concurrence est crucial — un flood d’appels déclenche le rate limiting :

import asyncio
from openai import AsyncOpenAI
import aiofiles
import base64

class BatchImageProcessor:
    def __init__(self, model="gpt-4o", max_concurrent=5):
        self.client = AsyncOpenAI()
        self.model = model
        self.semaphore = asyncio.Semaphore(max_concurrent)

    async def process_single(self, image_path: str, prompt: str) -> dict:
        """Traiter une image"""
        async with self.semaphore:
            try:
                async with aiofiles.open(image_path, "rb") as f:
                    image_bytes = await f.read()
                image_data = base64.b64encode(image_bytes).decode("utf-8")

                response = await self.client.chat.completions.create(
                    model=self.model,
                    messages=[{
                        "role": "user",
                        "content": [
                            {"type": "text", "text": prompt},
                            {"type": "image_url", "image_url": {
                                "url": f"data:image/jpeg;base64,{image_data}"
                            }}
                        ]
                    }]
                )

                return {
                    "path": image_path,
                    "result": response.choices[0].message.content,
                    "success": True
                }
            except Exception as e:
                return {
                    "path": image_path,
                    "error": str(e),
                    "success": False
                }

    async def process_batch(self, image_paths: list, prompt: str) -> list:
        """Traiter un lot d'images"""
        tasks = [self.process_single(p, prompt) for p in image_paths]
        return await asyncio.gather(*tasks)

# Exemple d'utilisation
async def main():
    processor = BatchImageProcessor(max_concurrent=3)
    results = await processor.process_batch(
        ["img1.jpg", "img2.jpg", "img3.jpg"],
        "Décrivez cette image en moins de 50 mots"
    )
    for r in results:
        print(f"{r['path']}: {r.get('result', r.get('error'))}")

asyncio.run(main())

IV. Pratique : compréhension de contenu vidéo

Le cœur du traitement vidéo est la « réduction de dimension » : transformer une suite d’images en frames clés discrètes, puis les analyser. L’équilibre entre exhaustivité et coût est le nœud du problème.

4.1 Extraction et traitement de frames

import cv2
import base64
from pathlib import Path

class VideoProcessor:
    def __init__(self, video_path: str):
        self.video_path = video_path
        self.cap = cv2.VideoCapture(video_path)
        self.fps = self.cap.get(cv2.CAP_PROP_FPS)
        self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
        self.duration = self.total_frames / self.fps

    def extract_frames(self, strategy="interval", **kwargs):
        """Extraire des frames vidéo

        Args:
            strategy: stratégie d'extraction
                - interval: une frame toutes les N secondes
                - scene: à chaque changement de scène
                - uniform: N frames uniformément réparties
        """
        frames = []

        if strategy == "interval":
            interval_sec = kwargs.get("interval", 1.0)
            interval_frames = int(interval_sec * self.fps)

            frame_idx = 0
            while self.cap.isOpened():
                ret, frame = self.cap.read()
                if not ret:
                    break
                if frame_idx % interval_frames == 0:
                    frames.append((frame_idx / self.fps, frame))
                frame_idx += 1

        elif strategy == "uniform":
            num_frames = kwargs.get("num_frames", 10)
            interval = max(1, self.total_frames // num_frames)

            for i in range(num_frames):
                self.cap.set(cv2.CAP_PROP_POS_FRAMES, i * interval)
                ret, frame = self.cap.read()
                if ret:
                    frames.append((i * interval / self.fps, frame))

        self.cap.release()
        return frames

    def frame_to_base64(self, frame) -> str:
        """Convertir une frame en base64"""
        _, buffer = cv2.imencode('.jpg', frame)
        return base64.b64encode(buffer).decode('utf-8')

# Exemple d'utilisation
processor = VideoProcessor("demo.mp4")
print(f"Durée de la vidéo : {processor.duration:.1f} s")

# Une frame toutes les 2 secondes
frames = processor.extract_frames(strategy="interval", interval=2.0)
print(f"{len(frames)} frames extraites")

4.2 Stratégies pour longues vidéos

Sur de longues vidéos, les coûts explosent. Quelques stratégies utiles :

Traitement hiérarchique : parcourir d’abord en basse résolution et faible fréquence d’images pour repérer les segments clés ; analyser finement uniquement ces passages.

Détection de scènes : ne traiter que les frames où la scène change, ignorer les plans statiques. OpenCV propose des outils dédiés.

Résumé prioritaire : demander un résumé par segment, puis synthétiser l’ensemble.

4.3 Cas pratique : génération de résumé vidéo

from openai import OpenAI

def generate_video_summary(frames: list, client: OpenAI) -> str:
    """Générer un résumé vidéo à partir de frames clés"""
    # Regrouper par lots de 5 frames maximum
    batch_size = 5
    segment_summaries = []

    for i in range(0, len(frames), batch_size):
        batch = frames[i:i+batch_size]

        # Construire le contenu du message
        content = [{"type": "text", "text": "Décrivez brièvement ce qui se passe dans ces images"}]
        for timestamp, frame in batch:
            frame_base64 = VideoProcessor("").frame_to_base64(frame)
            content.append({
                "type": "image_url",
                "image_url": {"url": f"data:image/jpeg;base64,{frame_base64}"}
            })

        # Appel API
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[{"role": "user", "content": content}]
        )

        segment_summaries.append(response.choices[0].message.content)

    # Synthèse finale
    final_prompt = f"""
    Voici les résumés de chaque segment de la vidéo :
    {chr(10).join(f'{i+1}. {s}' for i, s in enumerate(segment_summaries))}

    Synthétisez ces informations en un résumé complet incluant :
    1. Contenu principal
    2. Événements ou informations clés
    3. Thème global
    """

    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": final_prompt}]
    )

    return response.choices[0].message.content

V. Optimisation des coûts et performance

Le coût des appels multimodaux vient surtout des tokens visuels. Une image 1024×1024 consomme environ 765 tokens ; mal géré, une requête peut coûter très cher.

5.1 Calcul des tokens visuels

Règles de calcul pour GPT-4o :

Taille d’imageMode basse résolutionMode haute résolution
512×51285 tokens255 tokens
1024×1024170 tokens765 tokens
2048×2048255 tokens2550 tokens

Le mode basse résolution convient sans détail fin (type d’image, description grossière). Pour lire du texte ou des détails, il faut la haute résolution.

5.2 Compression et prétraitement d’images

Prétraiter avant l’upload est un levier efficace :

from PIL import Image
from pathlib import Path

def optimize_image(image_path: str, max_size: int = 1024, quality: int = 85) -> str:
    """Optimiser taille et qualité de l'image"""
    img = Image.open(image_path)

    # Redimensionner
    if max(img.size) > max_size:
        ratio = max_size / max(img.size)
        new_size = (int(img.size[0] * ratio), int(img.size[1] * ratio))
        img = img.resize(new_size, Image.Resampling.LANCZOS)

    # Rogner une zone clé (si la position est connue)
    # img = img.crop((left, top, right, bottom))

    # Sauvegarder l'image optimisée
    optimized_path = f"optimized_{Path(image_path).name}"
    img.save(optimized_path, "JPEG", quality=quality)

    return optimized_path

# Exemple d'utilisation
optimized = optimize_image("screenshot.png", max_size=1024)
# Une capture de 2 Mo peut passer à ~200 Ko

5.3 Cache et traitement par lots

Cache de résultats : même image, même requête — utiliser le hash de l’image comme clé :

import hashlib

def get_image_hash(image_path: str) -> str:
    """Calculer le hash d'une image"""
    with open(image_path, "rb") as f:
        return hashlib.md5(f.read()).hexdigest()

# Logique de cache
cache = {}
image_hash = get_image_hash("product.jpg")

if image_hash in cache:
    result = cache[image_hash]
else:
    result = analyzer.analyze("product.jpg", "Décrivez ce produit")
    cache[image_hash] = result

Fusion par lot : pour plusieurs images liées, privilégier une seule requête :

# Déconseillé : requêtes multiples
for img in images:
    result = analyze_image(img, "Décrivez l'image")

# Recommandé : une seule requête
all_images_content = [{"type": "text", "text": "Décrivez ces images"}]
for img in images:
    all_images_content.append({
        "type": "image_url",
        "image_url": {"url": img_url}
    })

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": all_images_content}]
)

5.4 Appels mixtes selon le modèle

Toutes les tâches n’exigent pas le modèle le plus puissant. Stratification possible :

def smart_analyze(image_path: str, task_type: str):
    """Choisir le modèle selon le type de tâche"""
    if task_type in ["classify", "detect"]:
        # Classification / détection simple : petit modèle
        model = "gpt-4o-mini"
    elif task_type in ["ocr", "extract"]:
        # OCR, extraction : modèle intermédiaire
        model = "gpt-4o"
    else:
        # Raisonnement complexe : modèle fort
        model = "gpt-4o"

    # ... logique d'appel

VI. Bonnes pratiques de déploiement en production

Du démo à la production, de nombreux aspects d’ingénierie entrent en jeu.

6.1 Gestion d’erreurs et retry

Les appels API échouent : timeout, rate limit, erreur serveur. Un retry robuste est indispensable :

import time
from openai import APIError, RateLimitError, APIConnectionError

def robust_api_call(func, max_retries=3, backoff_factor=2):
    """Appel API avec mécanisme de retry"""
    for attempt in range(max_retries):
        try:
            return func()
        except RateLimitError:
            if attempt < max_retries - 1:
                wait_time = backoff_factor ** attempt
                print(f"Rate limit atteint, nouvelle tentative dans {wait_time} s...")
                time.sleep(wait_time)
            else:
                raise
        except APIConnectionError as e:
            print(f"Erreur de connexion réseau : {e}")
            if attempt < max_retries - 1:
                time.sleep(1)
            else:
                raise
        except APIError as e:
            print(f"Erreur API : {e}")
            raise

6.2 Concurrence et limitation de débit

Les limites multimodales sont souvent plus strictes que pour le texte. Exemple de token bucket :

import asyncio
import time

class RateLimiter:
    def __init__(self, requests_per_minute: int):
        self.interval = 60.0 / requests_per_minute
        self.last_request = 0
        self.lock = asyncio.Lock()

    async def acquire(self):
        async with self.lock:
            now = time.time()
            wait_time = self.last_request + self.interval - now
            if wait_time > 0:
                await asyncio.sleep(wait_time)
            self.last_request = time.time()

# Exemple d'utilisation
limiter = RateLimiter(requests_per_minute=100)

async def process_with_limit(image_path):
    await limiter.acquire()
    return await async_analyze(image_path)

6.3 Monitoring et logs

Journaliser tokens et latence à chaque appel facilite le diagnostic :

import logging
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def log_api_call(model: str, input_tokens: int, output_tokens: int, latency: float):
    logger.info(
        f"Appel API - modèle : {model}, "
        f"tokens entrée : {input_tokens}, tokens sortie : {output_tokens}, "
        f"latence : {latency:.2f} s"
    )

# Après l'appel
start_time = time.time()
response = client.chat.completions.create(...)
latency = time.time() - start_time

log_api_call(
    model="gpt-4o",
    input_tokens=response.usage.prompt_tokens,
    output_tokens=response.usage.completion_tokens,
    latency=latency
)

6.4 Exemple complet

Classe utilitaire intégrant les éléments précédents :

from openai import OpenAI
from pathlib import Path
import base64
import logging
import time
from typing import Optional, List, Dict

logger = logging.getLogger(__name__)

class MultimodalAnalyzer:
    """Classe utilitaire d'analyse multimodale"""

    def __init__(
        self,
        model: str = "gpt-4o",
        max_retries: int = 3,
        requests_per_minute: int = 100
    ):
        self.client = OpenAI()
        self.model = model
        self.max_retries = max_retries
        self.min_interval = 60.0 / requests_per_minute
        self.last_request_time = 0

    def _wait_for_rate_limit(self):
        """Limitation de débit"""
        now = time.time()
        wait_time = self.last_request_time + self.min_interval - now
        if wait_time > 0:
            time.sleep(wait_time)
        self.last_request_time = time.time()

    def _read_image(self, image_path: str) -> str:
        """Lire une image et la convertir en base64"""
        with open(image_path, "rb") as f:
            return base64.b64encode(f.read()).decode("utf-8")

    def _call_with_retry(self, messages: list) -> dict:
        """Appel API avec retry"""
        for attempt in range(self.max_retries):
            try:
                self._wait_for_rate_limit()
                start_time = time.time()

                response = self.client.chat.completions.create(
                    model=self.model,
                    messages=messages,
                    max_tokens=1000
                )

                latency = time.time() - start_time
                logger.info(
                    f"Appel API réussi - tokens : {response.usage.total_tokens}, "
                    f"latence : {latency:.2f} s"
                )

                return {
                    "content": response.choices[0].message.content,
                    "tokens": {
                        "prompt": response.usage.prompt_tokens,
                        "completion": response.usage.completion_tokens
                    }
                }

            except Exception as e:
                logger.error(f"Échec API (tentative {attempt + 1}/{self.max_retries}) : {e}")
                if attempt == self.max_retries - 1:
                    raise
                time.sleep(2 ** attempt)

    def analyze_image(
        self,
        image_path: str,
        prompt: str,
        detail: str = "auto"
    ) -> dict:
        """Analyser une image"""
        image_data = self._read_image(image_path)

        messages = [{
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image_data}",
                        "detail": detail
                    }
                }
            ]
        }]

        return self._call_with_retry(messages)

    def analyze_multiple_images(
        self,
        image_paths: List[str],
        prompt: str
    ) -> dict:
        """Analyser plusieurs images"""
        content = [{"type": "text", "text": prompt}]

        for path in image_paths:
            image_data = self._read_image(path)
            content.append({
                "type": "image_url",
                "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}
            })

        return self._call_with_retry([{"role": "user", "content": content}])

    def extract_text_from_image(self, image_path: str) -> str:
        """Extraire le texte d'une image (OCR)"""
        result = self.analyze_image(
            image_path,
            "Extrayez tout le texte de l'image, en conservant la mise en forme d'origine"
        )
        return result["content"]

    def describe_image(self, image_path: str) -> str:
        """Générer une description d'image"""
        result = self.analyze_image(
            image_path,
            "Décrivez le contenu de cette image en un paragraphe"
        )
        return result["content"]


# Exemple d'utilisation
if __name__ == "__main__":
    analyzer = MultimodalAnalyzer()

    # Analyser une image
    result = analyzer.analyze_image(
        "product.jpg",
        "Quelle est la marque de ce produit ? Quel est le prix ?"
    )
    print(result["content"])

    # OCR
    text = analyzer.extract_text_from_image("document.png")
    print(text)

Synthèse

L’IA multimodale passe de « gadget » à outil utile. Au choix du modèle, ne vous limitez pas aux benchmarks : longues vidéos → Gemini, documents → Claude, prototype rapide → GPT-4o, budget serré → open source.

En développement, le coût est central. Prétraitement d’images, bon choix de résolution et cache réduisent nettement la facture. En production, retry, rate limiting et logs sont non négociables.

Les limites du multimodal continuent de s’étendre. Directions à suivre en 2025 : agents multimodaux, contextes plus longs, progrès des modèles open source. Maîtriser ces bases permet de s’adapter vite à l’évolution technique.


Références

FAQ

GPT-4o ou GPT-4V : lequel choisir ?
Choisissez GPT-4o pour le raisonnement visuel et les dialogues multimodaux multi-tours ; GPT-4V pour des descriptions d'images simples, une faible latence ou la compatibilité avec d'anciens systèmes.
Comment maîtriser les coûts des API multimodales ?
Trois leviers clés :

• Prétraiter les images : compresser et réduire la résolution avant l'envoi
• Choisir la bonne résolution : mode basse résolution quand les détails ne sont pas nécessaires
• Mettre en cache : stocker les résultats des requêtes sur la même image
Quelles techniques pour traiter de longues vidéos ?
Traitement hiérarchique : parcourir d'abord à faible fréquence d'images pour repérer les segments clés ; détection de scènes pour ne traiter que les changements visuels ; résumé prioritaire : résumer par segment puis synthétiser. Gemini 1.5 Pro supporte un contexte très long et peut traiter une vidéo entière en une fois.
Les modèles multimodaux open source sont-ils viables ?
Oui. Qwen2-VL est optimisé pour le chinois, GLM-4V convient aux exigences de conformité en Chine. Pour les scénarios sensibles aux coûts ou nécessitant un déploiement privé, l'open source est un choix réaliste — coût d'appel environ 1/10 des modèles propriétaires.
Quelles préparations pour la production ?
Trois piliers : retry sur erreur (timeouts réseau, rate limiting), contrôle de concurrence (token bucket pour éviter le blocage), monitoring et logs (tokens et latence à chaque appel).

16 min de lecture · Publié le: 24 mars 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog