Stocke la probabilité, jamais le verdict

Une décision de conception m'a plus rapporté que toute autre dans les petits systèmes d'IA que je construis : quand un modèle prend une décision, stocke la probabilité, jamais le verdict.
Ça ressemble à un détail de base de données. C'est en réalité une décision sur qui a le droit de changer d'avis plus tard, et combien ça coûte.
Le contexte
L'exemple vient d'un side project, un avatar de live-stream qui réagit à ce qui se dit. Quand un nouveau mot apparaît dans la transcription, le système doit décider si ce mot mérite son propre sprite illustré. Générer un sprite est lent, coûte une génération d'image et est plafonné par stream, donc la décision compte.
La décision est prise par un petit modèle juge, Jev, de TypeSafe. Il n'écrit rien. Il répond à une seule question, est-ce que ce mot mérite un sprite, avec une probabilité entre 0 et 1. Un appel utilise environ 375 tokens en entrée et coûte environ 0,000016 $ au tarif public de 42 $ par milliard de tokens en entrée.
L'erreur tentante
L'implémentation évidente compare la probabilité à un seuil, disons 0,5, et met en cache le résultat : oui ou non. La prochaine fois que le mot apparaît, tu lis le cache et tu sautes l'appel.
Le problème apparaît la première fois que tu déplaces le seuil. Et tu vas le déplacer, parce que personne ne trouve le bon seuil dès le premier jour. Au moment où tu le fais, chaque verdict en cache devient faux d'une façon que tu ne peux pas voir. Disons qu'un mot a obtenu 0,48 : il a été mis en cache comme « non », et il reste « non » après que tu baisses le seuil à 0,45. Pour corriger ça, tu dois rappeler le modèle pour tout.
Un verdict lie chaque décision au seuil du moment. Une probabilité, non.
Ce que stocker la probabilité t'apporte
Je tiens un journal : une ligne par décision, avec le mot, la probabilité et le contexte. Le journal survit aux redémarrages, parce qu'un seuil doit être calibré sur tout un stream, pas sur les trois derniers mots.
Par-dessus, il y a un curseur. Tu le déplaces, et l'interface met en évidence chaque mot qui basculerait, et dans quel sens, si le seuil se trouvait là. Aucun modèle n'est appelé. Quand la coupure a l'air bonne, un bouton écrit le nouveau seuil dans la configuration en direct, sans redémarrage.
Calibrer le seuil d'un modèle est passé de « tout rejouer et espérer » à une courte revue de décisions réelles.
Le cache s'est aussi révélé plus utile que prévu. Sur huit sessions réelles, il y a eu 16 098 mots candidats, dont seulement 2 127 distincts. Ça fait 87 % de répétition. Chaque appel est presque gratuit, mais 87 % d'entre eux n'ont jamais besoin d'avoir lieu, et ceux qui n'ont pas lieu n'ajoutent pas non plus de latence à un stream en direct.
Deux leçons connexes
La première, c'est que la confiance d'un modèle n'est pas sa justesse. La couche speech-to-text qui alimentait ce système bouclait parfois sur du silence et produisait la même phrase encore et encore. Ces transcriptions en boucle portaient une confiance médiane plus élevée que la parole normale, 0,83 contre 0,74. Le modèle était le plus sûr de lui exactement quand il était bloqué. Un seuil de confiance ne peut pas détecter cette panne ; seule la forme de la transcription le peut.
La seconde porte sur les modes de défaillance. Si le juge est injoignable, que doit faire le filtre ? Ma première réponse a été de laisser passer les mots par défaut (fail open), pour que le stream reste vivant. Mais chaque mot laissé passer déclenche une génération d'image qu'on ne peut pas annuler et qui entame un plafond de 50 par stream. Donc le filtre laisse passer pendant un moment, et après huit défaillances consécutives, il s'inverse et se met à rejeter, avec une erreur bruyante dans le journal. Le bon repli dépend de quelle erreur est réversible.
Au-delà d'un side project
La même règle s'applique partout où un modèle prend une décision oui/non à l'intérieur d'un logiciel : modération de contenu, scoring de leads, routage de tickets, signalement de fraude, filtres de pertinence en retrieval. Garde le nombre. Décide au moment de la lecture. Journalise le seuil en vigueur séparément, pour pouvoir expliquer une ancienne décision sans la confondre avec la règle actuelle.
Ça coûte quelques octets par décision. En échange, chaque futur changement d'avis est gratuit.
Sources
- Documentation TypeSafe, « Models » (tarification de Jev)
- Documentation TypeSafe, « Noul » (la primitive de probabilité)
- Mes propres mesures sur le projet, septembre 2026
