Suas fotos de iPhone finalmente funcionam: decodificação HEIC no vision_analyze e ferramentas de visão que param de desaparecer

Você tira um print de um erro no celular, envia por AirDrop para a máquina que roda o Hermes e pede para o agente dar uma olhada. A resposta vem assim: source is not a recognized image. O arquivo está bem ali, você abre localmente sem problema nenhum, mas para o agente ele não é imagem alguma. Enquanto isso, algo mais estranho acontece em um processo de longa duração: o vision_analyze funcionou de manhã e à tarde avisa que a visão está indisponível — e você não mudou nada.
Esses dois sintomas vêm de dois bugs independentes, ambos corrigidos recentemente. Um é um sniffer de formato de imagem que nunca soube o que é HEIC de iPhone; o outro é uma probe de inicialização que envenenou um cache de cliente e fez as ferramentas de visão desaparecerem sozinhas. A seguir, o que aconteceu e como verificar a sua própria instalação.
Sintoma um: uma foto de iPhone “não é uma imagem”
Primeiro, a versão curta da ferramenta envolvida: vision_analyze é como o agente olha para uma imagem. Você entrega um caminho de arquivo ou um anexo, ele manda a imagem para um modelo de visão, e você recebe de volta uma descrição ou uma resposta.
Fotos de iPhone vêm por padrão em HEIC (também chamado de HEIF), que não é da mesma família do JPEG. JPEG é pixel com cabeçalho; HEIC é embrulhado em um formato de contêiner chamado ISO-BMFF. Pense no contêiner como uma caixa de transporte: a marca escrita na caixa é que decide se a carga é uma imagem estática HEVC, uma imagem AVIF ou um pedaço de vídeo. O Hermes decide se um arquivo é imagem — e de que tipo — lendo os primeiros bytes (magic-byte sniffing) em vez de confiar na extensão, porque pipelines de upload renomeiam HEIC como .jpg o tempo todo. Extensões mentem; cabeçalhos não.
O sniffer antigo só reconhecia as credenciais de PNG / JPEG / GIF / WebP. Não havia ramo nenhum para contêineres ISO-BMFF. Então um arquivo HEIC saía do sniffer como “unknown” e nunca chegava ao caminho de reencode que já tratava SVG, BMP e TIFF. A imagem não era não-conversível. Ela simplesmente não passava pela porta.
Correção um: ler a brand da caixa ftyp (PR #106408)
A correção adiciona um ramo ISO-BMFF ftyp ao sniffer. Os bytes 4 a 8 do contêiner são sempre ftyp, seguidos de uma major brand e de uma lista de compatible brands — o rótulo da caixa mais a sua lista de “também conhecida como”.
Três detalhes em tools/vision_tools_image_prep.py importam:
- Tanto a major brand quanto a lista de compatible brands são consultadas. O AVIF costuma declarar o genérico
mif1como major brand e só identifica o AV1 na lista de compatíveis. Uma checagem apenas de major brand chamaria o AVIF de unknown. - A varredura é limitada pelo tamanho declarado da caixa e falha fechado diante de um tamanho malformado. Esse campo de comprimento é entrada controlada pelo atacante; deixá-lo ampliar a varredura para as caixas seguintes significa que o resultado do sniff pode ser construído, e não lido. Tamanho inválido é falha, não melhor palpite.
- Outras brands de
ftypseguem não reconhecidas.mp42(um vídeo MP4) não é imagem e não é entregue a um modelo de visão.
Depois de reconhecido, o arquivo segue o caminho existente _normalize_to_supported_image e é convertido para PNG antes de chegar ao modelo. A decodificação de HEIC vem do pillow-heif, registrado como dependência soft — as wheels pré-compiladas trazem o libheif junto, então não há bibliotecas de sistema para instalar. Decoder ausente gera uma mensagem acionável em vez de uma stack de visão quebrada.
O AVIF é tratado um pouco diferente de propósito: o Pillow 11.3 e posteriores decodificam AVIF nativamente, enquanto muitas wheels do pillow-heif não trazem codec AV1 nenhum — por isso o AVIF não depende do pillow-heif.
O pillow-heif agora é dependência principal (pillow-heif>=1.4.0,<2), então uma instalação nova funciona de saída:
# Fallback se você roda em um ambiente enxuto, sem o decoder
pip install pillow-heif
# Traga a versão que carrega as duas correções
hermes update
Vale saber: isso entrou como salvamento de uma contribuição da comunidade (@samfoy), e a mesma classe de bug foi corrigida upstream no MoonshotAI/kimi-code — o ReadMediaFile deles também recusava .heic de iPhone contra uma allowlist fixa de PNG/JPEG/GIF/WebP sem ramo HEIF. Mesma conclusão dos dois lados: uma allowlist de formatos precisa de sniffing de contêiner, ou “fotos tiradas no celular” falham para sempre.
Sintoma dois: ferramentas de visão que desaparecem em silêncio
O segundo sintoma é mais sorrateiro. Em um processo de vida longa — um gateway, um backend desktop, uma sessão aberta por horas — o vision_analyze pode começar a relatar que a visão está indisponível, e um restart resolve. Os suspeitos naturais são o provider ou a sua cota. O culpado real era uma probe de inicialização gravando um cliente stub no cache de runtime.
Veja o mecanismo. Na inicialização, o Hermes roda uma probe de capacidade de visão sobre o cliente de modelo auxiliar. Para manter o round trip de rede dessa probe fora do caminho de startup, a probe usa um stub client e troca pelo cliente real logo depois. O bug: o stub era armazenado sob a chave de cache de runtime real.
O PR #110136 detalha a consequência. A probe seguinte encontra o stub em cache, chega em _compat_model() e faz getattr(stub, "_client", None) — o que levanta RuntimeError, porque um default de getattr só absorve AttributeError. O check_vision_requirements() engolia isso como False pelo resto da vida do processo. Só model ids com barra chegam a esse ramo, e é por isso que os relatos se concentraram em configs vendor/model — NVIDIA, LM Studio, koboldcpp, litellm.
A correção mantém clientes em modo probe totalmente fora do cache e baseia a guarda em estar em modo probe, não no tipo do objeto — porque wrappers de adapter (CodexAuxiliaryClient e companhia) ficam em volta do stub e derrotam uma checagem isinstance.
Uma ressalva honesta: isso não cura um processo que já está rodando. O stub está no cache daquele processo; a correção passa a valer depois de um restart, que uma atualização já te dá de qualquer forma.
Faça a verificação você mesmo
Não adivinhe em que estado está a sua instalação. São três comandos:
# 1) O que o arquivo realmente é
file ~/Downloads/IMG_1234.HEIC
# 2) O lado Python consegue decodificar? (depois de instalar o 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) Pergunte direto ao agente, dentro de uma sessão
# > use vision_analyze em /path/to/IMG_1234.HEIC e diga o que aparece
Se o passo 2 levantar “no module named pillow_heif”, falta o decoder. Se o passo 2 funcionar mas o passo 3 ainda disser que a origem não é uma imagem reconhecível, você está quase certamente rodando código anterior à correção — atualize.
Se você lida muito com mídia de celular, dois conteúdos relacionados deste site valem o seu tempo: entrega de imagem e vídeo em sandbox no guia de entrega de mídia em sandbox, e extração de documentos para PDF, Word, Excel e EPUB em extração de documentos com read_file. Para imagens simplesmente pequenas demais, veja o guia de upscale de mídia.
Ressalvas e status de release
- Normalização significa transcodificação, não suporte nativo. O HEIC vira PNG antes de chegar ao modelo, então um lote de fotos grandes custa CPU e disco temporário. Não jogue mil arquivos de uma vez.
- “Meu desktop abre isso” não prova nada. O seu visualizador traz o próprio decoder; o Hermes decodifica no servidor, por um caminho diferente.
- As duas correções são independentes. O sniffing de formato responde “eu reconheço este arquivo”; o cache da probe responde “este processo ainda quer olhar imagens”. Uma atualização carrega as duas.
- Status de release: as duas mudanças (#106408, #110136) foram mergeadas depois da tag v0.21.2, então no momento em que escrevo elas vivem na
maine ainda não estão em um release tagueado. Ohermes updatepega amain, ou espere o próximo release.
A conclusão
Duas regras cobrem os dois bugs: faça sniff do contêiner, não da extensão — e nunca guarde em cache um stub de probe como se fosse um cliente real.
No uso diário isso significa três coisas:
- Fotos de iPhone e Android agora podem ir direto para o
vision_analyze. - Se a visão ainda recusar depois de atualizar, verifique se o pillow-heif está presente.
- Se um processo de longa duração de repente afirmar que a visão está indisponível, reinicie-o primeiro — depois verifique se você estava no bug antigo do cache da probe.
Na próxima vez que um agente insistir que a sua imagem não é uma imagem, você vai saber exatamente onde olhar.