Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
07 avril 2026

Un agent IA ne casse pas sur le prompt. Il casse sur une mise à jour.

Les agents IA tombent rarement à cause d’un prompt. Beaucoup plus souvent à cause d’un SDK qui change ses packages, ses artefacts ou sa sérialisation.

11 min de lecture
10 vues
réactions
Partager :
Un agent IA ne casse pas sur le prompt. Il casse sur une mise à jour.

On parle beaucoup de prompts, de RAG, de « quel modèle choisir ». Dans la vraie vie, les incidents que je vois sur les features IA viennent d’un truc beaucoup plus bête et beaucoup plus violent : le versioning. Une mise à jour de SDK, un rename de packages, une migration de sérialisation, et ton agent se met à halluciner côté infra, pas côté texte.

Spring AI 2.0 m’a remis ça dans la figure avec une série de breaking changes très concrets. Et c’est parfait comme prétexte pour poser une méthode réutilisable, que tu sois en Java, TypeScript ou Python : comment repérer les breaking changes avant de les merger, comment rendre l’upgrade « mécanique », quels tests ont vraiment de la valeur pour des agents, et quoi faire quand ça casse quand même en prod.

Spring AI 2.0 : le cas d’école du « ça compile plus »… et du « ça marche plus »

Sur la release 2.0.0-M3 de Spring AI, il y a plusieurs changements typiques de ce qui casse une feature IA sans prévenir. D’abord, des packages MCP ont été renommés côté annotations. Ça paraît anodin, mais dans une base de code réelle, ça veut dire une rafale d’imports à corriger, des IDE qui te montrent tout en rouge, et parfois des annotations qui ne sont plus détectées si le comportement a bougé avec le rename.

Ensuite, des artefacts MCP « transport » ont été déplacés. Là, tu peux avoir un build qui compile encore… mais un classpath qui n’a plus la même tête, donc un runtime qui se met à échouer au démarrage, ou pire, qui démarre mais n’a plus le transport attendu (hello bug « silencieux »).

Le gros morceau, c’est la migration Jackson 2 → Jackson 3. Toute personne qui a déjà touché à de la sérialisation sait que ce n’est pas un détail. Sur des agents, c’est encore plus sensible, parce que la sérialisation touche directement des points critiques : les schémas des tools, le format des appels, la structure des erreurs, les champs optionnels qui deviennent absents au lieu de null, etc. Et quand ça pète, ça ressemble rarement à « Jackson a changé ». Ça ressemble à « le modèle n’appelle plus les tools » ou « les retries s’emballent ».

Dernier exemple parlant : la disparition de l’historique de conversation dans un contexte de tool (dans Spring AI, via ToolContext). Ça, c’est le genre de changement qui ne crashe pas forcément ton app. Il dégrade ton agent. Tu te retrouves avec des comportements bizarres, des décisions moins cohérentes, des prompts qui semblent « moins bons », alors qu’en fait tu as perdu une pièce de contexte en cours de route.

Pourquoi le versioning est encore plus piégeux sur un agent IA

Un agent, ce n’est pas « juste » un appel HTTP vers un modèle. C’est une boucle avec des tool calls, des schémas, de la mémoire, parfois du streaming, des timeouts, des retries, des garde-fous. Donc dès qu’un SDK bouge, tu as plusieurs surfaces de casse en même temps.

Le piège numéro un, c’est la casse qui ne ressemble pas à une casse. Un test unitaire basique peut passer, ton endpoint répond 200, et pourtant l’agent n’appelle plus la bonne tool, ou parse mal une réponse structurée, ou perd une partie d’historique. Résultat : tu crois à un problème de prompt, tu « tweaks » pendant deux heures, et en fait tu viens de t’auto-infliger une régression de version.

Le piège numéro deux, c’est la dépendance indirecte. Tu mets à jour « le SDK IA », mais ce que tu bouges vraiment c’est une stack de sérialisation, de HTTP clients, de libs de streaming, de contraintes de versions. C’est exactement le genre de régression qui n’apparaît qu’en conditions réelles : payload un peu gros, latence, tool qui répond lentement, réponse partielle en streaming, etc.

