Vos photos iPhone fonctionnent enfin : le décodage HEIC dans vision_analyze, et les outils de vision qui cessent de disparaître


Vous faites une capture d’écran d’une erreur sur votre téléphone, vous l’envoyez par AirDrop à la machine qui fait tourner Hermes, et vous demandez à l’agent d’y jeter un œil. Réponse : source is not a recognized image. Le fichier est bien là, vous l’ouvrez localement sans problème, mais pour l’agent ce n’est pas une image du tout. Pendant ce temps, quelque chose de plus étrange se produit dans un processus longue durée : vision_analyze fonctionnait ce matin et cet après-midi il vous annonce que la vision est indisponible — et vous n’avez rien changé.

Ces deux symptômes viennent de deux bugs indépendants, tous deux corrigés récemment. Le premier est un détecteur de format d’image qui n’avait jamais appris le HEIC des iPhone ; le second est une sonde de démarrage qui empoisonnait un cache client et faisait disparaître les outils de vision tout seuls. Voici ce qui s’est passé, et comment vérifier votre propre installation.

Symptôme un : une photo iPhone n’est « pas une image »

Commençons par la version courte de l’outil concerné : vision_analyze est la façon dont l’agent regarde une image. Vous lui donnez un chemin de fichier ou une pièce jointe, il envoie l’image à un modèle de vision, et vous récupérez une description ou une réponse.

Les photos iPhone sont en HEIC par défaut (aussi appelé HEIF), et ce n’est pas la même famille que le JPEG. Un JPEG, ce sont des pixels avec un en-tête ; le HEIC est encapsulé dans un format conteneur appelé ISO-BMFF. Voyez le conteneur comme un carton d’expédition : la marque inscrite sur le carton décide si le contenu est une image fixe HEVC, une image AVIF ou un morceau de vidéo. Hermes détermine si un fichier est une image — et de quel type — en lisant les premiers octets (détection de magic bytes) plutôt qu’en faisant confiance à l’extension, car les pipelines d’upload renomment régulièrement du HEIC en .jpg. Les extensions mentent ; les en-têtes, non.

L’ancien détecteur ne reconnaissait que les cartes de visite PNG / JPEG / GIF / WebP. Il n’avait aucune branche pour les conteneurs ISO-BMFF. Un fichier HEIC ressortait donc du détecteur comme « inconnu » et n’atteignait jamais le chemin de ré-encodage qui gérait déjà le SVG, le BMP et le TIFF. L’image n’était pas inconvertible. Elle ne passait simplement jamais la porte.

