Aller au contenu

Vision OCR

Vision OCR est un sous-workflow N8N appelé depuis le Telegram Orchestrator chaque fois qu’un utilisateur envoie une photo. Il classe l’image dans une des cinq catégories de documents reconnus, puis applique un schéma d’extraction spécialisé pour retourner des données structurées prêtes à être consommées par les workflows métier (Odoo, notes, factures).

ChampValeur
Workflow ID2ZDgU3TWbF4OeOKY
TypeExecute Workflow Trigger (passthrough)
Nodes14
Appelé parBinary Content Handler (QtkJDN8XAGlpcSPV)
Modèlegemini-flash-yolo via cli-ollama
Issue#176

Sortie · contrat uniforme

Phase 2 · Extraction par schéma

Phase 1 · Classification

HTTP error

not_document

recognized type

Entrée · passthrough Binary Content Handler

Photo Telegram · base64 + mimeType

HTTP cli-ollama gemini-flash-yolo

Parse Detection · fallback regex

Switch Doc Type

Extract Business Card

Extract Invoice

Extract Screenshot

Extract Handwritten

Extract General Document

Parse and Format · escape HTML

status: success

status: fallback · not_document

status: error

TypeChamps extraitsUsage typique
business_cardname, function, company_name, email, phone, mobile, street, city, zip, country, website, commentCréation de contact Odoo
invoicevendor, invoice_number, date, items[], subtotal, tax, total, currencyPièce comptable Odoo
screenshotvisible_text, ui_elements[], error_messages[], contextDiscussion conversationnelle, tickets
handwritten_notetranscribed_text, confidence, languageNote dans Obsidian vault
general_documentfull_text, document_title, key_sections[]Texte recherche-able
not_document(aucun)Photo de scène, fallback IA Router

Avant ce sous-workflow, chaque photo envoyée au bot Telegram était traitée comme un message vide ou nécessitait une saisie manuelle. Photographier une carte de visite et taper ensuite tous les champs dans Odoo prenait plusieurs minutes par contact.

Problème sans extractionConséquence
Saisie manuelleQuelques minutes par carte, taux d’erreur élevé
Pas de structureImpossible de filtrer/rechercher après coup
Pas de classificationToutes les photos traitées identiquement
Pas de fallbackUne photo de paysage générait une erreur

Demander à un modèle vision “extrait les informations utiles” sur n’importe quelle image produit des résultats inégaux. Une carte de visite a des champs prévisibles, une facture a un format différent, un screenshot d’erreur n’a pas de “champs”. Le pipeline en deux phases résout cette tension :

ApprocheAvantageInconvénient
Prompt générique uniqueSimple, 1 appel LLMChamps incohérents, format JSON instable
Pipeline classify → extractSchéma adapté à chaque type2 appels LLM, latence x2

L’overhead de latence (≈ 4-6 s) est acceptable parce que l’utilisateur attend déjà la transcription. La qualité de l’extraction et la stabilité du contrat de sortie justifient le coût.

CritèreGemini FlashGPT-4 VisionClaude 3 Vision
CoûtGratuit (cli-ollama)$0.01 / image$0.005 / image
Latence1-3 s3-8 s2-5 s
JSON structuréVariable, parsing défensif requisStableStable
OCR latinExcellentExcellentExcellent
OCR manuscritBonExcellentTrès bon
HébergementSelf-hosted (cli-ollama)API OpenAIAPI Anthropic

Le choix Gemini Flash s’aligne avec la stratégie multi-provider de cli-ollama : pas de coût à l’image, latence acceptable, JSON parseable avec un fallback regex défensif.


Trois statuts possibles, tous renvoyés via le même format :

Succès :

{
"status": "success",
"docType": "invoice",
"extracted": {
"vendor": "Amazon",
"invoice_number": "INV-2026-001",
"total": 23.98,
"currency": "EUR"
},
"text": "<b>Facture</b>\n\nVendeur: <b>Amazon</b>\n..."
}

Fallback (pas un document) :

{
"status": "fallback",
"docType": "not_document"
}

Erreur (cli-ollama injoignable) :

