Votre test a réussi lundi : même entrée, même code, temperature=0. Mardi, il échoue sans modification apparente. L’assertion comparait une chaîne exacte, mais le modèle a reformulé la réponse. Le test est rouge alors que l’agent fonctionne correctement, et vous déboguez votre suite de tests au lieu de votre produit.
Essayez Apidog dès aujourd’hui
C’est le coût des tests qui impliquent un modèle linguistique : la sortie peut varier, y compris avec une température à zéro. Ce guide explique comment écrire des assertions robustes pour les agents IA. Il approfondit le troisième mode de défaillance présenté dans notre guide sur les raisons pour lesquelles les agents IA tombent en panne en production.
Pourquoi temperature=0 ne signifie pas déterministe
La température contrôle l’échantillonnage du prochain jeton. À 0, le modèle sélectionne le jeton le plus probable. Cela semble reproductible, mais le déterminisme ne dépend pas uniquement du paramètre envoyé dans votre requête.
Les calculs en virgule flottante sur GPU ne sont pas associatifs : le même calcul, exécuté dans un ordre différent, peut produire une variation minime. Cette variation peut modifier le token sélectionné, puis toute la suite de la génération.
L’ordre d’exécution dépend notamment :
- du regroupement de votre requête avec d’autres requêtes ;
- du matériel utilisé ;
- de la région ciblée ;
- des versions de bibliothèques et de noyaux d’inférence ;
- des changements d’infrastructure opérés par le fournisseur.
Les fournisseurs peuvent également remplacer des GPU, re-quantifier des poids ou modifier le routage. Cette discussion vLLM détaille pourquoi un seed fixe et temperature=0 ne garantissent pas une reproductibilité bit à bit.
La conséquence est simple : ne prenez pas une sortie textuelle identique comme contrat de test. Testez les propriétés stables de la réponse.
Pourquoi les assertions de chaîne exacte rendent les tests instables
Voici une assertion fragile :
assert.equal(response, "Votre total de commande est de 42,00 $.");
Elle passe tant que le modèle reprend exactement cette formulation. Mais cette réponse est tout aussi correcte :
Votre total s’élève à 42,00 $.
Le test échoue pourtant.
Un test qui échoue sur une réponse correcte devient rapidement ignoré. L’équipe relance le pipeline jusqu’à obtenir du vert, les vrais échecs se perdent dans le bruit, et la confiance dans toute la suite diminue. Ce problème fait partie des causes des tests instables.
N’essayez pas de résoudre ce problème en ajoutant davantage de snapshots textuels. Vous ne faites alors que coupler le test à l’élément le plus susceptible de varier : la formulation.
Affirmer le contrat : structure et sens, pas texte exact
La formulation peut varier, mais le contrat doit rester stable.
Par exemple, un agent de support peut confirmer un remboursement de nombreuses façons. Une réponse valide doit néanmoins contenir les mêmes informations :
- le montant remboursé ;
- l’identifiant de commande ;
- le statut du remboursement.
Remplacez cette question :
Le modèle a-t-il dit exactement cette phrase ?
Par celle-ci :
La réponse possède-t-elle la bonne structure, les bons champs et des valeurs valides ?
Les stratégies suivantes rendent vos tests plus fiables.
Valider chaque réponse avec un schéma JSON
Si votre agent renvoie des données structurées, définissez un schéma JSON et validez chaque réponse contre ce contrat.
Exemple de schéma pour une réponse de remboursement :
{
"type": "object",
"required": ["order_id", "status", "amount"],
"properties": {
"order_id": {
"type": "string",
"pattern": "^ORD-[0-9]+$"
},
"status": {
"type": "string",
"enum": ["refunded", "pending", "denied"]
},
"amount": {
"type": "number",
"minimum": 0
}
},
"additionalProperties": false
}
Ce test détecte les erreurs importantes :
- champ requis supprimé ;
- type incorrect ;
- objet mal imbriqué ;
- valeur de statut invalide ;
- texte renvoyé à la place du JSON ;
- champ inattendu dans la charge utile.
Vous pouvez charger ce schéma dans Apidog et valider les réponses de votre agent contre le contrat API. Lorsqu’un test échoue, vous identifiez le champ concerné au lieu de comparer plusieurs centaines de caractères.
Tester les appels d’outils, pas la phrase qui les précède
Lorsqu’un agent appelle un outil, testez la requête générée plutôt que le raisonnement ou la phrase qui a mené à cet appel.
Vérifiez au minimum :
- que l’agent a sélectionné le bon outil ;
- qu’il utilise la bonne méthode et la bonne cible ;
- que la charge utile respecte le schéma de l’outil.
Exemple pour un agent de réservation :
expect(toolCall.method).toBe("POST");
expect(toolCall.path).toBe("/reservations");
expect(toolCall.body).toMatchObject({
guests: expect.any(Number),
date: expect.stringMatching(/^\d{4}-\d{2}-\d{2}$/)
});
expect(toolCall.body.guests).toBeGreaterThan(0);
Ajoutez aussi une validation de schéma pour empêcher les paramètres inventés :
{
"type": "object",
"required": ["guests", "date"],
"properties": {
"guests": {
"type": "integer",
"minimum": 1
},
"date": {
"type": "string",
"format": "date"
}
},
"additionalProperties": false
}
La méthode de bout en bout pour tester les appels API d’un agent IA explique comment capturer et valider ces contrats d’outils.
Utiliser des plages numériques plutôt que des valeurs exactes
Pour les nombres calculés ou transmis par le modèle, testez une plage acceptable.
Exemple : un agent calcule le total d’un panier. Au lieu de figer un montant précis, définissez des bornes métier :
expect(response.total).toBeGreaterThanOrEqual(0);
expect(response.total).toBeLessThanOrEqual(cartSubtotal + maxShipping + maxTax);
Cette approche détecte les erreurs utiles :
- total négatif ;
- total disproportionné ;
- montant nul sur un panier non vide ;
- format numérique incorrect.
Utilisez la même logique pour :
- les scores de confiance ;
- le nombre d’articles ;
- l’utilisation de tokens ;
- les budgets de latence ;
- les montants, remises et quotas.
Choisissez la plage la plus large qui détecte encore un vrai bug.
Vérifier les champs obligatoires et interdits
Ajoutez deux contrôles systématiques à vos tests :
- les champs nécessaires sont présents et non nuls ;
- les champs sensibles ou internes sont absents.
Exemple :
expect(response.resolution).toBeDefined();
expect(response.resolution).not.toBeNull();
expect(response).not.toHaveProperty("internal_notes");
expect(response).not.toHaveProperty("raw_prompt");
Ces assertions ne dépendent pas de la formulation. Elles constituent aussi une protection simple contre les fuites de données internes.
Tester le texte libre avec des seuils et des règles sémantiques
Lorsque la réponse est nécessairement en prose, testez des propriétés stables plutôt qu’une égalité exacte.
Par exemple :
expect(response.message).toContain(orderId);
expect(response.message.length).toBeLessThanOrEqual(500);
expect(response.message).not.toMatch(/motif interdit|information interne/i);
Si vous devez vérifier le sens global, comparez la réponse à une référence via une similarité d’intégration (embedding similarity) :
expect(similarity(response.message, expectedMeaning)).toBeGreaterThan(0.82);
Utilisez ce type de vérification comme filtre grossier. Il peut détecter une réponse hors sujet, mais ne suffit pas à identifier une erreur factuelle subtile. Associez-le toujours à des assertions structurelles et métier.
Capturer des contrats de snapshot, pas des snapshots de texte
Les snapshots restent utiles s’ils enregistrent les éléments stables :
- les clés présentes ;
- les types ;
- les valeurs énumérées ;
- les bornes numériques ;
- la structure des objets.
Évitez de figer un bloc de prose complet. Un bon snapshot exprime plutôt ceci :
La réponse contient les clés a, b et c.
b est compris dans une plage définie.
c appartient à une énumération autorisée.
Ainsi, le snapshot ne casse que lorsqu’un changement structurel mérite réellement une revue.
L’état et la mémoire augmentent la variabilité
Les agents conservent souvent de la mémoire entre plusieurs tours. Leur sortie dépend alors :
- des documents récupérés ;
- des échanges précédents ;
- des résumés générés pendant la conversation ;
- de l’ordre des actions déjà réalisées.
Deux exécutions d’une conversation identique peuvent diverger si la récupération classe les documents différemment ou si un résumé intermédiaire influence le raisonnement ultérieur. Consultez notre guide sur le fonctionnement de la mémoire des agents IA pour comprendre où cet état est stocké.
Pour maintenir des tests reproductibles :
- Initialisez l’état à chaque test. Démarrez depuis une mémoire connue afin d’isoler la variation testée.
-
Testez des invariants indépendants du chemin. Par exemple :
- un solde ne devient jamais négatif ;
- une conversation qui réserve un vol crée exactement une réservation ;
- un utilisateur ne reçoit jamais de données d’un autre utilisateur.
Exemple :
expect(finalState.balance).toBeGreaterThanOrEqual(0);
expect(finalState.reservations).toHaveLength(1);
Simuler les dépendances externes
N’exécutez pas tous vos tests contre des API tierces réelles. Elles peuvent être limitées en débit, modifier leurs données ou introduire une seconde source d’aléatoire.
Simulez les dépendances de l’agent avec des réponses fixes :
{
"payment_api": {
"status": "success",
"receipt_id": "RCP-12345"
},
"search_api": {
"results": [
{ "id": "doc-1", "title": "Politique de remboursement" },
{ "id": "doc-2", "title": "Délais de traitement" },
{ "id": "doc-3", "title": "Conditions générales" }
]
}
}
Vous obtenez alors un environnement où :
- l’API de paiement renvoie toujours le même reçu ;
- l’API de recherche renvoie toujours les mêmes résultats ;
- la variation restante provient du comportement de l’agent lui-même.
Les mocks permettent également de forcer des cas limites difficiles à obtenir avec une API saine : timeout, erreur 500, ressource introuvable ou réponse incomplète.
Utilisez Apidog pour simuler les dépendances de l’agent avec des réponses contrôlées, puis combinez ces mocks avec vos validations de schéma. Cette pratique s’inscrit dans les tests IA agentiques, où simulation et assertions fonctionnent ensemble.
Où Apidog s’intègre — et où il ne s’intègre pas
Apidog est une plateforme de conception, de test et de simulation d’API. Ce n’est pas :
- un framework d’agent ;
- un hébergeur de modèles ;
- un runtime d’agent ;
- un orchestrateur de tâches ;
- une plateforme d’évaluation ou d’observabilité du raisonnement.
Apidog intervient à la couche API, là où vos contrats sont testables.
Vous pouvez notamment :
- valider les réponses API d’un agent avec un schéma ;
- contrôler les champs requis et interdits ;
- tester les plages numériques ;
- vérifier la structure des appels d’outils ;
- simuler les services externes utilisés par l’agent.
C’est cette séparation qui rend la stratégie robuste : Apidog teste le contrat entre requêtes et réponses, pas le modèle qui produit le texte.
Testez le contrat, pas la formulation
Le non-déterminisme n’est pas un bug que temperature=0 peut éliminer. C’est une propriété de l’exécution d’un modèle linguistique et de son infrastructure.
Pour stabiliser votre suite de tests :
- remplacez les comparaisons de texte exact par des schémas ;
- testez les clés, types et valeurs énumérées ;
- utilisez des plages pour les valeurs numériques ;
- vérifiez les champs sensibles absents ;
- simulez les dépendances externes ;
- réinitialisez l’état des agents avant chaque scénario.
Choisissez une assertion instable cette semaine et transformez-la en validation de schéma ou de plage. Votre suite restera verte lorsque le modèle reformule correctement une réponse, et elle deviendra rouge uniquement lorsqu’un vrai contrat est cassé.
Top comments (0)