Détecter les breaking changes avant le merge : release notes, mais surtout diff de dépendances

Lire les release notes, c’est bien. Mais dans un projet, la réalité c’est que tu ne lis pas tout, et que tu ne mesures pas l’impact. Mon réflexe, c’est d’ajouter une étape systématique : quand une PR touche aux dépendances IA (SDK, MCP, libs de sérialisation), je veux un diff de l’arbre de dépendances avant/après. Pas pour faire joli. Pour repérer rapidement les gros mouvements et les upgrades « cachés ».

En Java, ça se fait très bien avec Maven ou Gradle en sortant un dependency tree versionné en artefact de CI (ou même en commit dans une branche dédiée d’upgrade). En Node, c’est encore plus simple avec un lockfile et un diff. L’objectif n’est pas de vérifier chaque ligne. L’objectif est d’attraper les sauts qui doivent déclencher une vigilance immédiate, typiquement : sérialisation, HTTP client, libs de validation JSON schema, libs de streaming, libs qui impactent la compatibilité binaire.

Et je préfère être clair : si tu fais des upgrades IA « au fil de l’eau » au milieu d’autres refactors, tu crées toi-même ta zone grise. Le jour où ça casse, tu ne sais plus si c’est le prompt, le code métier, le SDK, ou le transport. C’est le meilleur moyen de perdre du temps.

Rendre les upgrades mécaniques : verrouillage, Renovate, et des règles strictes

Le meilleur upgrade, c’est celui qui ressemble à une formalité. Pour ça, il faut accepter un truc un peu frustrant : sur la pile IA, tu dois être plus strict que sur le reste. Parce que l’impact est plus diffus, et les régressions plus difficiles à diagnostiquer.

Concrètement, je verrouille. Pas forcément tout, pas de manière dogmatique, mais au moins les blocs sensibles. En Java, ça passe par un BOM maîtrisé et des contraintes explicites quand il faut. En Node, ça passe par le lockfile pris au sérieux. Et je sépare les mouvements : une PR d’upgrade « IA », dédiée, avec un scope clair, des tests ciblés, et une capacité de rollback immédiate.

Renovate (ou Dependabot) est utile, mais seulement si tu le pilotes. Sinon, il te sort quinze PR et tu finis par cliquer « merge » à l’aveugle. Ce que je veux, c’est limiter le bruit, regrouper intelligemment, et surtout éviter les automerges sur les libs IA.

{
  "extends": ["config:base"],
  "packageRules": [
    {
      "matchPackageNames": [
        "org.springframework.ai:*",
        "com.fasterxml.jackson.core:*",
        "com.fasterxml.jackson.datatype:*"
      ],
      "groupName": "Upgrade pile IA",
      "separateMinorPatch": false,
      "automerge": false,
      "stabilityDays": 7
    }
  ]
}

L’idée n’est pas de copier-coller ça tel quel. L’idée, c’est d’exprimer une politique : « ces dépendances-là ne bougent pas sans une vraie revue, et sans un run de tests qui a du sens ». Oui, c’est plus lent. Mais c’est infiniment moins cher qu’un agent qui part en vrille sur un détail de sérialisation.

Les tests qui valent quelque chose pour un agent (tools, schémas, timeouts, retries)

La plupart des tests d’agents que je vois sont soit trop fragiles (ils dépendent des réponses exactes du modèle), soit trop gentils (ils vérifient juste qu’on a une string en sortie). Les deux sont inutiles quand tu veux sécuriser un upgrade de SDK.

Ce que tu veux, ce sont des tests qui valident des invariants techniques. Exemple simple : « quand le modèle demande l’outil X, on exécute bien l’outil X, et on sérialise la réponse au format attendu ». Là, tu t’en fiches que le modèle écrive « Bonjour » ou « Salut ». Tu vérifies la mécanique. Pareil pour la validation des schémas de tools : si ton SDK change la manière dont il génère ou interprète un JSON Schema, tu veux le voir tout de suite.

