La panne la plus coûteuse d'un agent vocal, c'est un silence
Un écart de contrat d'outils ne produit pas d'erreur : l'action est ignorée sans trace. Retour sur une intégration où le contrat n'a jamais été chargé.

L'agent vocal parle bien. Il comprend « montre-moi les appartements à Lyon », il répond juste, il relance. Et à l'écran, rien ne bouge : pas de filtre appliqué, pas de fiche ouverte, pas une photo qui défile. Surtout, aucune erreur — ni dans la console du navigateur, ni dans les journaux côté plateforme. Le visiteur, lui, voit un assistant bavard devant une page inerte.
Le symptôme a tenu, sur une démonstration en production, jusqu'à ce qu'on cherche au bon endroit. Le fait mesuré qui explique tout est celui-ci. Le contrat d'outils publié pour cette démonstration déclare aujourd'hui 13 outils d'écran et 22 identifiants de catalogue matérialisés en liste fermée, validés en local avant tout appel réseau contre des bornes strictes : 64 outils maximum, profondeur de schéma 8, 16 mots-clés de schéma autorisés, 17 noms d'outils réservés. La version en service au moment de la panne avait été acceptée par le serveur, et une relecture confirmait qu'elle était active. Elle n'a jamais été chargée au démarrage : une clé d'écriture et une clé de lecture divergeaient. L'agent est retombé en silence sur un jeu d'outils appartenant à une autre démonstration, dont aucun nom ne correspondait à ce que la page écoutait. Zéro outil appelé, zéro erreur, nulle part.
Ma thèse : sur une intégration d'agent vocal, le vrai livrable n'est ni le bouton ni l'iframe, c'est le contrat d'outils. Et son mode de panne dominant n'est pas une erreur, c'est une action ignorée sans trace des deux côtés.
Le modèle mental : une iframe, un canal à sens unique, un agent qui pilote en aveugle
L'agent ne vit pas dans votre page. Il vit dans une iframe servie par un tiers, avec son propre modèle. Le seul lien avec votre interface est un canal de messages entre fenêtres : une frontière de sécurité, et le seul endroit où la panne peut se produire.
Ce canal est à sens unique. L'agent agit sur l'écran ; l'écran ne lui renvoie pas son état. Quand il demande « filtre sur Lyon », il ne saura jamais combien de biens sont affichés après le filtre. Il pilote en aveugle. Conséquence : pour qu'il parle juste des données — un prix, une surface, un nombre de résultats —, il faut qu'il les connaisse autrement que par l'écran, par des outils déterministes qui lisent la source de vérité. C'est le principe que j'applique sur toutes les intégrations d'agents et d'IA : les chiffres passent par un outil, jamais par la mémoire du modèle.
Quatre champs, trois sujets : à quoi ressemble vraiment le canal
L'enveloppe qui circule tient en quatre champs, et il n'y a que trois sujets possibles : un outil vient d'être appelé, un fragment de transcription, une erreur de l'agent.
{ source: "<émetteur attendu>", v: 1, topic: "<un des trois sujets>", payload: { ... } }
Côté page hôte, l'ordre de vérification compte. On filtre d'abord l'origine exacte de l'expéditeur, puis on valide la forme du message — émetteur attendu, version strictement égale à 1, sujet de type chaîne, présence d'une charge utile — et seulement ensuite on regarde le contenu. Un message qui échoue à l'une de ces marches est jeté, silencieusement : c'est ce qu'on attend d'une frontière de sécurité, et c'est ce qui rendra le diagnostic difficile.
Le contrat d'outils est le livrable
Ce que votre site expose à l'agent, ce n'est pas une interface : c'est une liste de noms d'outils, un schéma d'arguments par outil et des listes fermées d'identifiants autorisés. Dans la démonstration citée, l'outil de demande de visite embarque les 22 identifiants de biens comme énumération de son argument — l'agent ne peut pas inventer une référence qui n'existe pas.
Tant que ce contrat vit en double — dans le code du site et dans la configuration de l'agent —, la question n'est pas de savoir s'il va diverger, mais quand. Deux dépôts, deux rythmes de déploiement, parfois deux personnes : la dérive est mécanique. Et elle ne se voit pas au moment où elle est introduite. Elle se voit devant un prospect.
Pourquoi l'écart produit un silence, et pas une erreur
L'aiguillage par nom, et la valeur nulle qui ne dit rien à personne
Le site route l'action reçue par un aiguillage sur le nom de l'outil. Un nom inconnu tombe dans la branche par défaut :
default:
return null;
Cette valeur nulle remonte au composant, qui ne fait rien. Personne ne la journalise : « rien à faire » n'est pas une anomalie dans la vie normale du composant, où les fragments de transcription défilent en continu pendant que l'agent parle. Côté agent, la symétrie est parfaite : l'appel a été émis et accepté par le canal, donc pour lui l'action a réussi. Deux systèmes satisfaits, un écran figé.
La correction tient en une ligne de discipline : un écart de contrat doit produire du bruit. Un code d'erreur explicite « outil inconnu », journalisé côté site avec le nom reçu, transforme une enquête en une lecture de journal. Ça ne coûte rien au moment où l'on écrit le parser, et ça ne se rattrape pas après coup : le jour où vous en avez besoin, vous cherchez déjà au mauvais endroit.
Générer le contrat depuis le code
La parade est de ne plus avoir deux sources : que les schémas qui valident les arguments à l'exécution produisent aussi le fichier publié. Un outil ajouté, un champ renommé, une énumération élargie, et le fichier publié suit — parce qu'il vient de la même source.
Je le dis d'autant plus volontiers que mon propre dépôt me contredit à moitié. Sur la façade serveur-à-serveur, c'est fait : les mêmes schémas valident le runtime et génèrent le contrat publié. Sur le contrat d'embed — celui de la panne — non : le fichier reste une donnée maintenue à la main, et les identifiants du catalogue y sont recopiés. La dérive dont je décris les effets est toujours vivante à deux dossiers de là. C'est le meilleur argument que je puisse donner : ce n'est pas une faute d'inattention, c'est l'état par défaut de toute configuration publiée en double.
C'est le même réflexe que sur une application métier : une donnée de référence vit à un seul endroit. Ce qui change ici, c'est que la deuxième copie est chez un tiers, et que vous ne la voyez pas.
Le validateur local, exécuté avant tout appel réseau
Le script de publication ne se contente pas d'envoyer le fichier. Il le valide hors ligne, contre les bornes du serveur : 64 outils au maximum, 512 Ko de charge utile, 2 000 caractères de description, 500 valeurs d'énumération, 50 propriétés par objet, profondeur de schéma 8, 2 000 identifiants de catalogue. Il n'accepte que 16 mots-clés de schéma. Une clé inconnue sur un objet outil est une erreur, alors qu'un champ inconnu à la racine n'est qu'un avertissement : l'un est une faute de frappe qui sera ignorée en silence, l'autre une extension future du format. Et il refuse toute collision avec les 17 noms d'outils réservés par le backend et les 2 préfixes réservés.
Reproduire hors ligne le rejet du serveur n'est pas redondant : le rejet local vous dit quelle ligne corriger, sans dépendre d'un journal distant auquel vous n'avez pas forcément accès.
Poussé n'est pas actif, et actif n'est pas chargé
La publication renvoie quatre choses : le numéro de révision, un drapeau de changement, le nombre d'outils et une empreinte de contenu. La relecture inverse — on redemande le contrat actif et on compare — est censée fermer le sujet.
Ici, elle disait : contrat actif, le bon nombre d'outils, les noms attendus. Le schéma était aligné des deux côtés, vérifié à la main, et pourtant rien ne se passait.
La leçon vaut pour n'importe quelle configuration publiée chez un tiers : une confirmation d'écriture prouve le stockage, jamais le chargement. Le contrat était enregistré sous la clé de la démonstration ; le runtime allait le chercher sous une autre clé, ne trouvait rien, et se rabattait sans un mot sur le jeu d'outils statique d'une autre démonstration. La seule preuve qui vaille vient du runtime : une ligne de journal qui nomme la révision chargée, ou un événement d'outil réellement reçu par la page.
Le repli silencieux a un effet pervers : une intégration dont le jeu de secours ressemble au sien continue de paraître saine, et la panne se lit comme un défaut spécifique là où elle est structurelle.
Le panneau de diagnostic qui tranche en une minute
La pièce la plus rentable de cette intégration ne se voit pas en production. C'est un panneau de débogage qui injecte des actions d'outil directement dans la page sans passer par l'agent. Scénarios enchaînés, actions unitaires, un composant partagé entre les démonstrations.
Son intérêt n'est pas de tester le site : il est de l'exonérer. Si les actions injectées produisent le bon rendu, le problème est en amont, et on arrête de relire du code qui va bien.
L'ordre correct des questions
| Question | Comment on y répond | Ce qu'une réponse positive exclut |
|---|---|---|
| Le rendu réagit-il aux actions ? | panneau de débogage, actions injectées | un défaut d'interface |
| Le canal transporte-t-il quelque chose ? | la transcription s'affiche-t-elle pendant que l'agent parle ? | un problème d'origine ou de format d'enveloppe |
| Le contrat chargé est-il le bon ? | révision et nombre d'outils constatés au démarrage | une erreur de publication ou de clé |
| L'agent appelle-t-il des outils ? | écouter les messages reçus, lire les journaux de session | tout le reste |
On remonte la chaîne d'alimentation avant d'accuser le modèle, parce que le modèle est la seule pièce qu'on ne peut pas lire. J'ai fait l'inverse, en cherchant dans le comportement du modèle une explication qui était dans le câblage.
Ce que j'en retiens pour toute brique tierce
Quatre règles que j'applique dès la conception, et qui n'ont rien de spécifique au vocal :
- Le contrat d'échange est le livrable. Il se génère depuis le code, il ne se recopie pas à la main d'un système à l'autre.
- Tout écart doit faire du bruit. Un cas non géré qui retourne une valeur nulle est une panne invisible en attente d'un jour de démonstration.
- Une confirmation d'écriture n'est pas une preuve de lecture. Exigez un signal venu du runtime, pas un code de retour.
- Le budget diagnostic se prévoit avant la mise en production. Sans outil d'isolation, on débat longtemps ; avec, on tranche en une minute.
Et une cinquième, moins technique : prévoyez ce que voit le visiteur quand ça casse. Sur la page d'accueil de ce site, si la clé d'embed est absente, le bouton n'est pas monté du tout — pas de cadre vide, et une prise de rendez-vous reste visible juste en dessous.
Si vous avez une brique tierce dans votre parcours client — agent vocal, chatbot, module de réservation, connecteur métier —, son contrat vit peut-être en double quelque part. On peut le regarder ensemble : 30 minutes pour en parler concrètement, et vous repartez avec l'ordre des questions à poser.
Un enjeu similaire dans votre entreprise ?
30 minutes pour en parler concrètement — gratuit, sans engagement.
Articles similaires

Un agent vocal ne doit jamais citer un prix de mémoire
Comment j'empêche un agent vocal d'inventer un tarif : les chiffres passent par un outil déterministe, les fiches de biens ne portent aucun chiffre.

Un agent qui appelle votre système métier n'a pas de session
Signature HMAC, horodatage, anti-rejeu, idempotence : comment j'ouvre 18 outils métier à une IA sans jamais lui donner les clés de toute la base.

Le fichier qui coûtait 55 000 tokens avant ma première question
221 679 octets chargés à chaque session, ramenés à 7 875 sans rien perdre. Ce qu'un fichier de règles coûte vraiment à un agent, et comment l'élaguer.