Une plateforme d’expérimentation minimale viable pour les agents IA : tests A/B et exécutions en mode ombre
Comment déterminer si la version B de l’agent doit remplacer A, en utilisant les définitions de pipeline, l’affectation locale, les événements d’exposition, les traces, l’évaluation en environnement isolé, les exécutions en mode ombre et les règles de contrôle.
Sources: A minimum viable experimentation platform for AI agents
Déterminer si une nouvelle version d’agent est supérieure à celle en production est plus difficile qu’il n’y paraît. En phase de développement, la tâche reste gérable : on dispose d’un ensemble de cas de test, on exécute les deux versions, on analyse quelques traces d’activité, on fait évaluer les résultats par un autre modèle pour décider si ce changement mérite plus d’efforts. Les choses se compliquent lorsque l’objectif n’est pas une simple requête ou un appel à un modèle unique, mais un système dont les composants évoluent selon leurs propres calendriers et interagissent de manières pas toujours évidentes.
Imaginez un agent d’assistance client composé de ces éléments :
agent runtime
memory and retrieval
system prompt
model
tools
guardrails
application logic
Supposons que la stratégie de récupération change et que l’achèvement des tâches augmente sur l’ensemble de test. L’agent est peut-être réellement meilleur. Il est également possible qu’il effectue davantage d’appels au modèle, ce qui entraîne une augmentation des coûts de 20 %, qu’il se comporte différemment avec les utilisateurs réels, ou qu’il bénéficie d’un autre composant qui a justement changé pendant la même période. Dans ce cas, l’opération ressemble moins à une évaluation de modèle et plus à une expérience.
Ce qui suit est un aperçu de la plateforme la plus simple sur laquelle on pourrait s’appuyer pour répondre à une question assez simple :
should version B replace version A?
Cela n’a de sens que à une certaine échelle. Un seul agent, vingt cas de test examinés manuellement et quelques utilisateurs internes n’ont pas besoin de la majeure partie de ces fonctionnalités. La plateforme devient utile lorsque plusieurs équipes modifient indépendamment des modèles, des prompts, de la mémoire, des outils et des implémentations d’agents, que des expériences se déroulent en continu, et qu’il faut finalement valider un changement en production à l’aide de preuves encore lisibles des mois plus tard. Une plateforme d’agents à l’échelle de toute l’entreprise correspond à cette description.
Qu’est-ce que B, en réalité ?
Le premier problème est de nature fondamentale. Supposons qu’une expérience indique :
A = agent-v17
B = agent-v18
Qu’est-ce que agent-v18 exactement ? Si la requête change au cours de l’expérience, l’index de récupération est reconstruit et le gateway du modèle change son modèle par défaut, ce qui ne dit presque rien sur ce qui a été évalué. Cela affecte particulièrement les agents, car le comportement observé à la fin provient de l’ensemble du pipeline et non d’une seule invocation isolée du modèle.
Ainsi, la définition immuable du pipeline est ce qui fait l’objet de l’évaluation. Elle peut ressembler à ceci :
pipeline:
name: support-agent
version: 18
components:
runtime:
artifact: agent-runtime@sha256:...
memory:
artifact: memory-service@sha256:...
configuration:
strategy: hybrid
top_k: 8
model:
route: support
model: provider/model-x
prompt:
artifact: sha256:...
tools:
- artifact: customer-lookup@sha256:...
- artifact: refund-tool@sha256:...
Le format précis de l’artefact importe peu. Ce qui compte, c’est qu’une variante puisse être reconstituée en tant que configuration système, afin que des mois plus tard quelqu’un puisse savoir ce que l’utilisateur affecté à B était censé exécuter. Cela permet également à la plateforme d’expérimentation de rester indépendante de tout ce qui est en train de changer. Une expérience pourrait comparer :
model X vs model Y
Un autre pourrait comparer :
memory top_k=5 vs top_k=8
Un troisième scénario pourrait opposer deux implémentations d’agents complètement différentes. Du point de vue de l’expérience, chacune de ces variantes n’est qu’une version différente du flux de travail. Lorsqu’une expérience commence, ces définitions deviennent fixes. Si le traitement doit changer, une nouvelle itération de l’expérience est créée, de sorte que la signification historique de B n’est jamais réécrite en secret.
L’affectation ne doit pas devenir une dépendance de production
Ensuite se pose la question de savoir qui reçoit A et qui reçoit B. Une approche tentante consiste à faire en sorte que chaque requête demande à un service central d’expérimentation son traitement correspondant. Cela intégre la plateforme d’expérimentation dans les contraintes de disponibilité et de latence de l’application elle-même, ce qui est un mauvais équilibre pour un système dont le but principal est la mesure.
La première version utilise un hachage déterministe. Le plan de contrôle envoie sa configuration actuelle à un SDK intégré à l’application, et le choix du processeur s’effectue directement sur place, localement. Cela confère une propriété utile en cas de panne : si le service d’expérimentation tombe en panne, la plateforme ne peut pas temporairement lancer de nouvelles expérimentations ni modifier les allocations, mais l’application reste intacte et le SDK continue de fonctionner avec sa dernière configuration valide.
Pour la version MVP, quelques unités de randomisation suffisent :
request
user
session
workflow_instance
Le dernier existe parce que les agents n’ont pas nécessairement la même durée de vie qu’une requête Web. Un agent peut commencer une migration de répertoire aujourd’hui, attendre une approbation externe et reprendre demain. Si l’affectation était recalculée à partir de la configuration du lendemain, le même flux de travail pourrait commencer sur une implémentation et se terminer sur une autre, rendant à la fois les opérations et l’analyse inutilement complexes. L’affectation reste donc fixée :
workflow_instance = migration-8291
variant = B
pipeline = agent-v18
et il se déplace avec le flux de travail chaque fois qu’il est repris.
L’affectation diffère de l’exposition
Une autre distinction devient importante lorsque le traitement se trouve quelque part au sein d’un pipeline d’agent. Atteindre un groupe de traitement ne garantit pas que le traitement a bien été exécuté.
Prenons deux implémentations de mémoire à tester. Un utilisateur se retrouve dans B, la nouvelle version de la mémoire, mais l’agent répond sans effectuer de recherche dans la mémoire. L’affectation existe bien, mais le composant testé n’a jamais eu l’occasion d’influencer le résultat. Ce sont deux événements distincts :
ASSIGNMENT
user-18271 → B
EXPOSURE
memory-v2 retrieval started
Cela fournit également des informations sur la santé de l’expérience. En plus du nombre d’utilisateurs randomisés dans chaque groupe, il est possible d’examiner la situation en matière d’exposition :
assigned 10,428
exposed 8,912
exposure rate 85.46%
Si A et B présentent des taux d’exposition inattenduement différents, cette différence mérite d’être examinée en premier.
Un problème similaire survient lorsque le traitement assigné n’est pas celui qui est finalement exécuté. Supposons que B choisisse le modèle Y, que ce dernier renvoie une erreur 503, et que le système passe en revanche au modèle X. Le journal d’activité indique alors :
assigned_variant = B
realized_model = X
fallback_reason = provider_unavailable
et l’exécution reste dans B.
Cela respecte la règle de l’intention de traitement, ITT. Chaque sujet est analysé dans le groupe auquel il a été randomisé, même si ce qui a réellement été exécuté diffère. Ce recours fait partie de l’expérience B, et supprimer de telles demandes ultérieurement redéfinirait la population en fonction d’un événement survenu après la randomisation. Les données d’exposition et de traitement effectif restent précieuses, principalement en tant que signaux de santé et diagnostiques, sauf si l’analyse indique autre chose.
Le traçage explique ce qui s’est passé
Le résultat d’une expérience indique si un résultat a changé, mais il ne dit rien sur pourquoi. Pour comprendre pourquoi, OpenTelemetry est le choix judicieux, et non un nouveau protocole de traçage. La couche d’expérimentation doit simplement ajouter un peu de contexte supplémentaire à chaque span :
experiment.id
experiment.version
experiment.variant
experiment.assignment_id
pipeline.id
pipeline.version
execution.purpose
Avec cela en place, un résultat global tel que :
B has a lower task completion rate
peut se transformer en une demande bien plus utile :
show failed B traces
Et les traces révèlent si le nouveau pipeline utilise un contexte moins fiable, tente à nouveau les appels d’outils, passe à un modèle différent, reste bloqué dans des boucles ou emprunte simplement une voie plus longue pour atteindre le même résultat.
Une trace n’est pas pour autant un enregistrement d’expérience. Elle explique bien le déroulement d’une exécution, mais les résultats expérimentaux peuvent survivre à celle-ci. Un client peut parler à un agent aujourd’hui et renouveler son abonnement une semaine plus tard, bien après la fin de la trace initiale. Ainsi, un flux d’événements d’expérience plus restreint fonctionne en parallèle du suivi des traces :
assignment
exposure
outcome
feedback
evaluation
La trace enregistre l’exécution. Le flux d’événements assure la comptabilité qui lie la randomisation aux résultats qui arrivent par la suite.
L’évaluation hors ligne utilise le même pipeline
Avant que B n’atteigne les utilisateurs en production, il doit pouvoir gérer des scénarios connus. Pour les agents, un scénario ne doit pas se réduire à :
prompt
expected answer
Puisque le résultat intéressant n’est souvent pas le texte retourné, mais plutôt l’état de l’environnement par la suite. Un scénario de support pourrait être rédigé comme ceci :
scenario:
id: duplicate-charge-001
input:
message: >
I was charged twice for order 9811.
environment:
fixture: duplicate-charge-customer
expected:
refund_count: 1
ticket_status: resolved
limits:
max_turns: 15
max_tool_calls: 20
max_cost: 0.50
Le même scénario est ensuite appliqué aux pipelines A et B, éventuellement plusieurs fois, car les exécutions des agents sont stochastiques et deux exécutions avec la même configuration peuvent suivre des trajectoires différentes à partir du même état de départ. Le MVP utilise un environnement isolable réinitialisable avec des services basés sur des fixtures ou des bases de données de test. La création d’un système général de répétition des séquences multi-échanges devrait être reportée à plus tard.
La répétition ne fonctionne que tant que le candidat demande des interactions qui ont déjà été enregistrées. Supposons que B prenne une décision différente au troisième tour et appelle un outil que la trajectoire initiale n’a jamais utilisé. Dans ce cas, aucun monde enregistré ne contient cette branche. Le résultat attendu est donc :
REPLAY_DIVERGED
Une réponse simulée, feignant que l’exécution soit encore une reprise, est une mauvaise approche. Un environnement de test avec état permet aux deux agents de s’orienter naturellement tout en contrôlant leurs conditions initiales, ce qui constitue une meilleure première approximation pour évaluer des agents réels.
L’exécution en ombre comble ce manque
Une étape utile se situe entre l’environnement de test contrôlé et la mise à disposition des utilisateurs de B : exécuter B de manière asynchrone sur une copie des données réelles d’production sans afficher son résultat. Les écritures et les effets secondaires externes sont désactivés ou redirigés vers un environnement isolé. Cela fournit des données similaires à celles de la production et permet à l’équipe d’observer :
latency
cost
tool usage
model fallbacks
guardrail failures
judge scores
trajectory differences
avant que tout utilisateur ne voie le traitement.
Les simulations en arrière-plan ne permettent pas de déterminer si B améliore le résultat le plus important, car les utilisateurs ne voient jamais B et ne peuvent donc pas y réagir. Ce sont un tremplin : des entrées réalistes et un comportement en temps réel, sans aucun des risques liés à une expérience en direct.
Tout contrôle n’a pas besoin d’un juge basé sur un LLM
Lorsque l’agent termine une scénario, le résultat doit encore être évalué, c’est pourquoi la première version dispose de deux types d’évaluateurs.
Le premier type est déterministe. Lorsqu’une tâche consiste à effectuer un seul remboursement et à résoudre une demande, il suffit d’examiner l’environnement :
refund_count == 1
ticket_status == resolved
Demander à un autre modèle si la tâche a probablement été accomplie ne sert à rien lorsque l’état résultant peut être vérifié.
Le deuxième type est basé sur un modèle, pour des propriétés qui résistent aux affirmations :
helpfulness
clarity
tone
quality of explanation
Un juge basé sur un LLM peut aider, mais ce juge devient une autre composante versionnée et ne constitue jamais une source anonyme de vérité :
evaluator
evaluator version
judge model
judge prompt
rubric
Chaque note générée conserve cette provenance. Un petit ensemble de données de calibration étiqueté par des humains est également maintenu, et le juge est comparé à celui-ci périodiquement. Un juge basé sur un LLM est un instrument de mesure, et lorsque cet instrument change, il importe que ses lectures changent également. Les juges agents qui naviguent, appellent des outils ou retrouvent des preuves supplémentaires sont utiles et probablement inévitables dans certains domaines, mais ils n’ont pas leur place dans la première version.
Statistiques, limitées
Les systèmes d’expérimentation avancés offrent un degré de sophistication élevé. Un meilleur point de départ consiste à disposer d’un petit ensemble de méthodes mises en œuvre correctement, plutôt qu’un menu statistique impressionnant que peu d’utilisateurs comprennent. La première version prend en charge :
A/B allocation
binary metrics
continuous metrics
95% confidence intervals
sample ratio mismatch detection
fixed-horizon analysis
Cela suffit pour répondre à de nombreuses questions utiles, et un résumé des résultats pourrait être le suivant :
Experiment
support-agent-v18
Randomization
user
Primary metric
ticket resolution
A 81.4%
B 85.1%
Difference
+3.7 percentage points
95% CI
[...]
Experiment health
SRM PASS
Un déséquilibre dans le ratio d’échantillonnage nécessite une attention particulière, car les résultats ne doivent jamais paraître fiables lorsque la randomisation elle-même semble défaillante. Si le plan prévoyait un partage équilibré et que le partage observé diffère clairement, l’expérience est considérée comme défectueuse, et c’est tout ; aucun gagnant n’apparaît à côté d’un icône d’avertissement.
Les mécanismes de contrôle opérationnels font également partie de cette couche de santé. Une variante peut échouer en termes de coûts sans échouer sur le plan technique. Imaginez un cycle de requêtes qui envoie des appels depuis :
4 model calls
Dans :
47 model calls
Et la réponse peut encore être HTTP 200, même en cas de bonne réponse. Il est donc nécessaire d’établir des plafonds :
max cost / execution
max tokens / execution
max agent turns
max tool calls
max variant spend / hour
Traverser un cap peut arrêter cette exécution ou suspendre toute nouvelle exposition à la variante. Un rapport de santé pourrait alors indiquer :
Variant B
SRM PASS
Error rate PASS
Cost / request +312%
Circuit breaker TRIPPED
New exposure PAUSED
Un disjoncteur n’est pas une conclusion statistique. C’est un mécanisme de sécurité opérationnelle qui empêche un traitement problématique d’épuiser le trafic et le budget tant que l’expérience ne collecte pas encore suffisamment d’observations pour permettre des conclusions statistiques.
Les méthodes plus sophistiquées peuvent attendre : CUPED, tests séquentiels, échantillons de contrôle, couches d’expérience, randomisation par cluster, allocation adaptative et bien d’autres. Le modèle de données doit laisser de la place pour elles, mais la première version doit simplement permettre d’exécuter correctement des expériences A/B classiques.
Quoi construire en premier
Avec la plupart des éléments pouvant être différés supprimés, la plateforme devient gérable. Elle a besoin d’un service de contrôle qui contienne :
- un registre des expériences
- un registre des pipelines
- un registre des ensembles de données
- un registre des évaluateurs
- des définitions de métriques
En haut se trouve le SDK, qui télécharge la configuration de l’expérience et l’affecte de manière déterministe du côté client. Un chemin d’ingestion est nécessaire pour :
assignment
exposure
outcome
feedback
evaluation
Les traces OpenTelemetry sont également ingérées. Les choix de stockage raisonnables consistent en PostgreSQL pour l’état du plan de contrôle, NATS JetStream ou Kafka pour les événements, ClickHouse pour l’analyse des expériences et des traces, ainsi qu’un stockage d’objets compatible avec S3 pour les artefacts volumineux. Un exécuteur hors ligne traite un ensemble de données sur A et B au sein d’environnements qui peuvent être réinitialisés.
Beaucoup de fonctionnalités sont délibérément absentes. La version MVP ne prend pas en charge la virtualisation d’environnements à état arbitraire, les interactions subtiles entre expériences, l’évolution des évaluateurs, l’analyse automatique des causes racines, les effets thérapeutiques durables ou l’attribution causale entre plusieurs agents, et elle peut se passer d’elles. Ce qui est important dès le départ, c’est ce dont il est difficile de corriger une fois que des données historiques en dépendent :
what was randomized
what treatment was assigned
what treatment actually ran
what pipeline version produced the execution
what outcome was measured
how that outcome was evaluated
Tant que ces identités survivent à la première version, la plateforme peut devenir bien plus riche sans altérer le sens de ses anciens essais.
L’idée intéressante est que l’évaluation hors ligne, l’exécution en arrière-plan et les expérimentations en temps réel peuvent partager des définitions de pipeline, des traces et des outils d’évaluation, bien que chacun réponde à sa propre question. Les exécutions hors ligne montrent comment A et B se comportent dans des scénarios contrôlés. Les exécutions en arrière-plan révèlent comment B gère des entrées de production sans affecter les utilisateurs. Les expérimentations en temps réel montrent quels changements surviennent lorsque des utilisateurs réels sont divisés au hasard. Les traces enregistrent ce qui s’est passé lors de chaque exécution et aident à interpréter les résultats, mais un diagnostic au niveau des traces ne doit jamais être considéré comme une estimation causale. Ensemble, ils permettent de créer un MVP capable de répondre de manière fiable à trois questions :
What exactly did we run?
What happened when we ran it?
Did assigning users to B improve the outcome we care about?
Tout le reste peut se développer autour de cela.