API hallucinée.
Code généré par IA qui appelle des fonctions, méthodes, paramètres, clés de configuration ou paquets qui semblent plausibles mais n'existent pas dans la version réelle de la bibliothèque dont vous dépendez.
##Signes et symptômes
Un relecteur repère une API hallucinée quand le code se lit avec fluidité et « ressemble » à un usage idiomatique d'une bibliothèque, alors que l'appel, l'option ou l'import précis est introuvable dans l'API réelle de cette bibliothèque. Signes révélateurs :
- Une méthode ou une option dont le nom est trop pratique — elle fait exactement ce que le prompt demandait, avec un nom qui mélange deux API réelles (par ex.
findLastWhere,includesAll,parseDateSafe). - Des paramètres inventés sur une fonction réelle (la variante dangereuse — elle compile souvent et n'échoue qu'à l'exécution, ou ignore silencieusement l'option).
- Un
importd'un paquet qui n'est pas danspackage.json/requirements.txt, ou un import nommé que le module n'exporte jamais. - Des formes d'API qui mélangent les versions : une signature v2 appelée sur un client v5, ou une méthode supprimée/renommée plusieurs versions auparavant.
- Des commentaires inline confiants affirmant que l'appel est correct (« // renvoie la charge remboursée »).
// Généré par IA — fluide, plausible, et faux
import { formatRelative } from 'date-fns';
// `roundingMethod` est inventé ici — formatRelative n'accepte aucune option de ce nom (elle appartient à formatDistanceStrict), elle est donc silencieusement ignorée
const label = formatRelative(date, new Date(), { roundingMethod: 'floor' });
// `findLastWhere` n'existe pas sur Array — TypeError à l'exécution
const lastActive = items.findLastWhere(i => i.active);
// Méthode de SDK inventée : stripe-node n'a pas de refunds.createForCharge — le vrai appel est stripe.refunds.create({ charge, amount })
await stripe.refunds.createForCharge(chargeId, { amount: 500 });
Une heuristique rapide : si vous ne pouvez pas pointer vers l'entrée de documentation ou la définition de type d'un appel tiers en moins d'une minute, traitez-le comme halluciné jusqu'à preuve du contraire.
##Causes du problème
Pourquoi les modèles le produisent
- Plausibilité du token suivant, pas une recherche. Un LLM prédit la continuation statistiquement la plus probable, ce qui revient à la moyenne de chaque API similaire qu'il a vue. Cette moyenne est souvent une méthode qui devrait exister — le modèle émet « le nom pratique » plutôt que le vrai. Dans une étude sur la recommandation d'API pour Java 8 (JRE), 58,1 % à 84,1 % des API recommandées par trois modèles n'existaient pas dans la bibliothèque indiquée, et la plupart des erreurs portaient sur des noms de méthodes inexistants (arXiv 2409.15228).
- Mélange de versions. Les corpus d'entraînement mêlent de nombreuses versions d'une bibliothèque, donc le modèle fusionne les signatures v2 et v5 en une seule qui ne correspond à aucune. Les conflits de connaissance d'API, comme un appel avec un paramètre inexistant, forment une catégorie documentée d'hallucination au niveau du dépôt (Zhang et al., ISSTA 2025), et les auteurs notent que certaines hallucinations peuvent passer les vérifications statiques et l'ensemble des cas de test.
- Obsolescence liée à la date limite d'entraînement. Le modèle utilise avec assurance des API renommées, dépréciées ou supprimées depuis sa date limite, et se rabat sur ce qui apparaissait le plus souvent dans son corpus d'entraînement — parfois une version de la bibliothèque plus ancienne que celle que vous avez installée.
- Aucun contexte de dépôt/dépendances. Sans votre
package.jsonni la source réelle du module, le modèle invente des helpers qui « semblent » appartenir à votre stack. - Complaisance / empressement à répondre. L'assistant ne dit presque jamais « je ne suis pas sûr que cette méthode existe » — il produit un code confiant et d'apparence exécutable, ce qui abaisse la méfiance du relecteur.
- Le déterminisme le rend exploitable. L'hallucination de paquet n'est pas un bruit aléatoire : sur 16 modèles et 576 000 échantillons de code (2,23 M de références à des paquets), 19,7 % des références à des paquets étaient hallucinées (205 474 noms uniques inexistants), et 58 % des paquets hallucinés réapparaissaient plus d'une fois sur 10 relances du même prompt (Spracklen et al., USENIX Security 2025 ; synthèse SecurityWeek).
Pourquoi c'est nuisible
- Exactitude. Des paramètres inventés et des options silencieusement ignorées produisent un comportement erroné sur des chemins de code que les tests couvrent rarement ; la défaillance apparaît en production, pas à la compilation.
- Sécurité / chaîne d'approvisionnement. Un nom de paquet halluciné est une cible d'enregistrement : les attaquants publient des logiciels malveillants sous le nom prédit, de sorte que le prochain développeur qui accepte la suggestion l'installe — l'attaque slopsquatting (terme forgé par Seth Larson de la PSF). La variante d'obsolescence réintroduit silencieusement des API dépréciées ou porteuses de CVE.
- Charge de revue. Un code plausible reporte le fardeau sur les relecteurs, qui doivent vérifier chaque appel inhabituel dans la documentation ; une prose fluide rend cette vérification moins probable.
- Accumulation de dette technique. Les développeurs collent souvent le stub halluciné « presque fonctionnel » et bricolent autour plutôt que de corriger l'appel à la racine — alimentant la tendance plus large de l'ère IA : hausse du copier-coller et baisse de la refactorisation (GitClear 2025 : lignes déplacées/refactorisées 25 % (2021) → moins de 10 % (2024) ; copier-coller 8,3 % → 12,3 % ; blocs dupliqués de 5+ lignes ~8× en 2024).
##Traitement
Tactiques de processus & de prompting
- Ancrez le modèle dans l'API réelle. Collez les vrais stubs de types, la page de documentation pertinente ou la source de la version installée dans le contexte, ou utilisez un outil de récupération de documentation (style Context7 / RAG). Indiquez-lui les versions exactes des dépendances issues de votre fichier de verrouillage.
- Exigez des citations. Demandez au modèle de nommer l'entrée de documentation officielle ou la signature de type pour chaque appel tiers qu'il utilise. Règle empirique des praticiens : chaque appel de méthode sur une bibliothèque tierce doit être retracé jusqu'à son entrée de documentation avant l'approbation de la PR.
- Faites-le tourner, dans la boucle. Exigez que
tsc/mypy/pylint, le build et la suite de tests passent, et faites en sorte que l'agent exécute réellementnpm install/pip installpour qu'un paquet halluciné échoue tôt plutôt que d'atteindre la revue. - Demandez-lui de réutiliser, pas d'inventer. Donnez-lui un
grepdu module existant et demandez-lui d'appeler les helpers existants (réutilise les helpers de utils/http.ts) au lieu d'en conjurer de nouveaux — cela contre aussi le smell connexe de duplication par helper réinventé. - Verrouillez les dépendances. Utilisez un fichier de verrouillage + une liste blanche d'installation et un scanner de chaîne d'approvisionnement (Socket/Snyk) avant l'ajout de tout nouveau paquet, afin que des noms slopsquattés ne puissent pas se glisser.
Le refactoring du code
Remplacez l'appel inventé par le véritable, vérifié. Si vous voulez vraiment la commodité que le modèle a imaginée, implémentez-la une seule fois contre l'API réelle derrière un wrapper obtenu par Extraire une méthode au lieu de disperser l'appel fictif.
// Avant — méthode de SDK inventée + valeur d'option invalide
async function refundLast(chargeId: string) {
// `stripe.refunds.createForCharge` n'existe pas, et 'requested' n'est pas un motif de remboursement valide
return stripe.refunds.createForCharge(chargeId, { amount: 500, reason: 'requested' });
}
// Après — vérifié contre les types/docs du SDK Stripe installé,
// et la « commodité » encapsulée une fois pour ne pas répéter la vraie API
async function refundCharge(chargeId: string, amountCents: number) {
return stripe.refunds.create({
charge: chargeId,
amount: amountCents,
reason: 'requested_by_customer',
});
}
Pour la variante d'obsolescence, traitez un appel déprécié signalé comme une véritable tâche de mise à niveau : passez à l'API actuelle et épinglez la version, au lieu de faire taire l'avertissement.
Limites. Les vérificateurs de types et les résolveurs attrapent le sous-ensemble résoluble (objets typés, imports non résolus). Le reste dynamique/non typé — options inventées sur any, clés de configuration en chaînes, champs de payload REST — n'a aucun détecteur automatisé fiable et doit être vérifié par un humain contre la vraie documentation.
##Détecté par
- eslint-plugin-import import/no-unresolved — Import non résolu
- eslint-plugin-import import/named — Export nommé inexistant
- Pylint no-member (E1101) — Accès à un membre non défini
- mypy attr-defined — Attribut/méthode non défini sur le type