ITSkillsCenter
Intelligence Artificielle

Agent devis automatique avec n8n et LLM : extraction, PDF, suivi

13 دقائق للقراءة

📍 Guide principal : Agents IA pour PME : architecture, déploiement et opérations en 2026

Introduction

La génération de devis est l’un des cas où un agent IA produit un retour mesurable rapide. Les demandes arrivent par e-mail ou WhatsApp, formulées librement : « Bonjour, je voudrais un prix pour 30 chaises pliantes type catering et 5 tables banquet 2m40 ». Un commercial passe trois à quinze minutes par demande à extraire les références, vérifier la disponibilité, calculer la remise volume, rédiger le devis, l’envoyer, le tracer dans le pipeline. Multipliez par vingt demandes par jour : c’est une journée de travail.

Ce tutoriel construit un agent qui automatise la chaîne complète. Il reçoit une demande en texte libre, extrait les articles via un appel structuré au modèle, croise avec le catalogue Sheets ou Postgres, applique les règles tarifaires, génère un PDF propre, l’envoie au client, et inscrit la demande dans le pipeline commercial. L’humain garde un rôle de validation pour les montants supérieurs à un seuil configurable.

Prérequis

  • n8n version 2.0 ou ultérieure auto-hébergé. Voir n8n self-hosted 2026 : guide complet.
  • Une clé API Anthropic (Claude Sonnet 4.6 recommandé pour l’extraction structurée) ou OpenAI (GPT-5).
  • Un Google Sheets ou une base Postgres contenant le catalogue produit avec colonnes : référence, libellé, prix unitaire HT, stock disponible, taux de remise volume.
  • Un compte sur un service de génération PDF — PDFShift, Pdfmonkey ou DocRaptor — ou la volonté d’utiliser une fonction Code n8n avec Puppeteer pour la génération en interne.
  • Un canal de réception (Gmail OAuth2, WhatsApp Cloud API, ou formulaire web qui poste à un Webhook).
  • Niveau attendu : intermédiaire à avancé.
  • Temps estimé : 6 à 10 heures pour la première version, plus l’itération sur le template PDF.

Étape 1 — Modéliser le catalogue produit

L’agent ne peut pas inventer les produits ; il faut une source de vérité. Pour des PME avec moins de 500 références, un Google Sheets unique convient. Au-delà, ou si plusieurs équipes éditent en parallèle, basculer sur Postgres ou Airtable.

Structure minimale du Sheets catalogue :

reference libelle prix_ht stock remise_volume_seuil remise_volume_taux
CHAISE-PLIANT-CAT Chaise pliante catering 4500 240 20 0.07
TABLE-BANQUET-240 Table banquet 2m40 35000 18 5 0.10

La colonne remise_volume_seuil indique à partir de quelle quantité la remise s’applique ; remise_volume_taux est le taux décimal (0.07 = 7 %). Cette modélisation est volontairement simple — pour des PME plus matures, la grille de remise se complexifie (par client, par marque, par période) et il faut basculer sur une table dédiée.

Vérifier que le Sheets est accessible depuis n8n via une credential Google Sheets OAuth2 active. Tester avec un nœud Google Sheets en mode Get Many qui doit retourner les lignes sans erreur. Si une erreur 403 apparaît, vérifier que les scopes incluent spreadsheets.readonly au minimum.

Étape 2 — Construire l’extraction structurée

Le cœur de l’agent devis est l’extraction. Une demande libre doit devenir une structure JSON normalisée :

{
  "client": {
    "nom": "Restaurant Chez Aïssa",
    "email": "contact@chez-aissa.example",
    "remarques": "livraison souhaitée mardi prochain"
  },
  "lignes": [
    {"reference_devinee": "CHAISE-PLIANT-CAT", "quantite": 30, "libelle_demande": "chaises pliantes type catering"},
    {"reference_devinee": "TABLE-BANQUET-240", "quantite": 5, "libelle_demande": "tables banquet 2m40"}
  ]
}

Pour cela, utiliser le nœud AI Agent ou directement un nœud Anthropic Chat Model avec Output Format = JSON et un schema JSON imposé. Approche recommandée pour la fiabilité : utiliser le pattern Tool Use natif d’Anthropic ou le mode response_format: json_schema d’OpenAI.

Configuration côté Anthropic Chat Model :

  • Model : claude-sonnet-4-6
  • Temperature : 0.1 — extraction, pas créativité
  • Output Format : JSON
  • JSON Schema : copier la structure ci-dessus en schéma JSON valide

Prompt système :

Tu reçois une demande de devis en langage libre. Tu retournes UNIQUEMENT un
objet JSON structuré sans aucun commentaire avant ou après.

Pour chaque ligne, devine la référence catalogue la plus probable parmi
celles fournies dans le contexte. Si aucune référence ne correspond
clairement, mets "INCONNU" et garde le libelle_demande pour validation
humaine.

Si la quantité n'est pas explicite, mets 1.

