Le contexte : ce que l'assistant voit

Temps de lecture15 min

En bref

Résumé de l’article

Un LLM n’a pas vraiment de mémoire entre deux demandes et aucune connaissance de votre projet. Tout ce qu’il sait de votre code, au moment où il répond, tient dans le texte qu’on lui a transmis : c’est ce qu’on appelle le contexte.

Cet article explique ce que contient ce contexte, comment on le remplit dans VS Code, et pourquoi c’est de très loin le levier qui a le plus d’effet sur la qualité de ce que vous obtenez, bien avant le choix du modèle. C’est aussi ce qui explique la différence de résultat entre un assistant intégré à l’éditeur et une discussion dans un navigateur.

Points clés à retenir

  • Le modèle ne connaît que ce qu’on lui envoie. Hors du contexte, votre projet n’existe pas pour lui.
  • Sans contexte, l’assistant réécrit. Il produit une fonction qui fait le travail, en ignorant celle que vous aviez déjà.
  • Le contexte se remplit tout seul, mal. L’outil devine à partir des fichiers ouverts ; le désigner explicitement change le résultat.
  • Votre style fait partie du contexte. La complétion reproduit vos conventions, parce qu’elle les lit juste au-dessus du curseur.
  • Un fichier d’instructions de projet vaut mieux que de répéter les mêmes consignes à chaque demande.

Contenu de l’article

Ce qu’est le contexte

À chaque requête, l’extension construit un message qui contient : des instructions générales fournies par l’éditeur, votre demande, l’historique de la conversation, et un ensemble d’extraits de votre projet. Le modèle lit ce message et produit une réponse, sans conserver de mémoire pour les requêtes suivantes.

Cela a une conséquence importante : il n’y a pas de « l’assistant connaît mon projet ». Il y a seulement « l’extension a réussi, ou non, à mettre dans le message les bons morceaux de mon projet ». La qualité de la réponse dépend largement de cette sélection.

La taille du contexte est limitée : quelques dizaines à quelques centaines de milliers de jetons selon les modèles. Un gros projet n’y tient pas. Il faut donc choisir, et ce choix vous revient au moins en partie.

Comment le contexte se remplit

Dans VS Code, trois mécanismes s’empilent.

Automatiquement. L’extension joint le fichier courant, souvent les onglets ouverts, parfois les fichiers récemment consultés. C’est commode et c’est le comportement par défaut, mais vous ne contrôlez pas ce qui entre.

Explicitement. Dans la fenêtre de discussion, vous désignez ce que vous voulez joindre :

Référence Ce qu’elle joint
#file un fichier que vous choisissez
#selection la portion sélectionnée dans l’éditeur
#codebase des extraits que l’outil va chercher lui-même dans le projet
#terminalLastCommand la dernière commande et sa sortie
@workspace une question portant sur l’ensemble du projet

Durablement. Un fichier d’instructions à la racine du projet (.github/copilot-instructions.md pour Copilot, AGENTS.md pour plusieurs autres outils) est joint à chaque demande. C’est là qu’on écrit ce qu’on ne veut pas répéter : la version de Python visée, la convention de nommage, l’obligation d’annoter les types, l’interdiction d’ajouter des dépendances.

Astuce

Un fichier d’instructions utile est court et impératif. Une dizaine de lignes suffisent :

# Instructions pour ce projet

- Python 3.12, annotations de type obligatoires sur les fonctions publiques.
- Docstrings au format NumPy.
- Pas de nouvelle dépendance sans justification explicite.
- Réutiliser les fonctions existantes de `utils.py` plutôt que d'en écrire de nouvelles.

Pourquoi c’est le levier principal

Prenez une demande banale, que vous écririez à un moment pendant le développement d’une application de guidage GPS : « écris-moi une fonction qui calcule la distance entre deux points d’une carte ».

  • Sans contexte, le modèle produit une fonction correcte et autonome. Elle recalcule un plus court chemin de zéro, avec sa propre file de priorité et ses propres conventions de nommage. Elle marche. Elle fait doublon avec les cent lignes que vous aviez déjà écrites.
  • Avec le bon contexte (par exemple, le fichier où se trouve votre parcours de graphe), le modèle voit qu’une fonction équivalente existe, et il l’appelle au lieu de la réécrire.

