TL;DR : la plupart des tâches « IA » en production ne sont pas des tâches pour un LLM. Pour trier mes mails, j'ai remplacé un modèle de 7 milliards de paramètres par un petit classifieur en Go. La règle tient en une phrase. Les règles d'abord, un petit modèle ensuite, le LLM en dernier recours seulement. Résultat : plus de GPU, une inférence sous la milliseconde, un appel au cloud devenu rare. Voici comment, avec les vrais chiffres.
Cet article s'adresse aux devs qui mettent un LLM en production et qui paient la facture. Pas une démo.
La plupart des tâches « IA » ne sont pas des tâches pour un LLM
En 2026, le réflexe par défaut, c'est de brancher un gros modèle sur tout. Une question arrive, on appelle le LLM. Mais beaucoup de tâches n'ont pas besoin de lui. Ranger un mail dans « travail » ou « newsletter », c'est de la classification. Un problème résolu depuis vingt ans, bien avant les LLM.
Classer, c'est choisir une étiquette dans une courte liste stable. Générer du texte, c'est autre chose. Le premier travail se fait avec un petit modèle. Le second mérite un gros modèle. La règle que je défends tient en une phrase. Mets le LLM en dernier.
Le contexte
J'ai construit un agent qui trie ma boîte mail. C'est un daemon. Il lit les nouveaux messages et range chacun dans une catégorie : travail, notification, newsletter, promo, et quelques autres. Rien de secret, juste ma vraie boîte, avec des années de courrier.
La première version confiait chaque mail à un LLM local. Un modèle de 7 milliards de paramètres, Qwen 2.5 7B, servi par Ollama sur un GPU. Ollama, c'est un outil qui fait tourner un LLM sur ta machine. Ça marchait. Mais le prix était lourd. Un GPU allumé en permanence. Un conteneur de plus à surveiller. Et une lenteur absurde pour la question posée.
Un jour je me suis demandé : décider « est-ce une newsletter ? » réclame-t-il vraiment 7 milliards de paramètres ? Non. La réponse tient dans deux ou trois mots de l'expéditeur et du sujet. J'ai donc tout repensé.
Trois couches, de la moins chère à la plus chère
Chaque mail traverse trois couches, dans l'ordre. Il s'arrête à la première qui sait répondre.
- Des règles déterministes. Instantanées, exactes, sans coût.
- Un petit modèle. Sous la milliseconde, sur CPU.
- Le LLM. Seulement si le petit modèle doute.
Le code de routage tient en quelques lignes. Il dit toute l'histoire.
func (c *Classifier) decide(m Message) Decision {
// 1. Regles deterministes : expediteurs evidents, tranches d'entree.
if d := preClassify(m); d != nil {
return *d
}
// 2. Petit modele ML : sous la milliseconde, sur CPU.
pred := c.ml.Predict(m)
if pred.Confidence >= c.threshold {
return decisionFrom(pred)
}
// 3. LLM en dernier : seule la queue incertaine part vers le cloud.
return c.classifyWithLLM(m)
}
La logique est simple. Chaque couche coûte plus cher que la précédente. Donc chaque couche ne traite que ce que les autres n'ont pas su trancher.
Les règles écrèment le facile
Une bonne partie de mon courrier est évidente rien qu'à l'expéditeur. Une adresse de newsletter connue est toujours une newsletter. Une alerte de plateforme est toujours une notification. Aucune intelligence requise.
Une courte liste de règles décide ces cas d'un coup. Elle regarde le domaine de l'expéditeur et pose l'étiquette. Zéro devinette, zéro appel modèle, zéro coût. Ces mails n'atteignent jamais le petit modèle, encore moins le LLM.
Pourquoi ne pas laisser le modèle le faire ? Parce qu'une règle que tu lis vaut mieux qu'une prédiction que tu subis, quand la réponse est évidente. Une règle est stable, testable, et gratuite. Tu la gardes pour tout ce qui est certain.
Un petit modèle pour le milieu ambigu
Pour le reste, celui qui n'est pas évident, j'utilise deux vieilles techniques. Le TF-IDF et la régression logistique.
Le TF-IDF transforme un texte en chiffres. Chaque mot reçoit un poids selon sa fréquence, rare ou banal. La régression logistique est un modèle simple. Il apprend à séparer les catégories à partir de ces chiffres. Ensemble, ils font un classifieur de texte solide et léger.
Je l'entraîne en Python, avec scikit-learn, sur près de 5 800 mails étiquetés, répartis en 6 catégories. Puis je l'exporte dans un simple fichier JSON. Ce fichier pèse 2,4 Mo. À comparer au modèle de 7B, plusieurs gigaoctets et un GPU.
Le point clé : l'inférence est 100 % en Go. Pas de Python, pas de GPU, pas de dépendance C. Le code Go lit le JSON et prédit en bien moins d'une milliseconde, sur un simple CPU. Le tout tient dans mon image de production, une image distroless sans shell ni outils système.
Et la précision ? En validation croisée 5-fold, le modèle atteint 81 % de bonnes réponses. La validation croisée découpe les données en 5 paquets. On entraîne sur 4, on teste sur le 5e, à tour de rôle. C'est une mesure honnête, faite sur des mails jamais vus à l'entraînement. Le modèle est bon sur les catégories fréquentes et nettes, autour de 0,88. Il est plus faible sur les rares et floues, autour de 0,62.
Enfin, le modèle sort une confiance, entre 0 et 1. Au-dessus de 0,60, je lui fais confiance. En dessous, le mail passe à la couche suivante.
81 %, et alors ?
Tout seul, 81 % semble médiocre. Ce n'en est pas un, à cause de la cascade. Aucune couche ne doit être parfaite. Chaque couche doit juste faire ce qu'elle sait faire.
Les règles tranchent le facile, sans erreur. Le petit modèle décide le gros du milieu, avec confiance. Le LLM ne voit que la queue, les mails vraiment ambigus. L'appel cher devient rare. C'est tout l'intérêt.
Tu ne cherches pas un modèle parfait. Tu cherches un système où le coût suit la difficulté. Un mail évident coûte zéro. Un mail dur coûte un appel au cloud. Et il y a peu de mails durs.
Le piège de la parité des tokens
Il y a une partie délicate. Le modèle s'entraîne en Python, mais tourne en Go. La façon de découper le texte en tokens doit être identique des deux côtés. Byte pour byte.
Un token est un morceau de texte que le modèle compte, en général un mot. Si le découpage Go diffère du découpage Python, même un peu, le modèle voit des tokens qu'il n'a jamais appris. La précision chute en silence. Aucune erreur, juste des prédictions plus mauvaises.
Ma parade : un tokeniseur écrit à la main, dupliqué à l'identique dans les deux langages. Même expression régulière, même table pour retirer les accents, aucune bibliothèque unicode externe. Une table explicite, la même en Python et en Go.
// La meme table vit dans l'entraineur Python.
// Un seul ecart et le modele voit des tokens jamais appris.
var fold = map[rune]string{'é': "e", 'è': "e", 'ç': "c" /* table complete */}
var wordRE = regexp.MustCompile(`[a-z0-9]{2,}`)
func wordTokens(s string) []string {
var b strings.Builder
for _, r := range strings.ToLower(s) {
if rep, ok := fold[r]; ok {
b.WriteString(rep)
} else {
b.WriteRune(r)
}
}
return wordRE.FindAllString(b.String(), -1)
}
Un test de parité compare les deux tokeniseurs sur du vrai texte. S'ils divergent, le build casse. Mieux vaut un build rouge qu'une précision qui fond sans prévenir.
La régression silencieuse qui m'a appris un garde-fou
Le modèle s'améliore avec une boucle simple. Quand l'agent range un mail au mauvais endroit, je le déplace dans le bon dossier. Ce déplacement devient une nouvelle étiquette. Je réentraîne, et le modèle apprend de ma correction.
Un jour, cette boucle m'a mordu. J'avais retrié mes mails à la main. Une catégorie est tombée sous le minimum d'exemples nécessaire pour l'apprendre. Le réentraînement l'a supprimée en silence. Le nouveau modèle ne savait plus la prédire. Toujours aucune erreur affichée.
La correction : le script d'entraînement refuse maintenant de laisser tomber une catégorie dont je dépends. Il s'arrête et me dit quel dossier s'est vidé.
# Le reentrainement refuse de perdre une categorie en silence.
python train.py --in labeled.jsonl --out model.json \
--expect-labels "travail,notification,newsletter,promo,maison"
# ABORT: expected classes would be DROPPED from the model: [personnel]
La leçon dépasse le mail. Une régression silencieuse est pire qu'un crash. Un crash, tu le vois. Une précision qui baisse en douce, tu la découvres trop tard. Alors tu la rends bruyante, exprès.
Quand le LLM mérite sa place
Je n'ai pas supprimé le LLM. Je l'ai déplacé là où il gagne son coût. L'écriture.
Classer un mail est un problème fermé, avec peu de réponses. Écrire une réponse humaine est un problème ouvert. Et le langage libre, c'est exactement ce qu'un gros modèle fait mieux que tout. Alors quand l'agent doit rédiger un vrai message, un modèle cloud s'en charge.
Petit modèle pour la tâche fermée. Gros modèle pour la tâche ouverte. Le bon outil à chaque étage. Voilà la vraie leçon, pas « les LLM, c'est nul ». Les LLM sont excellents. Juste pas pour tout.
La checklist avant de coller un LLM sur une tâche
Avant d'appeler un gros modèle par réflexe, passe la tâche à ces questions.
- La tâche a-t-elle un petit nombre de réponses stables ? Alors c'est de la classification, pas un LLM
- Peux-tu écrire des règles pour les cas évidents ? Fais-les d'abord, elles sont gratuites
- As-tu des exemples étiquetés ? Un petit modèle suffit sans doute
- Le modèle a-t-il besoin de comprendre du langage libre, ou juste de choisir une étiquette ?
- Peux-tu mesurer une précision honnête, en validation croisée ?
- Le petit modèle tourne-t-il sans GPU, sur CPU, dans ton image de prod ?
- Ton tokeniseur est-il identique à l'entraînement et à l'inférence, byte pour byte ?
- Un garde-fou empêche-t-il une régression silencieuse au réentraînement ?
- Gardes-tu le LLM pour ce qu'il fait vraiment mieux, générer du langage ?
Ce qu'il faut retenir
Le réflexe de 2026, c'est le gros modèle pour tout. Souvent, la tâche n'en a pas besoin. Une cascade coûte bien moins cher et tourne bien plus vite. Des règles pour l'évident. Un petit modèle pour le milieu. Le LLM pour la seule vraie difficulté.
Ce n'est pas un rejet de l'IA. C'est de l'ingénierie. Tu fais correspondre le coût à la difficulté, couche par couche. Tu construis un système IA et la facture grimpe ? Tu veux savoir où un petit modèle remplacerait ton LLM ? C'est exactement ce que je fais. Écris-moi. Garde le gros modèle pour ce qui le mérite.