Aller au contenu
Blog / Tech

Un LLM pour lire des demandes de transport : retour d'expérience Mistral × Infomaniak

Chez notre client, un parser à regex lisait mal les remarques libres des demandes de transport (fauteuil, aide à la marche) et se cassait dès qu’une colonne changeait. On l’a complété par Mistral hébergé chez Infomaniak. Le LLM ne décide jamais seul : il peut seulement ajouter des besoins, ses réponses sont validées en code, et on retombe sur les regex en cas d’échec. Résultat : 48 corrections justifiées sur un mois, pour environ 0,15 CHF grâce au cache. Leçons principales : un simple dictionnaire d’en-têtes a suffi pour les colonnes, et seul un garde-fou en code a stoppé les hallucinations.

Un LLM pour lire des demandes de transport : retour d'expérience Mistral × Infomaniak

Le contexte

Chez notre client, les demandes de transport arrivent par e-mail, sous forme de fichiers XLSX exportés par plusieurs partenaires. Un service lit ces fichiers, en extrait les courses et les envoie dans l'outil de planification. Jusqu'ici, tout reposait sur des regex et des numéros de colonnes codés en dur.

Cette approche posait deux problèmes :

  • Les remarques libres. C'est dans ces remarques qu'on apprend que la personne se déplace en fauteuil roulant ou avec un déambulateur, ou qu'elle doit être accompagnée. Les regex fonctionnent bien sur les formulations connues, mal sur les autres.
  • Les fichiers qui changent. Quand un partenaire ajoute une colonne à son export, tous les index se décalent et le parser lit la mauvaise colonne sans le signaler.

Pourquoi Mistral chez Infomaniak

Les remarques contiennent des données de santé. Même anonymisé, elles restent des données personnelles au sens de la nLPD. Les envoyer à un fournisseur américain n'était donc pas une option. Infomaniak héberge ses modèles en Suisse, n'entraîne rien sur les données qu'on lui envoie et ces dernières ne quittent jamais le territoire.

Côté modèle, nous utilisons mistral3b, avec mistral9b en repli.

Le principe : le LLM ne décide jamais seul

Cette règle a guidé tout le reste. On n'appelle le LLM que là où une approche déterministe ne suffit pas. Chaque réponse est validée en code, et chaque étape a une solution de repli.

  • Les regex passent en premier. Le LLM complète leur résultat, il ne le remplace pas.
  • La fusion est asymétrique. Pour un booléen (« besoin d'un fauteuil »), le LLM peut ajouter un besoin, jamais en retirer un. Pour un compteur (bagages, accompagnants), il peut seulement augmenter la valeur. La raison est simple : un fauteuil signalé à tort coûte un véhicule surdimensionné, alors qu'un fauteuil oublié laisse une personne sur le trottoir face à un véhicule inadapté. Le LLM n'a donc d'autonomie que dans le sens de la prudence.
  • La validation du JSON est stricte (schéma zod, enums exacts). En cas de réponse invalide, de timeout ou d'erreur HTTP, on garde les valeurs des regex : le pipeline ne casse jamais.
  • Le feature flag. Si le flag est inconnu ou si le service de flags tombe, il vaut false : par défaut, aucune donnée ne part vers le LLM. C'est aussi le seul interrupteur à actionner pour tout couper sans redéployer.

Ce que ça apporte

  • Sur 42 cas annotés à la main (16 champs chacun), les regex seules atteignent 91,7 %. Le score descend à 57 % sur les aides à la marche. Le LLM obtenait 100 % sur les 16 premiers cas.
  • Sur un mois réel (1 353 lignes), le LLM a corrigé 48 valeurs que les regex avaient mal lues (type de fauteuil, aide à la marche). Après vérification manuelle, toutes étaient justifiées par le texte.
  • Le coût projeté est d'environ 0,15 CHF par mois. Le cache y contribue : les 1 353 lignes ne contiennent que 254 remarques distinctes, ce qui donne 254 appels pour tout le mois.
  • Latence : il faut environ 6 minutes pour traiter un mois complet. La quasi-totalité de ce temps vient de l'espacement imposé entre les requêtes (voir plus bas), et le traitement s'exécute en tâche de fond.

Ce qu'on n'avait pas vu venir

Le fauteuil électrique imaginaire

Sur environ 3 % des lignes, le modèle déduisait un fauteuil électrique ou XXL de la seule formule « reste dans le fauteuil roulant pendant le transport ». Deux révisions du prompt n'y ont rien changé.

La solution a été un garde-fou en code : le LLM ne peut proposer électrique ou XL que si un terme de la liste d'indices figure dans le texte. Cette liste n'est définie qu'une fois, et la consigne du prompt est générée à partir de celle là. Ce qu'on demande au modèle et ce qu'on vérifie ne peuvent donc pas diverger.

Parfois, il ne faut pas de LLM du tout

Pour la structure des fichiers XLSX, on avait prévu un LLM chargé de deviner à quoi correspond chaque colonne. Sur un mois réel (109 fichiers, 1 353 lignes), un simple dictionnaire des libellés d'en-tête résout pourtant 100 % des cas. Les index sont recalculés depuis l'en-tête de chaque fichier, si bien qu'une colonne ajoutée ne décale plus rien.

Le LLM reste prévu en seconde ligne, pour les libellés inconnus. Ce chantier a aussi fait remonter un vrai bug : dans un export à 25 colonnes, une colonne était décalée, et le parser envoyait au chauffeur comme instruction NON au lieu de la valeur d'une colonne de prix.

Les données personnelles

  • Anonymisation avant envoi : le prénom, le nom, le téléphone du bénéficiaire et autre champs sensible, connus grâce à leurs colonnes, sont remplacés (par [PRENOM], [NOM], [TEL], etc ... ).
  • Rien dans les logs : ni contenu de remarque, ni identifiant.

Ce qu'on retient

1\. Commencer par le déterministe. Un dictionnaire a résolu un problème qu'on croyait réservé au LLM.

2\. Valider en code ce que le prompt ne garantit pas. Retoucher le prompt n'a pas suffi, le garde-fou si.

3\. Mesurer les replis. Un fallback silencieux peut masquer 91 % d'échecs.

4\. Se constituer une vérité terrain tôt. Sans cas annotés, on compare le LLM aux regex, ce qui mesure surtout la faiblesse des regex.

5\. L'hébergement suisse est un vrai argument pour des données de santé, et il coûte quelques centimes par mois.