Correctif un : lire la marque dans la boîte ftyp (PR #106408)

Le correctif ajoute une branche ISO-BMFF ftyp au détecteur. Les octets 4 à 8 du conteneur valent toujours ftyp, suivis d’une marque principale et d’une liste de marques compatibles — l’étiquette de la boîte plus sa liste de « aussi connu sous ».

Trois détails dans tools/vision_tools_image_prep.py comptent :

  1. La marque principale et la liste des marques compatibles sont toutes deux consultées. AVIF déclare régulièrement la marque générique mif1 comme marque principale et n’identifie l’AV1 que dans la liste des marques compatibles. Une vérification limitée à la marque principale classerait AVIF comme inconnu.
  2. L’analyse est bornée par la taille déclarée de la boîte, et elle échoue en fermant en cas de taille malformée. Ce champ de longueur est une entrée contrôlée par un attaquant ; le laisser élargir l’analyse aux boîtes suivantes signifie que le résultat de la détection peut être construit plutôt que lu. Une taille invalide est un échec, pas une supposition optimiste.
  3. Les autres marques ftyp restent non reconnues. mp42 (une vidéo MP4) n’est pas une image, et n’est pas transmis à un modèle de vision.

Une fois reconnu, le fichier emprunte le chemin existant _normalize_to_supported_image et est converti en PNG avant d’atteindre le modèle. Le décodage HEIC vient de pillow-heif, enregistré comme dépendance douce — les wheels précompilés embarquent libheif, donc aucune bibliothèque système à installer. Un décodeur manquant produit un message exploitable plutôt qu’une pile de vision cassée.

AVIF est traité légèrement différemment, volontairement : Pillow 11.3 et versions ultérieures décodent l’AVIF nativement, tandis que beaucoup de wheels pillow-heif n’embarquent aucun codec AV1 — AVIF n’est donc pas conditionné à pillow-heif.

pillow-heif est désormais une dépendance centrale (pillow-heif>=1.4.0,<2), donc une installation neuve fonctionne immédiatement :

# Solution de repli si vous travaillez dans un environnement allégé sans le décodeur
pip install pillow-heif

# Récupérez la version qui contient les deux correctifs
hermes update

Bon à savoir : ce correctif est arrivé en récupérant une contribution communautaire (@samfoy), et la même classe de bug a été corrigée en amont dans MoonshotAI/kimi-code — leur ReadMediaFile refusait lui aussi les .heic d’iPhone à cause d’une liste blanche PNG/JPEG/GIF/WebP codée en dur, sans branche HEIF. Même conclusion des deux côtés : une liste blanche de formats a besoin de la détection du conteneur, sinon « les photos prises avec un téléphone » échouent définitivement.

Symptôme deux : des outils de vision qui disparaissent discrètement

Le second symptôme est plus sournois. Dans un processus de longue durée — un gateway, un backend desktop, une session laissée ouverte pendant des heures — vision_analyze peut se mettre à signaler que la vision est indisponible, et un redémarrage corrige le problème. Les suspects naturels sont le provider ou votre quota. Le vrai coupable était une sonde de démarrage qui écrivait un client stub dans le cache d’exécution.

Voici le mécanisme. Au démarrage, Hermes exécute une sonde de capacité de vision sur le client de modèle auxiliaire. Pour sortir cet aller-retour réseau du chemin de démarrage, la sonde utilise un client stub et échange ensuite contre le vrai. Le bug : le stub était stocké sous la vraie clé de cache d’exécution.

La PR #110136 détaille la conséquence. La sonde suivante tombe sur le stub en cache, atteint _compat_model() et exécute getattr(stub, "_client", None) — ce qui lève une RuntimeError, car une valeur par défaut de getattr n’absorbe que les AttributeError. check_vision_requirements() avalait cela en False pour le reste de la durée de vie du processus. Seuls les identifiants de modèle contenant un slash atteignent cette branche, ce qui explique pourquoi les rapports se concentraient sur les configurations vendor/model — NVIDIA, LM Studio, koboldcpp, litellm.

Le correctif garde entièrement les clients en mode sonde hors du cache, et indexe la protection sur le fait d’être en mode sonde plutôt que sur le type de l’objet — car les wrappers d’adaptateur (CodexAuxiliaryClient et compagnie) entourent le stub et mettent en échec une vérification isinstance.

Mise en garde honnête : cela ne répare pas un processus déjà en cours d’exécution. Le stub est dans le cache de ce processus ; le correctif prend effet après un redémarrage, ce qu’une mise à jour vous offre de toute façon.

Faites la vérification vous-même

Ne devinez pas dans quel état se trouve votre installation. Trois commandes :

# 1) Ce qu'est réellement le fichier
file ~/Downloads/IMG_1234.HEIC

# 2) Le côté Python peut-il le décoder ? (après installation de pillow-heif)
python3 -c "import pillow_heif, PIL; pillow_heif.register_heif_opener(); im=PIL.Image.open('/path/to/IMG_1234.HEIC'); print(im.format, im.size)"

# 3) Demandez directement à l'agent, dans une session
#    > use vision_analyze on /path/to/IMG_1234.HEIC and tell me what it shows

Si l’étape 2 lève « no module named pillow_heif », le décodeur manque. Si l’étape 2 réussit mais que l’étape 3 dit encore que la source n’est pas une image reconnaissable, vous faites presque certainement tourner du code antérieur au correctif — mettez à jour.

Si vous travaillez beaucoup avec des médias de téléphone, deux articles connexes de ce site valent votre temps : la livraison d’images et de vidéos en sandbox dans le guide de livraison de médias en sandbox, et l’extraction de documents PDF, Word, Excel et EPUB dans l’extraction de documents avec read_file. Pour les images simplement trop petites, voir le guide d’agrandissement de médias.

Mises en garde et état de publication

  • La normalisation signifie transcodage, pas prise en charge native. Le HEIC devient du PNG avant d’atteindre le modèle, donc un lot de grandes photos coûte du CPU et du disque temporaire. N’envoyez pas mille fichiers d’un coup.
  • « Mon ordinateur l’ouvre » ne prouve rien. Votre visionneuse embarque son propre décodeur ; Hermes décode côté serveur par un chemin différent.
  • Les deux correctifs sont indépendants. La détection de format répond à « est-ce que je reconnais ce fichier » ; le cache de sonde répond à « ce processus veut-il encore regarder des images ». Une seule mise à jour embarque les deux.
  • État de publication : les deux changements (#106408, #110136) ont été fusionnés après le tag v0.21.2, donc au moment où j’écris ils vivent sur main et ne sont pas encore dans une version étiquetée. hermes update récupère main, ou attendez la prochaine version.

À retenir

Deux règles couvrent les deux bugs : analysez le conteneur, pas l’extension — et ne mettez jamais en cache un stub de sonde comme s’il s’agissait d’un vrai client.

Au quotidien, cela signifie trois choses :

  1. Les photos iPhone et Android peuvent désormais aller directement dans vision_analyze.
  2. Si la vision refuse encore après la mise à jour, vérifiez que pillow-heif est présent.
  3. Si un processus longue durée prétend soudain que la vision est indisponible, redémarrez-le d’abord — puis vérifiez si vous étiez sur l’ancien bug de cache de sonde.

La prochaine fois qu’un agent vous assurera que votre image n’est pas une image, vous saurez exactement où regarder.