La mise à jour de modèle qui a discrètement cassé la production
Par Ulrich Dohou, Software Engineer
Un mardi matin de mars, notre pipeline d’extraction de données structurées a cessé de fonctionner. Pas d’erreur. Pas de timeout. Les requêtes partaient, les réponses revenaient, et le parseur JSON en aval crashait sur 40 % d’entre elles. Le diff de déploiement de la veille ? Zéro ligne changée.
Le coupable : notre fournisseur avait fait un « upgrade transparent » du modèle pendant la nuit. Même nom d’API, même endpoint, même facture. Mais le nouveau modèle avait une légère tendance à ajouter un commentaire en markdown avant le JSON demandé, Here's the JSON: suivi d’un bloc de code. Notre parseur attendait du JSON brut dès le premier caractère.
Pourquoi ça arrive plus souvent qu’on croit
Les fournisseurs de modèles font des mises à jour en continu. OpenAI déprécie et remplace des modèles régulièrement, parfois avec un préavis court. Anthropic versionne ses modèles plus explicitement, mais les versions datées finissent par être retirées. Le problème n’est pas le changement, c’est l’hypothèse implicite que la sortie d’un modèle est stable.
Elle ne l’est pas. Un modèle de langage n’est pas une fonction pure. Même avec temperature: 0, la distribution des sorties peut changer entre versions pour la même entrée. Et quand votre système dépend de la forme de la sortie (pas seulement de son sens), tout changement subtil devient une régression silencieuse.
Les trois modes de casse
1. Changement de format
Le plus courant. Le modèle ajoute des backticks autour du JSON, change l’indentation, utilise des guillemets simples au lieu de doubles, ou ajoute un préambule textuel. Votre parseur strict casse. Votre parseur permissif accepte des données corrompues.
2. Changement de vocabulaire
Le modèle commence à utiliser des mots différents pour les mêmes concepts. Là où il disait "status": "pending", il dit maintenant "status": "en attente" ou "status": "waiting". Si votre code fait un switch sur ces valeurs, les cas non reconnus tombent dans le default, souvent silencieusement.
3. Changement de comportement aux limites
Le modèle devient plus ou moins conservateur. Il refuse des requêtes qu’il acceptait avant. Il ajoute des caveats là où il n’en mettait pas. Il interprète les instructions ambiguës différemment. C’est le plus difficile à détecter parce que chaque réponse individuelle semble raisonnable, c’est la distribution qui a bougé.
La défense : les évals comme filet
La seule défense fiable contre les régressions silencieuses est un jeu d’évaluations qui tourne en continu. Pas une suite de tests au sens classique, les sorties de LLM ne sont pas déterministes, mais un ensemble d’assertions sur la forme et les propriétés de la sortie :
// Assertions structurelles — pas sur le contenu exact
assert(isValidJSON(response));
assert(response.keys().includes("status"));
assert(VALID_STATUSES.includes(response.status));
assert(response.summary.length < 500);
assert(!response.summary.startsWith("```"));
Ces assertions ne vous disent pas si le modèle a « raison ». Elles vous disent si le contrat entre le modèle et votre code est respecté. C’est suffisant pour attraper 80 % des casses silencieuses.
Le protocole de mise à niveau
Voici la procédure que j’applique maintenant à chaque changement de modèle, qu’il soit initié par moi ou par le fournisseur :
1. Versionner explicitement
Ne pointez jamais sur gpt-4 ou claude-sonnet sans suffixe de date. Pointez sur gpt-4-0613 ou claude-sonnet-4-20250514. Quand vous êtes prêt à upgrader, c’est une décision explicite avec un diff dans le code.
2. Shadow mode
Avant de basculer, faites tourner le nouveau modèle en parallèle pendant 48h. Envoyez les mêmes requêtes aux deux versions. Comparez les sorties, pas pour l’exactitude (subjective), mais pour la conformité structurelle. Si le taux de divergence dépasse 5 %, investiguez avant de basculer.
3. Parseur défensif
Votre parseur devrait gérer les variations connues : JSON avec ou sans backticks, avec ou sans préambule, avec ou sans trailing comma. Pas en acceptant n’importe quoi, en normalisant les formats connus vers un format canonique, et en rejetant explicitement tout le reste.
4. Monitoring du contrat
Après le basculement, surveillez le taux de succès du parseur, pas juste le taux de succès de l’API. L’API renvoie 200 même quand la réponse est inutilisable pour votre cas. Votre métrique devrait être : « pourcentage de réponses que mon système a pu utiliser de bout en bout ».
Le vrai problème
Le vrai problème n’est pas technique, c’est organisationnel. Les équipes traitent le choix de modèle comme une décision d’infrastructure (choisie une fois, jamais revue) plutôt que comme une dépendance vivante qui évolue indépendamment de votre code.
Un modèle, c’est un service tiers dont l’API est instable par nature. Traitez-le avec la même méfiance que vous traiteriez une API externe sans SLA sur la forme des réponses. Parce que c’est exactement ce que c’est.
Si cela vous a parlé, vous aimerez Votre logique de retry vous ment et Fallbacks, timeouts et l’art de se dégrader gracieusement. Abonnez-vous ci-dessous pour recevoir le billet de vendredi prochain.
Abonnez-vous pour recevoir l'article de vendredi prochain ci-dessous.
Un e-mail · le vendredi · désabonnement à tout moment