{
"status": "error",
"error": "cli-ollama request failed (HTTP 500)"
}

Le champ text contient une représentation HTML formatée prête à envoyer dans Telegram. Le champ extracted contient les données structurées qu’un workflow appelant peut consommer pour créer un enregistrement Odoo, par exemple.

Le parsing JSON Gemini est défensif sur plusieurs niveaux :

CasComportement
JSON propreParse direct
JSON enveloppé dans backticks markdownStrip ``` puis parse
JSON avec préambule textuelRegex \{[\s\S]*\} puis parse
JSON malforméFallback sur general_document avec text: <raw response>
Type inconnu retournéForce not_document
HTTP error cli-ollamastatus: error propagé sans crash

Cette défensive est nécessaire parce que Gemini Flash retourne parfois du texte additionnel (“Voici le résultat:”) avant le JSON, ou enveloppe le JSON dans des backticks. Sans le fallback, le workflow appelant recevait des erreurs de parsing au lieu d’un fallback gracieux.

Le détecteur initial classifie l’image avec un prompt court :

Analyze this image and classify it as ONE of:
- business_card
- invoice
- screenshot
- handwritten_note
- general_document
- not_document
Return ONLY JSON: {"type": "...", "confidence": 0.95}

Puis le routage Switch envoie l’image vers un prompt d’extraction spécialisé. Exemple pour une carte de visite — les champs sont alignés sur le modèle res.partner d’Odoo pour permettre une création directe via XML-RPC :

Extract contact information from this business card.
Return ONLY JSON (null if not found):
{
"name": "Full name",
"function": "Job title",
"company_name": "Company",
"email": "...",
"phone": "...",
"mobile": "...",
"street": "...",
"city": "...",
"zip": "...",
"country": "...",
"website": "...",
"comment": "LinkedIn or notes"
}

Le Binary Content Handler appelle Vision OCR via Execute Workflow puis route le résultat selon le status :

StatusRouting
success + docType=business_cardProposition d’enregistrer comme contact Odoo (boutons Telegram)
success + docType=invoiceProposition de stocker comme pièce comptable
success + docType=screenshot/handwritten/generalAffichage direct + bouton “Discuter” (lance une conversation)
fallback (not_document)Bascule vers IA Router pour traitement conversationnel
errorNotification d’erreur via Notification Hub
ÉtapeLatence typique
Encodage base64 + transfert100-500 ms (selon taille photo)
Détection classification1-2 s
Extraction par schéma2-4 s
Format HTML + return< 100 ms
Total bout-en-bout3-7 s

La compression Telegram réduit déjà les photos à ~1 MB max, ce qui maintient la latence dans une fourchette acceptable même pour des cartes de visite haute résolution.


LimiteImpactMitigation
Latence x22 appels LLM successifsAcceptable, l’utilisateur attend déjà
Pas de classification multi-docUne photo avec carte + ticket = 1 seul typeDemander 2 photos séparées
Pas de validation OdooEmail malformé créerait un partner invalideValidation côté workflow appelant
Pas de mémoire entre photosChaque photo est traitée isolémentLe système conversationnel garde le contexte une fois la photo extraite

Si la qualité d’extraction se dégrade :

  • Passer à gemini-pro-yolo pour les types complexes (factures multi-lignes)
  • Ajouter une étape de validation/correction par un second prompt
  • Comparer avec un modèle alternatif (Claude Vision via Anthropic API si quota disponible)

Si de nouveaux types de documents émergent :

  • Ajouter un nouveau case dans le Switch + un prompt d’extraction dédié
  • Garder le contrat {status, docType, extracted, text} inchangé pour ne pas casser les workflows appelants
  • Exemples candidats : id_card, passport, recipe, prescription

Si les volumes augmentent significativement :

  • Mutualiser le cache de classification (mêmes images à 1-2 jours d’écart)
  • Passer à un modèle Vision local quantisé (LLaVA, MiniCPM) hébergé sur le VPS pour zéro latence réseau
  • Batching de plusieurs images en un seul appel API

  • Glossaire — OCR, Vision, Sub-workflow, cli-ollama