Aller au contenu principal

Limites et Optimisation de la Vision API

Connaître les limites pour mieux les contourner

La Vision API de Mistral est un outil puissant, mais elle a des limites qu’il est essentiel de connaître pour construire des applications fiables. Dans cette leçon, vous découvrirez les contraintes techniques, les pièges courants et les stratégies d’optimisation.

Limites techniques

Taille et résolution des images

  • Taille maximale : environ 20 Mo par image
  • Résolution : les images sont redimensionnées en interne (généralement à 768x768 ou équivalent). Envoyer une image 4K n’améliore pas les résultats
  • Formats : JPEG, PNG, GIF (première frame uniquement), WebP
  • PDF : non supporté directement par la Vision API (utilisez Document AI pour les PDF)

Consommation de tokens

Chaque image consomme un nombre significatif de tokens :

# Estimation du coût d'un appel vision
def estimer_cout(nb_images=1, tokens_texte=100, tokens_reponse=500,
                 tokens_par_image=1000, prix_input=0.10, prix_output=0.30):
    """Estime le coût en dollars pour un appel vision (Small 3.2)."""
    input_tokens = (nb_images * tokens_par_image) + tokens_texte
    output_tokens = tokens_reponse

    cout_input = (input_tokens / 1_000_000) * prix_input
    cout_output = (output_tokens / 1_000_000) * prix_output
    cout_total = cout_input + cout_output

    print(f"Tokens input : {input_tokens} ({cout_input:.4f} $)")
    print(f"Tokens output : {output_tokens} ({cout_output:.4f} $)")
    print(f"Coût total : {cout_total:.4f} $")
    return cout_total

# 1 image avec Small 3.2
estimer_cout(nb_images=1)

# 5 images avec Large 3
estimer_cout(nb_images=5, prix_input=2.00, prix_output=6.00)

Fenêtre de contexte

Le total des tokens (images + prompt + réponse) ne doit pas dépasser la fenêtre de contexte du modèle. Avec plusieurs images, vous pouvez atteindre cette limite rapidement.

Les hallucinations visuelles

Le modèle peut « inventer » des détails qui ne sont pas dans l’image. C’est le problème le plus critique en production.

Types d’hallucinations courantes

  • Texte inventé : le modèle lit un mot qui n’existe pas dans l’image
  • Chiffres approximatifs : les valeurs lues sur un graphique peuvent être fausses de 5-15 %
  • Détails ajoutés : le modèle complète ce qu’il ne voit pas clairement avec des informations plausibles mais fausses
  • Confusion d’éléments : deux objets proches sont confondus

Stratégies anti-hallucination

# 1. Demander un niveau de confiance
prompt_confiance = """Analysez cette image. Pour chaque information extraite, indiquez votre niveau de confiance :
- [CERTAIN] : clairement visible
- [PROBABLE] : partiellement visible ou déduit
- [INCERTAIN] : peu lisible, estimation

Si un élément est illisible, indiquez [ILLISIBLE] plutôt que de deviner."""

# 2. Demander une vérification croisée
prompt_verification = """Lisez le texte de ce document. Après la transcription, relisez l'image et vérifiez chaque chiffre et chaque nom propre. Corrigez les erreurs détectées."""

# 3. Double lecture avec deux modèles
def double_verification(image_uri, question):
    """Envoie la même image à deux modèles et compare les résultats."""
    resultats = {}
    for modele in ["mistral-small-latest", "mistral-large-latest"]:
        response = client.chat.complete(
            model=modele,
            messages=[{
                "role": "user",
                "content": [
                    {"type": "text", "text": question},
                    {"type": "image_url", "image_url": image_uri}
                ]
            }]
        )
        resultats[modele] = response.choices[0].message.content

    return resultats

Optimiser les coûts

Redimensionner avant l’envoi

L’optimisation la plus impactante : réduire la taille des images avant l’envoi.

from PIL import Image
from io import BytesIO
import base64