Autre point qui casse souvent avec des upgrades : les timeouts et les retries. Un changement de client HTTP, de valeurs par défaut, ou de stratégie de backoff, et tu te retrouves avec des requêtes qui pendouillent ou au contraire qui retry trop agressivement. Sur un agent, ça fait vite boule de neige, parce que tu as plusieurs appels en cascade.

Si tu dois tester avec un provider réel, fais-le mais proprement. Un petit set de tests d’intégration, isolés, qui tournent sur une branche d’upgrade, avec un budget de tokens explicite et une tolérance à l’instabilité (par exemple : tu asserts sur la structure, pas sur le texte). Et pour le reste, des mocks sérieux (WireMock/MockServer côté HTTP, ou un faux provider) qui te permettent de vérifier la sérialisation et les erreurs.

La couche d’adaptation : ton meilleur anti-casse (et ton meilleur anti-lock-in)

Le move le plus rentable, c’est de ne pas laisser ton code métier dépendre directement des objets du SDK IA. Le jour où un package est renommé, où un type change, ou où une notion disparaît (genre un historique dans un contexte), tu veux que la zone de blast soit petite.

Ce que je vise, c’est une interface « interne » stable, et des adapters par provider ou par SDK. Tu perds un peu de « vitesse » au début, parce que tu écris une couche de mapping. Mais tu gagnes un truc énorme : les upgrades deviennent localisées. Et si un provider ou un SDK te fait une crasse, tu peux pin, basculer sur un fallback, ou même revenir à un mode dégradé sans réécrire l’app.

export type ToolCall = {
  name: string;
  arguments: unknown;
};

export type AgentStep =
  | { type: "message"; content: string }
  | { type: "tool_call"; call: ToolCall };

export interface AgentRuntime {
  run(input: { userMessage: string; context?: Record<string, unknown> }): Promise<AgentStep[]>;
}

Ce genre d’interface te force à décider ce qui est « ton contrat » à toi. Et c’est exactement ce qui te protège quand un SDK décide de renommer MCP, de déplacer un artefact, ou de changer un format de sérialisation.

Quand ça casse quand même : pin, rollback, et un runbook qui assume le mode dégradé

Tu peux être carré, tu peux avoir des tests, tu peux avoir Renovate bien réglé. Un jour, ça cassera quand même. Parce que les providers changent, parce que des comportements non documentés bougent, parce que tu as un edge case que personne n’avait. Donc il te faut un plan « en dessous » : le runbook.

Pour moi, un runbook minimal sur une feature IA doit répondre à des questions très concrètes. Est-ce que je peux repinner la version en une commit et redéployer vite ? Est-ce que je sais désactiver temporairement une tool ou repasser en mode « chat simple » ? Est-ce que j’ai un message UX acceptable quand l’agent n’est pas dispo, au lieu de laisser une erreur opaque ? Est-ce que j’ai des métriques qui me disent « tool call rate » et « error rate », pas juste des logs noyés ?

Et oui, parfois la bonne décision c’est de rollback et de perdre une amélioration. Un agent qui « marche à moitié » en prod, c’est un piège : tu vas cramer du temps support, dégrader la confiance utilisateur, et tu vas finir par patcher dans l’urgence. Alors qu’un rollback propre, ça laisse du temps pour comprendre. C’est moins héroïque. C’est beaucoup plus pro.

Conclusion : si tu veux des agents fiables, traite les upgrades comme une feature

Le meilleur apprentissage de ce genre de breaking changes, ce n’est pas « il faut lire les release notes ». C’est « un upgrade de SDK IA est une petite migration ». Tu le scopes, tu le testes, tu le livres avec des garde-fous, et tu prépares le retour arrière.

Le jour où tu fais ça, les agents arrêtent d’être des jouets fragiles. Ils deviennent un morceau de produit maintenable. Et c’est là que tu peux te permettre d’itérer sur ce qui compte vraiment, le comportement, l’UX et la valeur, au lieu de te battre contre une mise à jour qui t’a renversé la table.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !