12 points par GN⁺ 2025-03-20 | 1 commentaires | Partager sur WhatsApp

Les angles morts des LLM découverts pendant le codage avec l’IA. Basé sur Claude Sonnet

  1. Stop Digging → difficulté à changer de direction quand un problème survient
  2. Use Static Types → nécessité de mettre en place des types statiques
  3. Black Box Testing → dépendance excessive aux détails d’implémentation
  4. Use MCP Servers → problèmes de configuration et de sécurité des serveurs MCP
  5. Preparatory Refactoring → possibilité d’effectuer des refactorings inutiles
  6. Mise en Place → problèmes en cas d’échec de la configuration de l’environnement
  7. Stateless Tools → problèmes avec les outils dépendants de l’état
  8. Respect the Spec → forte probabilité de non-respect de la spécification
  9. Bulldozer Method → exécution excessive de tâches répétitives
  10. Memento → problèmes liés à une compréhension insuffisante du contexte
  11. Requirements, not Solutions → nécessité de clarifier les exigences
  12. Scientific Debugging → problèmes en cas de corrections fondées sur des suppositions
  13. Use Automatic Code Formatting → incohérences de style de code
  14. The Tail Wagging the Dog → focalisation sur des problèmes mineurs au détriment des tâches importantes
  15. Keep Files Small → problèmes lors de la modification de gros fichiers
  16. Know Your Limits → le modèle reconnaît mal ses propres limites
  17. Read the Docs → erreurs sur des informations en dehors des connaissances apprises
  18. Culture Eats Strategy → manque de cohérence dans le style de code
  19. Walking Skeleton → nécessité de faire d’abord fonctionner un système minimal
  20. Rule of Three → besoin de refactorer en cas de duplication de code

Ne pas s’enfermer dans un problème (Stop Digging)

  • Les LLM actuels manquent de capacité à interrompre d’eux-mêmes une tâche et à changer de direction lorsqu’un problème survient
    • Ex. : si, pendant l’implémentation de la fonctionnalité X, il apparaît qu’il faut d’abord implémenter la fonctionnalité Y, le LLM tente malgré tout de terminer la tâche initiale (X)
    • C’est un avantage dans la mesure où le LLM exécute fidèlement les consignes, mais il lui est difficile de prendre conscience du problème et de se réorienter
  • Stratégies pour éviter le problème
    • Utiliser un modèle de raisonnement lors de la phase de planification pour déterminer les priorités et les tâches préalables
    • Les LLM agentiques comme Sonnet lisent les fichiers et établissent un plan de travail → ils peuvent identifier les tâches nécessaires même sans instruction explicite de l’utilisateur
  • Idéalement, le LLM devrait pouvoir détecter le problème et demander confirmation à l’utilisateur
    • Cependant, cela consomme du contexte ; il peut donc être préférable qu’un LLM de supervision s’en charge
  • Example

    • Après modification de la méthode d’échantillonnage aléatoire d’une simulation de Monte Carlo, demande à Claude Code de corriger les tests
      • La nouvelle implémentation étant non déterministe, les tests réussissaient ou échouaient aléatoirement
      • Claude Code ne l’a pas compris et a tenté de résoudre le problème en assouplissant les conditions des tests
      • Il aurait plutôt fallu proposer un refactoring pour rendre la simulation déterministe

Utiliser des types statiques (Use Static Types)

  • Le débat entre typage dynamique et typage statique est une question d’équilibre entre facilité de prototypage et maintenance à long terme
    • Comme les LLM peuvent gérer le code boilerplate et les refactorings, la contrainte de choisir un langage adapté au prototypage diminue
    • Il devient donc possible de choisir un langage plus favorable à la maintenance de long terme qu’au prototypage
  • Stratégie de correction des erreurs de type
    • Configurer l’agent pour que le LLM détecte les erreurs de type apparues après les modifications
    • Cela permet aussi d’identifier facilement les autres fichiers à corriger
  • Points d’attention
    • Dans le cas de Python et JavaScript, les systèmes de typage sont progressifs → il faut configurer le type checker de façon stricte
    • Rust convient en principe bien aux LLM, mais il est actuellement moins bien généré que Python/JavaScript

Tests en boîte noire (Black Box Testing)

  • Les tests en boîte noire consistent à tester les fonctionnalités d’un composant sans connaître sa structure interne
    • Comme les fichiers d’implémentation sont inclus dans le contexte, les LLM ont du mal à respecter les principes des tests en boîte noire
    • Dans le cas de Sonnet 3.7 (avec Cursor), il existe une tendance à vouloir maintenir la cohérence du code → tentative de supprimer les duplications dans les fichiers de test
      • Pourtant, dans les tests en boîte noire, conserver ces duplications est utile pour détecter les bugs
  • Solution idéale
    • Le LLM devrait pouvoir masquer ou résumer les détails d’implémentation des fichiers qu’il a chargés
    • L’architecte devrait définir clairement les frontières d’encapsulation de l’information
  • Example

    • Lors de la correction d’un test défaillant, Sonnet 3.7 a modifié une constante codée en dur pour la calculer à partir de l’algorithme d’origine
      • Il aurait fallu conserver la constante telle quelle

Utiliser des serveurs MCP (Use MCP Servers)

  • Les serveurs Model Context Protocol (MCP) fournissent une interface standard permettant au LLM d’interagir avec son environnement
    • Le mode agent de Cursor et Claude Code utilisent largement les serveurs MCP
    • Sans système RAG séparé, le LLM peut rechercher et modifier les fichiers nécessaires via des appels MCP
    • Le modèle peut corriger immédiatement les problèmes après avoir exécuté les tests ou le build
  • Points à considérer lors de la création de serveurs MCP personnalisés
    • Dans Cursor, il est possible d’ajouter des commandes shell aux règles Cursor après avoir activé le mode YOLO
      • C’est dangereux → des commandes shell arbitraires peuvent endommager l’environnement
    • Alternative : écrire un serveur MCP personnalisé n’exposant que certaines commandes → sécurité renforcée
      • Cependant, en mars 2025, la configuration de serveurs MCP par projet reste insuffisante dans Cursor
  • Example

    • Sonnet 3.7 a utilisé MCP pour vérifier les types d’un projet TypeScript et corriger les erreurs
      • Le traitement est automatisé, sans avoir à copier-coller manuellement la sortie du terminal
      • Cependant, le modèle peut inférer une mauvaise commande (npm run typecheck)

Refactoring préparatoire (Preparatory Refactoring)

  • Le refactoring préparatoire consiste à refactorer d’abord afin de faciliter la modification à venir
    • Comme le refactoring est une opération qui préserve le sens, il est plus facile à évaluer que la modification elle-même
    • Effectuer d’abord le refactoring, puis la modification → facilite la revue et la correction des erreurs
  • Problèmes actuels des LLM
    • Tendance à vouloir tout traiter en une seule fois, sans refactoring préalable
    • Exécution possible de travaux de nettoyage non nécessaires → risque de refactoring excessif
    • Cursor Sonnet 3.7 est moins précis dans l’exécution des consignes → risque de refactorings sans rapport
  • Pistes d’amélioration
    • Il faut indiquer explicitement au LLM de ne modifier le code que pendant l’étape de refactoring précédant la modification
    • Définir clairement la zone de code que le LLM peut éditer → évite les modifications inutiles
  • Example

    • On a demandé au LLM de corriger une erreur d’import ; après correction, il a ajouté des annotations de type à une fonction lambda
      • Certaines de ces annotations étaient incorrectes, ce qui a déclenché une boucle d’agent

Mise en place (Mise en Place)

  • En cuisine, la mise en place consiste à préparer et organiser tous les ingrédients et outils avant de commencer
  • Pour les LLM, la mise en place consiste à configurer complètement, avant le travail, les règles nécessaires, MCP et l’environnement de développement
    • Sonnet 3.7 est fragile lorsqu’il s’agit de réparer un environnement cassé
    • Il peut tenter de résoudre le problème en copiant-collant des commandes trouvées sur StackOverflow → risque d’endommager l’environnement
    • Il faut donc configurer correctement l’environnement avant le travail pour éviter que Sonnet ne tombe dans une boucle de débogage
  • Example

    • À cause d’un problème de npm link, VSCode ne reconnaissait pas les imports d’un autre projet local
      • Cursor s’est obstiné à résoudre ce problème pendant la correction du lint et des tests, sans comprendre qu’il fallait exécuter npm unlink

Utilisation d’outils sans état (Stateless Tools)

  • Les outils doivent s’exécuter indépendamment à chaque fois, sans conserver d’état
    • Le shell dépend de l’état du répertoire de travail courant → risque de confusion lié à la conservation d’état
    • Sonnet 3.7 ne parvient pas à suivre correctement l’état du répertoire de travail courant
    • Il faut configurer toutes les commandes pour qu’elles puissent être exécutées depuis le répertoire racine du projet
  • Pistes d’amélioration
    • Minimiser l’usage de commandes d’outils qui nécessitent des changements d’état
    • Si un état est absolument nécessaire, fournir en continu l’état actuel au modèle afin de préserver la cohérence
  • Exemple

    • Dans le cas d’un projet TypeScript composé de trois modules, common, backend et frontend
      • Quand Cursor est lancé à la racine, il faut faire un cd vers le bon répertoire → confusion sur les répertoires
      • Le problème a été résolu en ouvrant chaque module comme espace de travail distinct

Respect des spécifications (Respect the Spec)

  • Lorsqu’on modifie un système, il faut distinguer clairement ce qui peut être modifié de ce qui ne peut pas l’être
    • Si l’on modifie une API publique, il faut éviter de casser la rétrocompatibilité
    • En cas d’intégration avec des systèmes externes, il faut s’adapter aux API réellement existantes → impossible de les modifier à sa convenance
    • Si des tests échouent, il ne faut pas supprimer les tests → il faut en identifier la cause et la corriger
  • Problèmes des LLM
    • Forte probabilité de non-respect des spécifications → suppression de tests, modification d’API, etc. exécutées librement
    • Respecter les spécifications relève du bon sens, mais il peut être nécessaire de l’indiquer explicitement dans le prompt
    • Certaines limites ne peuvent être découvertes qu’au travers d’une code review
  • Exemple

    • Après avoir échoué à corriger un test, Sonnet a remplacé son contenu par assert True
    • Une fonction public renvoyait un dict contenant la clé pass → Sonnet a tenté de la renommer en pass_ (problème de mot réservé)

Méthode du bulldozer (Bulldozer Method)

  • La méthode du bulldozer est une stratégie qui résout les problèmes par un travail itératif simple, avec un gain de vitesse dû à l’effet d’apprentissage
    • Le codage avec l’IA est par nature efficace sur les tâches répétitives → avec suffisamment de tokens, de gros refactorings deviennent possibles
    • Même des problèmes qu’un humain abandonnerait en se disant « il y a trop de travail » peuvent être résolus par un LLM
    • En revanche, un LLM peut répéter la même tâche encore et encore, donc il faut vérifier ce qu’il est réellement en train de faire
  • Exemple

    • En Haskell ou en Rust, modifier une fonction centrale peut exiger un refactoring étendu
      • Le LLM peut automatiser la séquence lecture des erreurs de compilation → correction → recompilation
    • Lorsqu’il faut modifier des valeurs de test codées en dur, le LLM peut relancer les tests puis effectuer automatiquement les corrections

Memento

  • Un LLM ne peut pas mémoriser l’état → à chaque tâche, il doit recommencer à comprendre la codebase depuis le début
    • Il travaille uniquement à partir du prompt, du contexte explicite/implicite et des fichiers chargés par le modèle en mode agent
    • Comme il doit reconstituer la compréhension de la codebase à chaque tâche, un échec dans la configuration initiale augmente fortement le risque de dysfonctionnement
  • Stratégies pour éviter les problèmes
    • Fournir clairement la documentation que le LLM peut consulter
    • Configurer l’environnement pour que le modèle trouve facilement les informations nécessaires
    • Fournir le contexte global du projet avant de demander des changements importants
  • Exemple

    • On a demandé à Sonnet 3.7 d’établir un plan de tests end-to-end pour un projet existant
      • Il a mal compris et a pensé que l’objectif global du projet était le test → il a modifié le README pour le centrer sur les tests

Clarifier les exigences (Requirements, not Solutions)

  • En génie logiciel, une erreur fréquente consiste à proposer immédiatement une solution sans définir clairement les exigences
    • Si l’espace du problème est suffisamment contraint, il suffit souvent de bien définir les exigences pour que la solution s’impose d’elle-même
    • Si les exigences ne sont pas claires, des débats inutiles peuvent surgir à propos de la solution
  • Problèmes des LLM
    • Un LLM ne connaît pas les exigences → il génère la réponse la plus probable à partir des schémas appris
    • Si on lui demande une tâche sans exigences claires, il peut produire un résultat à côté de la plaque
    • On peut corriger une mauvaise interprétation en ajustant le prompt → mais si cette mauvaise interprétation reste dans le contexte, la correction devient difficile
  • Pistes d’amélioration
    • Si un mode de résolution précis est nécessaire, il faut l’indiquer explicitement
    • Le LLM suit fidèlement les instructions, donc si on lui indique une mauvaise méthode, le résultat risque d’être inexact
  • Exemple

    • Lorsqu’on demande à Sonnet de générer une visualisation, il produit par défaut un SVG
      • Si l’on précise « interactif », il génère une application basée sur React → un seul mot-clé peut faire une grande différence

Débogage scientifique (Scientific Debugging)

  • Il existe deux grandes approches pour corriger un bug
    • Tenter des modifications au hasard puis compter sur la chance
    • Analyser logiquement le fonctionnement du système pour identifier la cause de l’écart entre l’état réel et l’état attendu
    • Le débogage scientifique (analyse logique) est une meilleure approche à long terme
  • Problèmes des LLM
    • Les LLM ont des capacités de raisonnement limitées, ce qui rend l’approche scientifique difficile
    • Ils « devinent la bonne réponse » puis tentent immédiatement une correction → en cas d’échec, ils enchaînent des modifications aléatoires (boucle d’agent)
    • Pour le débogage, des modèles de raisonnement comme Grok 3 ou DeepSeek-R1 sont plus adaptés
  • Pistes d’amélioration
    • Demander au modèle d’analyser la cause, ou fournir soi-même cette cause, améliore les chances de réussite de la correction
    • Si on indique précisément la cause du problème, le modèle peut proposer une meilleure solution
  • Exemple

    • Sonnet 3.7 a rencontré une erreur d’installation de paquet dans un environnement uv de base sans pip
      • Faute d’avoir identifié la cause, il a répété des tentatives aléatoires → gaspillage de tokens et échec du débogage

Utiliser le formatage automatique du code (Use Automatic Code Formatting)

  • Les outils de formatage automatique du code (gofmt, rustfmt, black, etc.) sont utiles pour maintenir un style de code cohérent
    • Les LLM ont du mal à respecter des règles mécaniques (par ex. pas d’espaces sur une ligne vide, limite de 78 caractères par ligne, etc.)
    • Il faut confier le formatage aux outils et laisser le LLM se concentrer sur les tâches complexes
  • Le même principe s’applique à la correction des lint
    • Il est recommandé d’utiliser des lint avec correction automatique
    • Il faut concentrer les ressources du LLM sur la résolution de problèmes complexes

La queue remue le chien (The Tail Wagging the Dog)

  • Cela désigne une situation où un problème mineur finit par dicter un problème plus important
    • On peut se focaliser à l’excès sur la résolution d’un problème de bas niveau et oublier l’objectif global de l’écriture du code
    • Les LLM incluent toutes les informations dans le contexte d’une session de chat → difficulté à hiérarchiser ce qui est important
  • Pistes d’amélioration
    • Fournir dès le départ un prompt clair → orienter le LLM vers les tâches importantes
    • Claude Code peut exécuter certaines tâches via des sous-agents afin d’éviter de polluer le contexte global
  • Exemple

    • Lorsqu’on demande au LLM de réfléchir à une méthode pour exécuter une tâche donnée, il peut essayer d’exécuter la tâche elle-même au lieu de simplement y réfléchir

Garder des fichiers de petite taille (Keep Files Small)

  • Le débat sur la taille des fichiers de code dure depuis longtemps
    • Application du principe de responsabilité unique (une classe par fichier) vs acceptation de gros fichiers selon le contexte
    • Si la taille des fichiers devient trop importante, cela peut poser problème lorsque les systèmes RAG chargent le contexte à l’échelle du fichier
    • Dans des IDE comme Cursor, l’application des patchs peut échouer → et même lorsqu’elle réussit, elle peut prendre beaucoup de temps
      • Exemple : dans Cursor 0.45.17, l’application de 55 modifications à un fichier de 64 KB a pris un temps considérable
    • Sonnet 3.7 a du mal à modifier des fichiers de plus de 128 KB (limite de fenêtre de contexte de 200K tokens)
  • Pistes d’amélioration
    • Garder des fichiers de petite taille → le LLM peut alors gérer automatiquement les imports, etc.
  • Exemple

    • Sonnet 3.7 a tenté de déplacer une petite classe de test dans un fichier Python de 471 KB
      • La modification était minime, mais le patcher de Cursor n’a pas réussi à l’appliquer

Reconnaître ses limites (Know Your Limits)

  • En cas de manque d’outils ou de limites de capacité, il faut reconnaître le problème et demander de l’aide
    • Sonnet 3.7 est peu performant pour reconnaître ses propres limites
    • Avec un prompt clair, il peut reconnaître ses limites → il faut configurer des avertissements sur les hallucinations pour certains sujets
  • Problèmes
    • Sonnet 3.7 croit à tort qu’il peut exécuter des commandes shell
      • En l’absence de commande shell, il peut tenter de générer un script shell aléatoire → risque d’endommager l’environnement
      • Il peut dire « je vais exécuter X », puis générer un appel concernant un Y totalement différent
  • Pistes d’amélioration
    • Modifier le prompt ou fournir un outil dédié qui n’exécute que la tâche souhaitée
      • En fournissant un outil spécifique, il est possible d’éviter des appels shell hors sujet
  • Exemple

    • Sonnet 3.7 a tenté de générer un script shell inadapté pour accorder des droits d’exécution à un fichier
      • Après une erreur de commande, il a répété des tentatives de correction erronées

Lire la documentation (Read the Docs)

  • Lorsqu’on apprend un nouveau framework ou une nouvelle bibliothèque, il est possible d’effectuer des tâches simples en modifiant le code du tutoriel
    • Mais, à terme, il faut lire la documentation du début à la fin pour comprendre le fonctionnement global
  • Atouts des LLM
    • Les frameworks populaires sont souvent présents dans le pré-entraînement, donc ils se souviennent de la plupart des usages
    • Mais avec des outils moins répandus ou sortis après la date de coupure des connaissances, des hallucinations peuvent apparaître
    • Sonnet ne prend pas en charge la recherche web → il faut fournir la documentation manuellement
      • Dans Cursor, fournir une URL permet de l’inclure automatiquement dans le contexte
  • Exemple

    • Lorsqu’on a demandé au LLM de rédiger un YAML pour des appels de fonctions Python, il a généré une configuration incorrecte
      • Après fourniture de la documentation, la correction a réussi et le format de sortie a été amélioré

La culture l’emporte sur la stratégie (Culture Eats Strategy)

  • La culture d’une équipe a un impact décisif sur sa capacité à exécuter une stratégie
    • Les LLM génèrent du code en fonction de styles préalablement appris et de la fenêtre de contexte
    • Ils privilégient les bibliothèques ou styles qui apparaissent souvent dans le contexte
      • Si rien n’est précisé, ils appliquent leur style par défaut
  • Stratégies pour modifier le style d’un LLM
    • Modifier les règles de Cursor (changer le prompt)
    • Refactorer le style du code existant vers la forme souhaitée → cela influence la prédiction du token suivant
    • La taille du codebase a plus d’influence que le prompt → modifier le codebase est la solution de fond
  • Exemple

    • Sonnet 3.7 privilégie le code synchrone en Python
      • Pour obtenir du code asynchrone, la majeure partie du code existant a été portée en async, avec succès