def optimiser_image(chemin, taille_max=1024, qualite=80):
    """Redimensionne et compresse une image pour l'API Vision."""
    img = Image.open(chemin)
    taille_originale = img.size

    # Redimensionner
    if max(img.size) > taille_max:
        img.thumbnail((taille_max, taille_max))

    # Compresser en JPEG
    buffer = BytesIO()
    img.convert("RGB").save(buffer, format="JPEG", quality=qualite)
    taille_fichier = buffer.tell()

    b64 = base64.standard_b64encode(buffer.getvalue()).decode("utf-8")

    print(f"Original : {taille_originale} | Optimisé : {img.size} | Taille : {taille_fichier/1024:.0f} Ko")
    return f"data:image/jpeg;base64,{b64}"

Choisir le bon modèle

Utilisez un routage par complexité :

def choisir_modele(tache):
    """Sélectionne le modèle optimal selon la tâche."""
    taches_simples = ["classification", "ocr_simple", "description", "triage"]
    taches_complexes = ["extraction_json", "comparaison", "analyse_graphique", "raisonnement"]

    if tache in taches_simples:
        return "mistral-small-latest"  # ~0.10 $/M tokens
    else:
        return "mistral-large-latest"  # ~2.00 $/M tokens

Mettre en cache les résultats

import hashlib
import json
from pathlib import Path

CACHE_DIR = Path("cache_vision/")
CACHE_DIR.mkdir(exist_ok=True)

def analyser_avec_cache(image_path, prompt, modele="mistral-small-latest"):
    """Analyse une image avec cache disque."""
    # Générer une clé de cache
    with open(image_path, "rb") as f:
        image_hash = hashlib.md5(f.read()).hexdigest()
    cache_key = hashlib.md5(f"{image_hash}{prompt}{modele}".encode()).hexdigest()
    cache_file = CACHE_DIR / f"{cache_key}.json"

    # Vérifier le cache
    if cache_file.exists():
        return json.loads(cache_file.read_text())

    # Appel API
    response = client.chat.complete(
        model=modele,
        messages=[{
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {"type": "image_url", "image_url": encoder_image(image_path)}
            ]
        }]
    )

    resultat = response.choices[0].message.content

    # Sauvegarder en cache
    cache_file.write_text(json.dumps(resultat, ensure_ascii=False))
    return resultat

Optimiser la latence

Pour les applications temps réel :

  1. Redimensionnez les images côté client (1024px max)
  2. Utilisez Small ou Ministral pour les réponses rapides
  3. Envoyez les images par URL si elles sont déjà hébergées (évite l’upload Base64)
  4. Activez le streaming pour afficher la réponse progressivement :
# Streaming pour une réponse progressive
stream = client.chat.stream(
    model="mistral-small-latest",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Décrivez cette image."},
            {"type": "image_url", "image_url": url_image}
        ]
    }]
)

for chunk in stream:
    if chunk.data.choices[0].delta.content:
        print(chunk.data.choices[0].delta.content, end="", flush=True)

Checklist de mise en production

Avant de déployer une application Vision en production :

  • Redimensionnement automatique des images (max 1024px)
  • Validation du format et de la taille avant envoi
  • Gestion des erreurs API (retry, timeout, fallback)
  • Cache des résultats pour les images identiques
  • Routage intelligent (Small pour le simple, Large pour le complexe)
  • Monitoring des coûts et de la latence
  • Tests avec des images de mauvaise qualité (flou, basse résolution, rotation)
  • Stratégie anti-hallucination (niveaux de confiance, double vérification)

Points clés à retenir

  • Les images sont redimensionnées en interne — inutile d’envoyer de la très haute résolution
  • Les hallucinations visuelles sont le risque principal — demandez des niveaux de confiance
  • Le redimensionnement côté client est l’optimisation la plus rentable
  • Le routage par complexité (Small vs Large) réduit les coûts de 90 % sur les tâches simples
  • Le cache disque évite les appels API redondants sur les mêmes images