TL;DR : Ton IA va inventer des champs d’API qui semblent parfaits mais qui n’existent pas. La solution n’est pas de croiser les doigts pour qu’elle tombe juste : c’est de télécharger le schéma réel avant d’écrire, capturer des réponses réelles comme fixtures, et séparer fetch du traitement pour pouvoir tester hors réseau. Programmation adversariale : programme en supposant que ton copilote ment.
As-tu déjà écrit du code pour une API et tout compilait, les tests passaient, la logique semblait correcte… mais quand tu te connectes à l’API réelle, plus rien ne fonctionne ?
Quand tu programmes seul, ça arrive parce que tu as mal lu la documentation. Mais avec une IA au clavier, ça arrive parce que… l’IA a inventé la documentation.
Une hallucination qui ne ressemble pas à une hallucination
Je construisais une interface en ligne de commande en Rust pour interagir avec une API GraphQL. J’ai demandé à mon copilote IA de développer un filtre permettant de classer les résultats par priorité. Voilà ce qu’il m’a retourné :
query {
issues(orderBy: { priority: ASC }) {
nodes {
id
title
priority
}
}
}
Net. Logique. Exactement ce à quoi on pourrait s’attendre. Un seul problème : le champ orderBy de cette API n’accepte pas priority comme valeur. L’enum réel s’appelle PaginatedOrder et contient des valeurs comme createdAt et updatedAt. Pas de priority à l’horizon.
Comment l’ai-je découvert ? Quand l’API a renvoyé une erreur 400 incompréhensible. Il m’a fallu 20 minutes pour réaliser que le problème ne venait pas de mon code : c’est juste que le champ que j’avais utilisé n’existait pas.
Le motif est toujours le même
Ce n’était pas un incident isolé. Pendant des semaines de développement, l’IA a halluciné de manière répétée :
- Des champs de filtrage inexistants —
state.id.orau lieu destate.type.in. Cela semble tellement logique !… mais l’API utilise une tout autre approche. - Des enums inventés — des noms de valeurs qui semblent évidents, mais que l’API n’a jamais définis.
- Des designs issus d’un autre écosystème — sur un projet Rust, elle m’a suggéré d’utiliser
fcntl.flockpour verrouiller les fichiers. Ça fonctionne pour Python, mais en Rust, c’est plutôtfs2::FileExt.
Chaque erreur était plausible. Aucun code n’était complétement absurde. Un débutant pourrait facilement faire les mêmes fautes en parcourant rapidement une documentation. Et c’est là où réside le danger : ces erreurs ne ressemblent pas à des hallucinations. Elles donnent l’impression que leur auteur a des notions sur le sujet, mais… ne sait pas tout à fait.
Pourquoi les LLMs inventent-ils les APIs ?
Pour le dire simplement : le LLM ne connaît pas les champs spécifiques que ton API propose. Ce qu’il connaît, ce sont des milliers d’APIs GraphQL qu’il a vues durant son entraînement. Quand tu lui demandes d’en utiliser une, il fait ce qu’un humain intuitif mais peu regardant ferait en l’absence de documentation : il devine.
Et il devine souvent bien. Presque toujours. Juste assez bien pour te mettre en confiance. Mais c’est ce « presque » qui peut faire dérailler toute ton avancée dans un projet.
C’est un peu comme travailler avec un collègue brillant mais qui ne lit jamais la documentation. Il te dit : « Oui, l’endpoint accepte un champ nommé priority » avec tant d’assurance que tu n’y réfléchis pas plus longtemps. Et lorsque ça tombe en panne en production, tu réalises qu’il l’a tout simplement inventé.
La solution : la programmation adversariale
Après une troisième hallucination en une semaine, j’ai pris un chemin différent. Plutôt que de faire confiance et de vérifier ensuite, j’ai commencé à me méfier et à vérifier avant. Je l’appelle la programmation adversariale : code comme si ton copilote allait inventer des choses.
Ce n’est pas de l’hostilité. C’est une question de discipline.
1. Inspection des schémas avant d’écrire du code
Si tu travailles avec une API GraphQL, télécharge toujours ses schémas avant de demander n’importe quoi à ton IA :
# Télécharger le schéma complet de l'API
curl -s https://api.example.com/graphql \
-H "Authorization: token-here" \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name fields { name type { name kind ofType { name } } } } }"}' \
> schema.json
Tu as la vérité en main. Si l’IA te dit « utilise orderBy: { priority: ASC } », tu peux vérifier dans le schéma qu’il n’existe tout simplement pas de champ appelé priority. Fournis à l’IA un extrait du schéma pertinent et demande-lui d’utiliser uniquement ces champs. Fini les devinettes.
Pour les APIs REST, télécharge le fichier OpenAPI. Peu importe le type d’API : l’idée reste la même, trouve et utilise toujours la source de vérité avant de commencer à coder.
2. Fixtures réels, pas inventés
Deuxième rempart : captures des réponses réelles de l’API et stocke-les comme fixtures pour tests :
# Capturer une réponse réelle
curl -s https://api.example.com/graphql \
-H "Authorization: token-here" \
-d '{"query":"{ items(first: 5) { nodes { id title state { name } } }"}' \
> tests/fixtures/items_real.json
Ce fichier JSON ne vient pas d’un LLM. Il provient directement de l’API réelle. Tous les champs, types et valeurs affichés sont authentiques. Lorsque tu développes un parseur, teste-le sur cette fixture. Si ton DTO ne peut pas désérialiser la réponse réelle, le test échoue. Fin des hypothèses fictives.
La clé ici, c’est la discipline : ne laisse jamais l’IA générer des fixtures. Sinon tu te retrouves à tester une invention contre une invention. Un château de cartes parfait qui s’effondre dès qu’il est exposé à la réalité.
3. Séparer fetch du traitement
Voilà une astuce essentielle côté architecture. Si ton code combine fetch + parser + transformation dans une seule opération, ça devient compliqué de tester le parseur sans réseau. Et si tu ne peux pas tester sans réseau, tu dois introduire des mocks… ce qui nous ramène au problème des fixtures inventés.
La solution, c’est de diviser en deux couches :
┌─────────────────────┐
│ Client (fetch) │ ← Communication avec l'API réelle
│ Que du HTTP + JSON│
└────────┬────────────┘
│ JSON brut
┌────────▼────────────┐
│ Processor │ ← Parse, transforme, formate
│ Que les données│
└─────────────────────┘
La couche client reste simple : elle fait uniquement les requêtes HTTP et renvoie le JSON brut. Ensuite, le processor prend ce JSON et le transforme. Pour tester le processor, t’utilises simplement les fixtures réelles. Sans besoin d’utiliser la couche HTTP, sans réseau. Il n’y a donc aucune chance que l’IA invente la structure ou le contenu du JSON, puisque tu la connais déjà.
4. Liste de contrôle pour la programmation adversariale
Avant d’accepter du code interagissant avec une API externe, vérifie ces points :
| Question | Si la réponse est non… |
|---|---|
| Ai-je le schéma/spec de l’API dans le projet ? | Télécharge-le avant de continuer |
| Les champs utilisés existent-ils dans le schéma ? | Cherche-les. Si absents, l’IA les a inventés |
| Les fixtures du test proviennent-elles bien de l’API réelle ? | Va les chercher. Dis à l’IA de ne pas les simuler |
| Puis-je tester le parseur sans accès réseau ? | Sépare faisceau de la partie processing |
| Les types du langage correspondent-ils à ceux de l’API ? | Compare ton struct/DTO avec le schéma |
Cinq questions. Trente secondes pour vérifier. Cela t’évitera de précieuses heures à déboguer un code illusoire.
Le brutal retour sur soi (dogfooding)
[…] Exemples montrant punitif d’intégrations continuer local stipulé retraite.
[…] (Remplace- Une expansion Texte Demandez hélas …" suite !!.
détail pas résumé ici “). ***.