Une liste de contrôle de prêt à la production pour les agents IA, étape par étape.
Une liste de contrôle en quatre étapes pour passer d’un prototype local à un agent IA en production, avec la raison derrière chaque élément et un exemple concret pour chaque vérification.
Sources: Shipping AI Agents: A Production Readiness Checklist
Passer d’un prototype fonctionnel à un agent d’IA en environnement de production suit une trajectoire prévisible, et chaque étape comporte ses propres vérifications ainsi que ses propres risques en cas de saut. Cette présentation suit ces étapes une par une.
Un prototype a une seule fonction : démontrer que l’idée fonctionne : une seule machine, l’état stocké en mémoire, sans utilisateurs réels. La production exige davantage : il doit résister aux redémarrages, servir plusieurs personnes en même temps, se remettre en marche en cas d’échec à mi-parcours, et laisser un historique permettant de diagnostiquer les problèmes.
Peu de ces défis concernent l’IA elle-même, car l’appel au modèle est généralement la partie la plus simple. Le travail le plus difficile concerne ce qui entoure ce processus : où est stocké l’état, que se passe-t-il lorsque une étape est exécutée deux fois, et comment un changement parvient à un utilisateur réel sans causer de dommages. Utilisez les listes ci-dessous pour savoir ce qui est déjà pris en compte et ce qui ne l’est pas avant que l’agent ne soit mis en production.
Étape 1 : développement et prototype local
L’objectif ici est délibérément restreint : il s’agit simplement de prouver que l’idée de base fonctionne. Ne le transformez pas encore en une solution adaptée à la production. Le faire trop tôt signifie généralement construire l’infrastructure inadaptée pour un design qui n’a pas encore été finalisé.
Faites fonctionner le flux de travail de base au moins une fois, de la manière la plus simple possible. Effectuez une seule exécution réussie, du fichier d’entrée au résultat correct, avant d’intégrer des files d’attente, des bases de données ou des mécanismes de déploiement.
Pourquoi : Les choix d’infrastructure pris trop tôt sont généralement erronés, car la véritable nature du problème est inconnue : le temps d’exécution, la taille de l’état et les modes de défaillance restent des estimations. Une version adaptée à la production construite en premier doit généralement être reconstruite de toute façon.
Exemple : Un agent de tri des tickets de support commence par un script court : il lit un ticket, demande à un modèle la catégorie correspondante et affiche la réponse. Il n’y a ni serveur ni base de données, et il est lancé manuellement depuis une terminal.
Rendre le comportement configurable plutôt que codé de manière fixe. Lorsque le système doit agir différemment pour des entrées différentes, telles que des segments de clients, des types de documents, des langues ou des tons, gérer cela via des paramètres de configuration plutôt que par l’ajout d’une nouvelle branche de code.
Pourquoi : Les variations s’accumulent rapidement dès l’apparition des utilisateurs (« Gère-t-il les tickets en français ? », « Les comptes d’entreprise peuvent-ils avoir un ton plus strict ? »). Chaque variation nécessite une modification du code et une nouvelle version, ce qui fait enfileurer de nouveaux cas derrière les ingénieurs, tandis qu’avec la configuration il s’agit simplement d’une modification rapide.
Exemple : Au lieu d’utiliser des conditions if/else selon le niveau de chaque client, les instructions de l’agent sont générées à partir de petits modèles de texte, un modèle de base et un par niveau. Ajouter un nouveau niveau signifie ajouter un fichier.
Notez ce que le prototype ne résout pas encore. Avant de passer à l’étape suivante, énumérez délibérément les lacunes : les redémarrages en cours d’exécution, plusieurs utilisateurs en même temps, l’enregistrement des erreurs, l’exécution n’importe où sauf sur votre propre machine.
Pourquoi : Une lacune non enregistrée deviendra plus tard une surprise, généralement découverte lors d’un incident et non lors d’une discussion de planification. Une lacune enregistrée devient la liste de contrôle pour la phase suivante.
Exemple : Un tableau succinct contenant des lignes telles que « les données sont stockées dans un dictionnaire Python, il faut donc un stockage fiable », « une seule tâche peut s’exécuter à la fois, il faut donc prendre en charge les exécutions simultanées », et « il n’y a aucun enregistrement de ce qui s’est passé, il faut donc un journal d’audit ». Rien n’est fixé pour l’instant ; ces éléments sont simplement nommés.
Étape 2 : environnement réduit et environnement de préparation
Ici, le système rencontre d’abord l’infrastructure réelle ainsi que les modes réels de défaillance. Il fonctionne désormais sur un ordinateur portable, est redémarré, et peut être utilisé par plusieurs personnes en même temps.
Choisissez l’environnement d’exécution adapté à la charge de travail réelle. Examinez des chiffres concrets avant de décider où l’agent sera hébergé : la durée maximale d’exécution autorisée, le coût de l’inactivité, et la possibilité pour un processus arrêté de reprendre là où il s’était arrêté.
Pourquoi : Les charges de travail des agents ressemblent rarement à des requêtes web ordinaires. Une exécution peut durer des minutes et doit gérer de nombreux éléments contextuels au cours de plusieurs étapes. Un environnement de exécution conçu pour des requêtes courtes plantera en cours d’exécution, tandis qu’un environnement conçu pour un trafic constant coûterait trop cher pour quelque chose qui s’exécute seulement quelques fois par jour.
Exemple : Un agent chargé de la révision de documents juridiques examine les sections d’un document une par une lors d’une seule exécution afin de conserver tout le contexte, et un document long peut nécessiter de 10 à 15 minutes. Cela élimine les fonctions serverless disposant d’une limite de temps stricte. Cela élimine également un ensemble de conteneurs toujours en marche, car l’agent ne s’exécute que quelques fois par jour et la capacité inutilisée resterait inexploitée. Des exécutions longues, intermittentes et peu fréquentes indiquent qu’il faut un environnement de exécution qui ne facture que le temps de traitement actif et qui ne comporte pas de délai de timeout.
Transférez l’état de la mémoire vers un stockage durable et partagé. Tout ce qui doit survivre à un redémarrage ou être visible par plusieurs serveurs, comme l’état d’exécution, l’historique des conversations et les résultats en cours, doit être stocké dans un véritable système de stockage et non dans une variable au sein du processus en cours d’exécution. Choisissez un système comme source unique de vérité.
Pourquoi : L’état conservé uniquement en mémoire disparaît lorsque le processus est redémarré, et en environnement de production cela se produit plus souvent que ce à quoi la plupart des gens s’attendent, en raison des déploiements, des pannes et de la mise à l’échelle. Lorsque l’état est dispersé dans plusieurs systèmes de stockage sans propriétaire clair, deux systèmes finissent par être en désaccord quant à ce qui est vrai.
Exemple : Ici, un agent d’édition de documents conserve l’état de l’exécution, l’historique des conversations et les modifications proposées dans un stockage d’objets, sans conserver quoi que ce soit pendant le traitement de la demande. Le stockage d’objets constitue la seule source de vérité, tandis qu’une base de données est utilisée pour une tâche très spécifique : verrouiller une demande afin d’empêcher deux travailleurs de la traiter en même temps. L’état général n’est pas réparti entre les deux, sans règle déterminant lequel l’emportera.
Supposons que « ceci ait été exécuté deux fois » et que « ceux-ci aient été exécutés simultanément ». La plupart des systèmes de messagerie réels peuvent déclencher un événement plus d’une fois. Une simple vérification d’état (« démarrer si ce n’est pas déjà fait ») échoue, car deux processus peuvent chacun lire « ce n’est pas encore démarré » avant que l’un d’eux enregistre « déjà démarré ». Il faut donc recourir à un mécanisme qui permet à un seul d’entre eux de l’emporter.
Pourquoi : Sans cela, deux processus pourraient répéter le même travail coûteux ou enregistrer des résultats contradictoires, et le bug ne se manifeste que lorsque les tâches se chevauchent réellement, ce qui est rarement le cas dans un environnement de développement calme.
Exemple : Un verrou permettant de réclamer une tâche fonctionnait dans des conditions normales, mais ne pouvait pas la libérer si le processus qui le détenait plantait ou cessait de fonctionner. En production, cela se traduisait par une pile de tâches bloquées en état « en traitement » pendant des jours, sans moyen de les récupérer. La correction a ajouté une durée de validité limitée pour le verrou, un jeton empêchant un processus rétabli d’être confondu avec le détenteur initial, ainsi qu’un nettoyage périodique permettant de récupérer toute tâche dont la durée de validité était écoulée sans qu’elle ne soit terminée. Cela est bien plus complet que la simple approche consistant à « la réclamer une seule fois ».
Intégrez le suivi et la journalisation avant même que les incidents ne les exigent. Lorsqu’il y a plusieurs services impliqués, tels qu’une couche web, une file d’attente, un processus de travail et un environnement d’exécution d’IA, une action de l’utilisateur passe par chacun d’eux. Mettez en place dès maintenant un moyen de suivre ce parcours à travers les services, afin de ne pas devoir le reconstituer en cours de panne.
Pourquoi : Reconstituer les événements à partir de journaux distincts qui ne sont pas connectés est lent et sujet aux erreurs, surtout sous pression. Un système dont le parcours peut être suivi dès le début transforme une enquête stressante en une recherche rapide.
Exemple : Le temps de exécution de l’IA d’un système disposait dès le début d’un suivi détaillé, mais le service accessible via le web qui se trouvait en amont ne l’avait pas ; par conséquent, un suivi montrait tout ce que l’agent faisait sans indiquer la requête qui l’avait déclenché, puisqu’aucun élément commun ne reliait les deux. La solution a été de créer un identifiant de exécution au moment de la requête et de le transmettre dans les journaux de chaque service, afin qu’une seule recherche puisse recueillir l’ensemble du parcours d’une exécution donnée, même si les deux systèmes de suivi restaient séparés.
Testez le client réel dès que possible. Lorsque les utilisateurs accèdent au système via un navigateur, une application ou un appareil spécifique, effectuez des tests sur cet appareil ou ce navigateur rapidement, et évitez de vous contenter d’un client API générique ou d’une approximation.
Pourquoi : Les clients réels présentent des particularités, telles que des limites de longueur, des coupes silencieuses et des encodages inhabituels, que des outils génériques ne détectent pas. Ces problèmes ne se manifestent généralement qu’avec des utilisateurs réels, au moment où ils deviennent des incidents en production et non des problèmes découverts en environnement de test.
Exemple : La fonction d’ouverture de documents d’une application de bureau a réussi tous les tests automatisés, mais a échoué de manière intermittente pour les utilisateurs d’un système d’exploitation donné. L’application tronquait silencieusement les liens de téléchargement lorsqu’ils dépassaient une certaine longueur, et les identifiants de sécurité temporaires utilisés pour signer ces liens les faisaient dépasser largement cette limite. La solution a consisté à signer les liens avec une identité distincte qui génère une signature beaucoup plus courte, à la place des identifiants temporaires utilisés par le reste du système.
Assurez-vous que le chemin réseau de la porte d’entrée correspond à la manière dont les clients se connectent. Si les utilisateurs n’ont accès au système que via un réseau privé, tel qu’un VPN, un proxy de confiance zéro ou un réseau réservé à l’interne, vérifiez que l’adresse qu’ils utilisent mène bien, du début à la fin, à un élément situé sur ce même chemin privé. Un domaine qui semble interne mais qui se résout en réalité vers un équilibreur de charge ou un CDN public peut fonctionner pour certains utilisateurs tout en posant des problèmes intermittents pour d’autres.
Pourquoi : Un tel déséquilibre entraîne des échecs inconstants : certains utilisateurs, à certains moments, ce qui le fait ressembler à une infrastructure peu fiable plutôt qu’à une mauvaise configuration. Il échappe aux contrôles car il peut passer par la machine d’un développeur, qui emprunte souvent un itinéraire réseau différent de celui d’un ordinateur portable du personnel.
Exemple : Un outil interne devait être accessible uniquement via le réseau de l’entreprise, en passant par un proxy de zéro confiance qui dirige le trafic vers différents connecteurs en fonction du choix de chaque ordinateur portable. Le domaine de cet outil semblait interne, mais il renvoyait en réalité vers un réseau public de distribution de contenu. Certains connecteurs pouvaient atteindre cette adresse publique, tandis que d’autres ne pouvaient accéder qu’aux adresses privées ; par conséquent, les pannes apparaissaient et disparaissaient en fonction du connecteur utilisé. La solution a consisté à héberger l’entrée principale sur un équilibreur de charge situé dans le réseau privé, accessible par le même itinéraire que celui emprunté par les utilisateurs réels, ainsi qu’à prévoir un chemin séparé et signé pour une application de bureau qui ne peut pas utiliser le proxy du tout.
Créez un client fictif capable de faire fonctionner l’ensemble du processus chaque fois que vous le souhaitez. Il s’agit d’un script qui effectue les mêmes demandes et actions qu’un utilisateur réel, via la même interface, afin de pouvoir reproduire des exécutions complètes sans attendre un testeur humain.
Pourquoi : Les flux d’agent ne consistent rarement en une seule demande et une seule réponse. Ils impliquent plusieurs échanges, des appels à des outils ainsi que des mises à jour diffusées dans le temps, ce qui est difficile à vérifier en détail manuellement. Les tests unitaires et d’intégration se contentent généralement de tester des parties séparément, sans reproduire une session réelle complète. Avec un client fictif, la question de savoir si une conversation multistep s’est déroulée correctement devient une vérification que vous pouvez effectuer à tout moment.
Exemple : Un outil de test de charge effectue les mêmes appels API que ceux utilisés par un tableau de bord de client réel pour lancer une exécution, puis vérifie un tableau de statut pour savoir quand celle-ci est terminée, car l’exécution se termine plus tard et la première réponse ne donne aucune information sur le résultat. Le nombre d’exécutions simultanées augmente progressivement, de quelques-unes à des dizaines, puis à plus de cent, avec une limite stricte au nombre total d’exécutions et aux coûts, car chaque exécution fictive correspond en réalité à un appel facturé. Cela a révélé que le véritable goulot d’étranglement sous charge était le fournisseur du modèle et non l’infrastructure environnante, ce que des tests effectués séparément n’auraient pas montré.
Choisissez le moment où une personne peut consulter la sortie de l’agent avant qu’elle ne prenne effet. Prévoyez un endroit où quelqu’un puisse voir ce que l’agent s’apprête à faire et puisse l’approuver, le modifier ou le refuser, au lieu de laisser la sortie produire des effets automatiquement dès le premier jour.
Pourquoi : Les erreurs commises par les agents se distinguent des bugs ordinaires. Elles semblent plausibles mais sont fausses, et ne ressemblent pas à des plantages évidents. L’examen humain permet de détecter les erreurs qui échappent à tous les contrôles automatisés, et il constitue le historique nécessaire pour décider ultérieurement où l’agent peut agir en toute sécurité sans supervision.
Exemple : Un agent de mise en forme de documents n’écrit jamais directement dans le document final. Il propose des suggestions que l’éditeur accepte, modifie ou rejette individuellement, de sorte que rien de ce qu’il produit n’est pris en compte tant qu’une personne n’a pas agi.
Étape 3 : pré-production
C’est l’environnement final avant les utilisateurs réels. La question passe de « la fonctionnalité fonctionne-t-elle ? » à « le processus dans son ensemble continue-t-il de fonctionner lorsque les conditions ne sont pas idéales ? » Cela inclut des déploiements répétés, des pannes partielles et des dérives lors du remplacement du modèle ou de la prompt utilisée.
Élaborer une évaluation appropriée, car un simple résultat « réussi » ou « échoué » ne suffit pas. Déterminer ce que signifie « bon » pour la sortie de l’agent, ce qui comprend généralement plusieurs critères (agir en fonction de l’entrée appropriée, être substantiellement correct, rester cohérent d’une exécution à l’autre), ainsi que savoir qui ou quoi vérifie chacun de ces critères : une règle fixe, un évaluateur humain ou un modèle distinct agissant en tant que juge.
Pourquoi : La sortie d’un agent est rarement simplement correcte ou incorrecte comme cela l’est pour une assertion de test unitaire. Un simple signal de réussite/échec masque la partie qui s’est détériorée, et une méthode de notation que personne n’a examinée de près, en particulier un juge automatisé, peut s’éloigner discrètement de ce qui est important pour ceux qui utilisent cette sortie.
Exemple : Un agent de résumé est évalué selon trois critères distincts : la couverture des faits clés, tout contenu inventé qui n’apparaît pas dans la source, et le degré de variation de ses résultats lors de deux exécutions sur la même entrée. Une personne examine un échantillon pour vérifier s’il y a eu invention de contenu, car c’est cette erreur qui coûte le plus cher.
Réexécutez l’agent sur un ensemble fixe d’exemples connus et fiables. Conservez un petit ensemble d’entrées avec des résultats approuvés qui ne changent pas, et exécutez l’agent à chaque déploiement, en particulier après toute modification d’une instruction ou d’un modèle.
Pourquoi : Contrairement au code ordinaire, le comportement d’un agent peut changer subtilement après une petite modification d’instruction ou une mise à jour de modèle, sans que aucun test ne génère d’erreur. Un ensemble d’exemples fixe est souvent le seul moyen de détecter que « l’agent fonctionne bien, mais les résultats se détériorent progressivement ».
Exemple : Un agent de mise à jour de documents est évalué sur un ensemble d’exemples fixe de trois manières : s’il a sélectionné les sections correctes à modifier (comparé à une clé de réponse vérifiée par un humain), si les modifications proposées étaient justes (évaluées par un modèle d’arbitre distinct), et dans quelle mesure ses résultats varient lors de plusieurs exécutions avec le même entrée. En suivant ces trois critères à travers les différentes versions, et non seulement un indicateur de réussite ou d’échec, il s’est avéré qu’un seul changement améliorait une métrique tout en nuisant discrètement à une autre.
Permettez à un échec d’arrêter la publication. Un ensemble de tests dont l’échec autorise encore le déploiement n’est qu’un simple commentaire. Associez les résultats au pipeline afin qu’un échec interrompe automatiquement la publication, de sorte que le contournement de cet échec ne devienne pas la norme.
Pourquoi : Si un contrôle peut être ignoré sans difficulté, cela arrivera, généralement sous pression de délai, moment où les modifications défectueuses ont le plus de chances d’être acceptées.
Exemple : Un pipeline est configuré de telle manière que si un test échoue, l’étape de déploiement ne s’exécute jamais. Il n’y a pas de bouton « déployer quand même », et pour y remédier, il faut modifier consciemment la configuration du pipeline, et non simplement cliquer pour ignorer l’avertissement.
Brisez délibérément le chemin le plus rapide et vérifiez que le système fonctionne correctement en cas de dégradation. Si les résultats sont normalement transmis via un chemin rapide tel qu’un cache, une connexion en temps réel ou une file d’attente, désactivez-le intentionnellement et assurez-vous qu’un mécanisme de secours plus lent continue de fonctionner, plutôt que la fonctionnalité de cesser complètement de fonctionner.
Pourquoi : Les caches, les connexions en temps réel et autres raccourcis similaires sont ceux qui ont le plus de chances de tomber en panne en production. Sans mécanisme de secours, une petite panne dans un composant suffit à faire cesser le fonctionnement de toute la fonctionnalité.
Exemple : Les mises à jour arrivent généralement en une seconde via une connexion push. Lors d’un test avec cette connexion désactivée, le système effectue des vérifications toutes les quelques secondes, ce qui ralentit le processus tout en affichant néanmoins les résultats à l’utilisateur.
Permettez aux outils d’imposer un ordre lorsque l’une des étapes doit être terminée avant l’autre. Si la partie A doit être en place avant que la partie B ne puisse être configurée, intégrez cette séquence dans le processus de déploiement et non dans la mémoire de quelqu’un.
Pourquoi : Une règle du type « déployer X avant Y » ne reste effective que tant que la personne qui la connaît est présente, ou jusqu’à ce que quelqu’un automatise le processus sans en être conscient. Les séquences importantes doivent être gérées par des outils.
Exemple : Un service d’orchestration a besoin de l’ID d’un environnement d’exécution d’IA créé par une autre pile de déploiement, et si chaque pile faisait référence à l’autre, cela créerait un cycle. Ainsi, la pile de l’environnement d’exécution écrit son ID dans un stockage de paramètres partagé lors de sa création, et la pile d’orchestration le lit à cet endroit. On garantit que l’environnement d’exécution existe en premier, et les piles ne se font jamais référence mutuellement.
Étape 4 : le seuil de production
La dernière liste de vérification intervient avant que des utilisateurs réels ne s’appuient sur le système. La correction du fonctionnement devrait déjà avoir été confirmée. Il ne reste plus qu’à s’assurer que ce qui est déployé correspond précisément à ce qui a été testé, et que les pannes peuvent être corrigées et expliquées.
Déployez exactement l’artefact qui a été testé. Utilisez la version compilée qui a réussi les tests pré-prodution et redémarquez-la pour la production, plutôt que de compiler une nouvelle copie à la dernière étape.
Pourquoi : Une reconstruction de dernière minute, même à partir de la même source, peut entraîner l’utilisation d’une version de dépendance différente, d’une date et heure différentes ou de détails d’environnement différents. Ce décalage signifie que la version en production exécute quelque chose qui n’a jamais vraiment été testé.
Exemple : L’application empaquetée qui a réussi tous les tests pré-prodution est simplement rebaptisée version de production et envoyée telle quelle, c’est-à-dire avec le même fichier et non une nouvelle compilation à partir du même code.
Décidez à l’avance où seront envoyées les tentatives échouées et pendant combien de temps elles seront conservées. Fixez une limite stricte de tentatives, choisissez l’endroit où iront les tentatives échouées après cette limite, et déterminez la durée de conservation.
Pourquoi : Sans limite, une tâche défaillante peut continuer à tenter de s’exécuter indéfiniment, consommant silencieusement des ressources ou bloquant une file d’attente. Sans endroit de destination, une tâche échouée disparaît simplement, et personne ne peut agir face à une erreur qu’il ne peut pas voir.
Exemple : Une tâche qui échoue trois fois est automatiquement mise de côté dans une zone de stockage distincte et n’est pas relancée indéfiniment. Elle y reste pendant deux semaines, ce qui donne au technicien le temps de la détecter et de l’analyser.
Planifiez dès maintenant le suivi des audits. Si les décisions prises par le système doivent être expliquées ultérieurement à un utilisateur, à un auditeur ou à un collègue, déterminez à l’avance ce qui sera enregistré et comment le retrouver.
Pourquoi : L’expression « Nous ajouterons un suivi adéquat plus tard » ne se réalise presque jamais lorsque les délais réels arrivent, et au moment où le besoin devient évident, l’historique manquant ne peut plus être reconstitué a posteriori.
Exemple : Chaque décision automatisée est enregistrée sous forme de fiche datée. Si quelqu’un demande plus tard pourquoi le système a agi d’une certaine manière dans un cas particulier, il existe une réponse écrite à consulter plutôt qu’une simple supposition.
Rédigez un rapport pour chaque incident réel, et non seulement pour le correctif appliqué. Après une panne inattendue, notez la cause racine ainsi que la solution afin que l’équipe puisse y accéder ultérieurement ; cela va au-delà d’un ticket clos ou d’un thread de discussion.
Pourquoi : Une solution non documentée sera de nouveau résolue à partir de zéro par quelqu’un d’autre, généralement sous plus de pression. Une brève note constitue souvent la mesure la moins coûteuse pour éviter de devoir mener à nouveau une enquête qui dure des heures.
Exemple : Un lot d’exécutions est resté partiellement traité pendant des jours parce que rien ne les récupérait automatiquement. La cause, la solution et la réflexion qui en a découlé ont été consignées dans un document distinct du correctif de code. Plus tard, une analyse ultérieure a remis en question une conclusion antérieure, selon laquelle le modèle inventait des données, et a révélé, après un examen plus approfondi, que la véritable cause était plus subtile que ce qui avait été pensé au départ. Cette mise à jour a également été documentée, de sorte que la conclusion initiale, moins précise, n’a plus de valeur.
Le schéma sous-jacent à la liste de contrôle
Presque rien de tout cela n’est spécifique à l’IA. Ce sont les habitudes nécessaires à tout système distribué : un état durable, une gestion sécurisée des répétitions, une visibilité sur ce qui s’est passé et pourquoi, ainsi qu’un processus de déploiement qui met en œuvre ce qui a été testé.
L’agent modifie l’endroit où se situe le risque. Les exécutions sont plus longues et intègrent davantage de contexte, ce qui exige que le traitement informatique s’y adapte. Une exécution peut échouer ou dévier de manières que des tests ordinaires ne détectent pas, c’est pourquoi les vérifications de régression sur des exemples connus comme fonctionnels reviennent plus souvent. Tôt ou tard, quelqu’un demandera « pourquoi a-t-il fait ça ? », et le journal d’audit doit être en mesure de répondre à cette question.