La différence ne tient ni au modèle, ni à la formulation de la demande. Elle tient à ce que le modèle avait sous les yeux. C’est aussi la raison pour laquelle copier-coller du code depuis un chat externe produit du code redondant. Ce n’est pas que l’outil soit moins bon : c’est qu’il ne voit pas votre projet.

Le contexte de la complétion

La complétion au fil de la frappe obéit au même principe, avec un contexte beaucoup plus petit : ce qui précède le curseur, ce qui le suit, et quelques fichiers voisins.

D’où un effet facile à observer : elle imite ce que vous venez d’écrire. Si vos trois dernières fonctions sont annotées et documentées en français, la quatrième vous sera proposée annotée et documentée en français. Si vous avez pris l’habitude de nommer vos accumulateurs total, la suggestion nommera son accumulateur total.

Cela se retourne dans les deux sens :

  • En votre faveur : écrire soigneusement les deux premières fonctions d’un module est le moyen le plus efficace de faire produire les suivantes dans le même style.
  • Contre vous : une convention bancale, ou un bout de code écrit à la va-vite, se propage de la même façon. La complétion n’a aucun jugement sur ce qu’elle imite.

Récapitulatif pratique

Quand une réponse vous déçoit, posez-vous les questions dans cet ordre :

  1. Le modèle avait-il l’information ? Neuf fois sur dix, c’est là que ça se joue. Joignez le fichier concerné et recommencez.
  2. La demande est-elle vérifiable ? « Améliore ce code » ne peut pas donner un bon résultat ; « ajoute les annotations de type et gère le cas de la liste vide » le peut.
  3. Le modèle est-il adapté à la tâche ? C’est la dernière question, pas la première.

Pour aller plus loin

Comment #codebase trouve les bons extraits

Quand vous laissez l’outil chercher lui-même dans le projet, il utilise principalement une recherche sémantique. Le projet est découpé en morceaux, et chaque morceau est transformé en un vecteur de nombres (appelé embedding), calculé de telle sorte que deux textes de sens proche donnent des vecteurs proches. Votre demande est transformée de la même manière, et l’outil retient les morceaux dont les vecteurs sont les plus proches du sien. Lorsque cet index n’est pas disponible, l’outil se rabat sur des recherches textuelles classiques.

Cette technique, qui consiste à aller chercher des documents pertinents pour les joindre à la demande, est appelée génération augmentée par récupération (en anglais, Retrieval-Augmented Generation, ou RAG). Elle a une conséquence pratique : un code bien nommé et bien documenté est plus facile à retrouver. Une fonction shortest_path(...) dont la docstring parle de distance a de bonnes chances d’être trouvée quand vous demandez « la distance entre deux cases ». Une fonction f2(...) sans docstring, beaucoup moins.

Plus de contexte n’est pas toujours mieux

Il est tentant de tout joindre, au cas où. Ce n’est pas une bonne idée, pour deux raisons :

  • Le coût. Chaque jeton de contexte est lu par le modèle, et donc payé (voir la fiche Crédits et modèles).
  • La qualité. Une étude de 2023 (Lost in the Middle, voir ci-dessous) a montré que les modèles exploitent mieux une information placée au début ou à la fin d’un long contexte qu’au milieu. Noyer la bonne information dans des fichiers inutiles peut donc dégrader la réponse.

En pratique, cela vaut aussi pour les conversations longues : chaque échange s’ajoute au contexte des suivants. Quand vous changez de tâche, ouvrez une nouvelle conversation plutôt que de continuer dans une conversation encombrée de sujets précédents.

Des instructions ciblées

En plus du fichier d’instructions global, VS Code permet d’écrire des instructions qui ne s’appliquent qu’à certains fichiers. Ce sont des fichiers .instructions.md, placés dans le dossier .github/instructions, dont l’en-tête indique à quels fichiers ils s’appliquent :

​
---
applyTo: "tests/**/*.py"
---

- Utiliser unittest, comme dans le reste du projet.
- Chaque méthode de test vérifie un seul comportement.
- Toujours inclure un test pour le cas d'un labyrinthe sans chemin vers le fromage.

Ces instructions ne sont jointes que lorsque l’assistant travaille sur un fichier de tests. Cela évite d’encombrer le contexte de règles inutiles pour le reste du code.

Pour aller encore plus loin

  • Le site du format AGENTS.md, reconnu par plusieurs outils d’IA pour programmer (en anglais).
  • L’article de recherche de Liu et al. (2023) sur la manière dont les modèles exploitent, ou non, les longs contextes (en anglais).