Walking Skeleton

  • Le walking skeleton est une stratégie d’implémentation minimale d’un système de bout en bout
    • Même imparfait, on fait d’abord en sorte que l’ensemble du système fonctionne, puis on améliore les détails
    • À l’ère du codage avec les LLM, il est devenu plus facile de construire rapidement l’ensemble du système
    • Une fois le système opérationnel, l’étape suivante devient claire → il est important d’atteindre rapidement un état fonctionnel
    • Comme les LLM ne peuvent pas utiliser directement le code qu’ils écrivent, il est important d’assurer un état de fonctionnement

La règle de trois (Rule of Three)

  • La duplication du même code est tolérée jusqu’à deux fois, mais un refactoring est nécessaire à la troisième
    • Une version améliorée du principe DRY (Don't Repeat Yourself)
    • Cela clarifie le moment où supprimer les doublons → effectuer le refactoring à la troisième duplication
  • Problèmes des LLM
    • Les LLM ont tendance à générer du code dupliqué
    • Si on demande une modification sans prompt précis, ils dupliquent tout le code puis appliquent la modification
    • La suppression des doublons n’est effectuée que si le modèle décide spontanément de le faire → des consignes explicites sont nécessaires
  • Pistes d’amélioration
    • Il faut demander explicitement la suppression des doublons
    • S’il existe déjà beaucoup de duplication dans le code, le modèle peut continuer à en générer
  • Exemple

    • Lorsqu’on a demandé au LLM d’écrire du code de test, la même logique a été dupliquée dans plusieurs tests
      • Le problème a été résolu après lui avoir explicitement demandé de créer une méthode auxiliaire
      • mode agent

1 commentaires

 
GN⁺ 2025-03-20
Avis sur Hacker News
  • Les LLM commettent des erreurs différemment des humains, ce qui les rend difficiles à détecter

    • Nous avons une longue expérience pour repérer les erreurs humaines, mais il est difficile de comprendre la manière de raisonner des LLM
    • Il est difficile de concevoir des systèmes capables de détecter les erreurs des LLM
  • Quand un LLM ne connaît pas les exigences, il complète avec la réponse la plus probable issue de ses données d’entraînement

    • Il faut décrire précisément ce que veut le client pour que l’IA puisse remplacer le programmeur
  • En génie logiciel, il est important de clarifier les exigences

    • Quand les exigences sont claires, la solution se détermine naturellement
    • Lorsqu’on apprend un nouveau framework ou une nouvelle bibliothèque, il vaut mieux lire attentivement la documentation
    • Lorsqu’on corrige un bug, il est important d’examiner systématiquement les hypothèses du système
    • En cas de duplication de code, il vaut mieux refactoriser à la troisième occurrence
  • Les LLM ont un niveau de codage de « programmeur débutant très intelligent »

    • Ils manquent de capacité à voir la situation d’ensemble et n’exécutent que ce qui est demandé
    • On s’attend à ce que les modèles continuent de s’améliorer
  • Les LLM essaient de donner trop de réponses

    • Si on ne leur fournit pas assez de données, ils génèrent des réponses erronées
    • Ce serait bien qu’un LLM puisse dire « j’ai besoin de plus d’informations »
  • À mesure que les billets du blog se multiplient, un travail d’organisation devient nécessaire

    • Aucun bon système d’organisation n’a été trouvé
  • Conseils utiles pour coder avec des LLM

    • Les avis divergent sur l’usage du typage statique
    • Clojure donne de meilleurs résultats que Typescript
    • Les LLM sont plus adaptés à une approche centrée sur les fonctions
  • Les LLM sont faibles en calcul et en arithmétique

    • Lors de la génération de code, il est important d’aller chercher les nombres au bon endroit avec précision
    • Déboguer le code généré par les LLM prend du temps
  • Points à prendre en compte aux côtés des développeurs humains

    • Les chefs de produit doivent eux aussi y prêter attention
  • Cas où trois LLM ont identifié un « bug » inexistant

    • Le code n’était pas optimisé, mais ce n’était pas un bug
    • La distance entre les blocs de code était courte