Si l'email ou le nom du client manque, laisse une chaîne vide.

Injecter en contexte la liste des références catalogue récupérée à l’étape 1. Pour 500 références, cela fait 5 à 10 KB de contexte — acceptable pour un appel ponctuel.

Tester avec une demande simple. La sortie doit être un JSON parsable sans étape de nettoyage. Si le modèle ajoute parfois \« jsonautour, ajouter une étapeCode` qui strip les backticks avant de poursuivre.

Étape 3 — Vérifier la disponibilité et calculer

Une fois la structure extraite, croiser chaque ligne avec le catalogue. Ajouter un nœud Loop Over Items qui itère sur les lignes, et à l’intérieur :

  1. Un nœud Google Sheets qui cherche la reference_devinee dans le catalogue.
  2. Un nœud Code qui calcule la ligne :
const ligne = $input.first().json;
const catalogue = $('Sheets-Lookup').first().json;

const reference = catalogue.reference || 'INCONNU';
const libelle = catalogue.libelle || ligne.libelle_demande;
const prix_unitaire = Number(catalogue.prix_ht) || 0;
const stock = Number(catalogue.stock) || 0;
const quantite = Number(ligne.quantite);

const seuil = Number(catalogue.remise_volume_seuil) || Infinity;
const taux = Number(catalogue.remise_volume_taux) || 0;
const remise_pct = quantite >= seuil ? taux : 0;

const sous_total = prix_unitaire * quantite;
const montant_remise = sous_total * remise_pct;
const total_ligne = sous_total - montant_remise;

return [{
  json: {
    reference,
    libelle,
    quantite,
    prix_unitaire,
    remise_pct,
    total_ligne,
    stock_disponible: stock,
    rupture: quantite > stock,
  }
}];

Cette logique est volontairement déterministe — c’est du calcul, pas de la décision. Le modèle ne doit pas calculer les totaux ; il commet trop d’erreurs arithmétiques. La règle est simple : extraction et compréhension côté modèle, calcul côté code.

Après la boucle, agréger en un total devis avec un nœud Aggregate puis un Code qui calcule TVA et total TTC. La TVA varie selon le pays — au Sénégal, elle est à 18 % standard ; en France à 20 %. Stocker le taux dans une variable d’environnement n8n plutôt qu’en dur dans le code.

Étape 4 — Gérer le seuil de validation humaine

Toute action qui engage l’entreprise doit avoir un garde-fou. La règle : au-dessus d’un montant configurable, le devis n’est pas envoyé directement au client mais à un humain qui valide.

Ajouter un nœud IF après le calcul du total : si total_ttc > 500000 (500 000 FCFA, équivalent ~760 EUR), brancher vers un workflow d’attente avec notification au commercial. Sinon, continuer vers la génération PDF.

Pour l’attente humaine, deux patterns. Le plus simple : envoyer un e-mail au commercial avec deux liens (approuver et modifier), dont l’approbation déclenche un webhook qui poursuit le workflow. Le plus robuste : le nœud Wait de n8n avec mode On Webhook permet de figer l’exécution jusqu’à réception d’un signal. Le commercial valide ou modifie via une interface (un Sheets, un Trello, ou un mini-formulaire web), et le workflow reprend.

Cette validation humaine est aussi le moment d’auditer périodiquement la qualité des extractions. Si le commercial corrige systématiquement les mêmes types d’erreurs, c’est un signal pour ajuster le prompt système ou enrichir les données catalogue.

Étape 5 — Générer le PDF de devis

Trois approches selon le budget et l’autonomie souhaités.

Approche service externe — PDFShift. Préparer un template HTML avec des placeholders Mustache ou des variables JavaScript. Poster à https://api.pdfshift.io/v3/convert/pdf avec le HTML et récupérer le binaire PDF en réponse. Coût : forfait gratuit jusqu’à 50 conversions par mois, puis quelques euros mensuels. Avantage : zéro maintenance.

Approche Puppeteer dans n8n. Installer un nœud communautaire de génération PDF via Puppeteer ou utiliser un microservice Docker dédié. Plus complexe à mettre en place mais zéro coût récurrent et données qui ne sortent pas. Pour des volumes au-delà de 200 devis par mois, c’est rentable.

Approche template HTML + html2pdf côté client. Si le devis est consulté via un lien web, générer le HTML directement dans n8n avec un nœud Code qui assemble le template, et laisser le client télécharger en PDF côté navigateur. Plus simple mais expérience moins professionnelle.

Pour la version exposée ici, on prend l’approche PDFShift. Configuration du nœud HTTP Request :

  • Méthode : POST
  • URL : https://api.pdfshift.io/v3/convert/pdf
  • Authentication : Basic Auth avec la clé API en username, vide en password
  • Body JSON :
{
  "source": "{{ $json.html_devis }}",
  "format": "A4",
  "margin": "1cm",
  "css": "body { font-family: 'Open Sans', sans-serif; }"
}
  • Response Format : File

La sortie est un binaire qu’on attache au mail dans l’étape suivante. Un template HTML minimal et propre fait dix à quinze KB ; il inclut le logo, les coordonnées de l’entreprise, la table des lignes, les totaux HT/TVA/TTC, les conditions de paiement, et la mention légale obligatoire (numéro de TVA ou équivalent local, mention « ne vaut pas facture »).

Étape 6 — Envoyer le devis et tracer

Le PDF généré, deux destinations : le client et le pipeline commercial.

Côté client, ajouter un nœud Send Email (Gmail OAuth2, Brevo SMTP, ou Mailjet) avec en pièce jointe le binaire PDF de l’étape précédente. Le corps du mail est court et personnalisé via les champs extraits :

Bonjour [client.nom],

Voici votre devis numéro [devis.numero] valable 15 jours. Total : [devis.total_ttc] FCFA TTC.

Pour valider, répondez à ce message ou rappelez-nous au [tel].

Toujours utiliser le nom réel récupéré depuis l’extraction, jamais inventer. Si le nom n’est pas extrait, utiliser une formule neutre comme « Bonjour ».

Côté pipeline, ajouter un nœud Google Sheets ou Postgres qui inscrit une nouvelle ligne dans la table devis avec les champs : numero, date, client, total_ttc, statut (envoye), reference_workflow_run. Ce dernier champ — l’identifiant d’exécution n8n — permet de retrouver les détails d’un devis problématique en remontant aux logs.

Numéroter les devis avec un schéma stable et lisible : D-202605-0042 pour le 42e devis de mai 2026. Implémenter avec un compteur Postgres SERIAL ou un nœud Code qui combine date et identifiant d’exécution.

Étape 7 — Suivi automatique des devis envoyés

Un devis envoyé sans suivi reste dans la boîte mail du client et tombe dans l’oubli. Mettre en place un workflow planifié quotidien qui parcourt les devis non encore validés et déclenche la bonne action.

Logique : pour chaque devis dans la table avec statut = 'envoye' et date_envoi < J-3, envoyer un e-mail de relance polie ; pour date_envoi < J-10, marquer comme expire et notifier le commercial pour décision (relance personnalisée ou abandon).

Côté n8n, créer un workflow distinct avec un Schedule Trigger sur cron 0 9 * * * (tous les jours à 9 h) qui exécute la logique. Garder ce workflow simple et idempotent — il ne doit pas envoyer deux relances le même jour si on le déclenche manuellement par erreur. Pour cela, ajouter un champ derniere_relance_le dans la table devis et tester sa valeur.

Erreurs fréquentes

Erreur Cause Solution
Le modèle invente une référence absente du catalogue Le contexte ne contient pas la liste des références Injecter explicitement la liste, instruire « INCONNU si pas de match clair »
Les totaux PDF sont faux Calcul fait par le modèle au lieu du code Forcer le calcul côté nœud Code, jamais côté LLM
Le PDF affiche des caractères cassés sur les accents Police par défaut sans support UTF-8 complet Utiliser Open Sans, DejaVu Sans, ou Noto Sans dans le CSS du template
Doublons dans la table devis lors de redémarrages Pas de clé unique sur le numéro Contrainte UNIQUE sur la colonne numero, gérer le cas en amont
Mauvais montant TVA selon les clients Taux en dur dans le code Externaliser le taux par client ou par pays dans une table de configuration
Le commercial ne sait pas qu’un gros devis attend validation Notification mal câblée Doubler par e-mail + Slack, prévoir une relance si pas validé sous 4 heures

FAQ

Quel modèle choisir pour l’extraction structurée ?
Claude Sonnet 4.6 ou GPT-5 sont fiables pour de la sortie JSON. Les modèles plus petits (Haiku, GPT-5 mini) suffisent souvent et coûtent trois à dix fois moins. Tester sur un échantillon représentatif de 50 demandes avant de figer le choix.

Comment gérer les remises spéciales par client ?
Ajouter une feuille clients_remises avec colonnes email_client, taux_remise_supplementaire. Au moment du calcul, croiser avec l’e-mail extrait et appliquer le taux additionnel. Documenter ces remises côté commercial pour qu’il puisse les vérifier.

Doit-on rendre l’agent disponible en self-service côté client ?
Pas pour la première version. Garder l’humain en boucle au moins le temps de lever les bugs d’extraction et de calcul. Le passage en self-service exige une fiabilité d’extraction supérieure à 99 % sur les demandes standard, ce qui demande typiquement deux à trois mois d’observation et d’ajustement.

Comment intégrer ce flux à un CRM existant comme HubSpot ou Pipedrive ?
Remplacer la trace dans Sheets/Postgres par un appel API au CRM via le nœud HubSpot ou Pipedrive de n8n. Mapper les champs de manière à ce que le devis génère une opportunité ou un deal correctement valorisé.

Tutoriels associés

Ressources

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité