Publié le : 30 janvier 2026
Lors de la création de l'assistance IA pour les performances, le principal défi technique consistait à faire fonctionner Gemini de manière fluide avec les traces de performances enregistrées dans les outils de développement.
Les grands modèles de langage (LLM) fonctionnent dans une "fenêtre de contexte", qui fait référence à une limite stricte sur la quantité d'informations qu'ils peuvent traiter en même temps. Cette capacité est mesurée en jetons. Pour les modèles Gemini, un jeton correspond à peu près à un groupe de quatre caractères.
Les traces de performances sont d'énormes fichiers JSON, souvent de plusieurs mégaoctets. L'envoi d'une trace brute épuiserait instantanément la fenêtre de contexte d'un modèle et ne laisserait pas de place pour vos questions.
Pour que l'assistance IA soit possible pour les performances, nous avons dû concevoir un système qui maximisait la quantité de données utiles pour un LLM avec une utilisation minimale de jetons. Dans cet article de blog, vous découvrirez les techniques que nous avons utilisées pour y parvenir et vous pourrez les adopter pour vos propres projets.
Adapter le contexte initial
Le débogage des performances d'un site Web est une tâche complexe. Un développeur peut examiner la trace complète pour obtenir du contexte, se concentrer sur les Core Web Vitals et les périodes associées de la trace, ou même examiner les détails et se concentrer sur des événements individuels tels que les clics ou les défilements et leurs piles d'appels associées.
Pour faciliter le processus de débogage, l'assistance IA des outils de développement doit correspondre à ces parcours de développement et ne fonctionner qu'avec les données pertinentes pour fournir des conseils spécifiques à l'objectif du développeur. Au lieu d'envoyer systématiquement la trace complète, nous avons intégré une assistance IA pour segmenter les données en fonction de votre tâche de débogage :
| Tâche de débogage | Données initialement envoyées à l'assistance IA |
|---|---|
| Discuter d'une trace des performances | Résumé de la trace : rapport textuel qui inclut des informations générales sur la trace et la session de débogage. Inclut l'URL de la page, les conditions de limitation du débit, les métriques clés de performances (LCP, INP, CLS), une liste des insights disponibles et, le cas échéant, un récapitulatif CrUX. |
| Discuter d'un insight sur les performances | Récapitulatif de la trace et nom de l'insight sur les performances sélectionné. |
| Discuter d'une tâche à partir d'une trace | Récapitulatif de la trace et arbre d'appel sérialisé où se trouve la tâche sélectionnée. |
| Discuter d'une requête réseau | Résumé de la trace, clé de requête et code temporel sélectionnés |
| Générer des annotations de trace | Arbre d'appel sérialisé dans lequel se trouve la tâche sélectionnée. L'arborescence sérialisée identifie la tâche sélectionnée. |
Le récapitulatif de la trace est presque toujours envoyé pour fournir un contexte initial à Gemini, le modèle sous-jacent de l'assistance IA. Pour les annotations générées par IA, il est omis.
Fournir des outils à l'IA
L'assistance IA dans les outils de développement fonctionne comme un agent. Cela signifie qu'il peut interroger de manière autonome pour obtenir plus de données, en fonction de la requête initiale du développeur et du contexte initial partagé avec lui. Pour interroger davantage de données, nous avons fourni à l'assistance IA un ensemble de fonctions prédéfinies qu'elle peut appeler. Un modèle appelé appel de fonction ou utilisation d'outils.
En nous basant sur les parcours de débogage décrits précédemment, nous avons défini un ensemble de fonctions précises pour l'agent. Ces fonctions permettent d'examiner en détail les éléments considérés comme importants en fonction du contexte initial, de la même manière qu'un développeur humain aborderait le débogage des performances. Voici l'ensemble des fonctions :
| Fonction | Description |
|---|---|
getInsightDetails(name) |
Renvoie des informations détaillées sur un insight sur les performances spécifique (par exemple,des détails sur la raison pour laquelle le temps LCP a été signalé). |
getEventByKey(key) |
Renvoie les propriétés détaillées d'un événement spécifique. |
getMainThreadTrackSummary(start, end) |
Renvoie un récapitulatif de l'activité du thread principal pour les limites données, y compris les récapitulatifs descendant, ascendant et tiers. |
getNetworkTrackSummary(start, end) |
Renvoie un récapitulatif de l'activité réseau pour les limites temporelles indiquées. |
getDetailedCallTree(event_key) |
Renvoie l'intégralité de l'arborescence des appels pour un événement de thread principal spécifique dans la trace de performances. |
getFunctionCode(url, line, col) |
Renvoie le code source d'une fonction définie à un emplacement spécifique d'une ressource, annoté avec des données de performances d'exécution issues de la trace de performances. |
getResourceContent(url) |
Renvoie le contenu d'une ressource de texte utilisée par la page (par exemple, HTML ou CSS). |
En limitant strictement la récupération des données à ces appels de fonction, nous nous assurons que seules les informations pertinentes entrent dans la fenêtre de contexte dans un format bien défini, ce qui optimise l'utilisation des jetons.
Exemple d'opération d'agent
Examinons un exemple pratique de la façon dont l'assistance IA utilise l'appel de fonction pour récupérer plus d'informations. Après une requête initiale "Pourquoi cette requête est-elle lente ?", L'assistance IA peut appeler les fonctions suivantes de manière incrémentielle :
getEventByKey: récupère la répartition détaillée du timing (TTFB, temps de téléchargement) de la requête spécifique sélectionnée par l'utilisateur.getMainThreadTrackSummary: vérifiez si le thread principal était occupé (bloqué) au moment où la requête aurait dû démarrer.getNetworkTrackSummary: analysez si d'autres ressources étaient en concurrence pour la bande passante en même temps.getInsightDetails: vérifiez si le récapitulatif Trace mentionne déjà un insight lié à cette requête en tant que goulot d'étranglement.
En combinant les résultats de ces appels, l'assistance IA peut ensuite fournir un diagnostic et proposer des mesures concrètes, comme suggérer des améliorations de code à l'aide de getFunctionCode ou optimiser le chargement des ressources en fonction de getResourceContent.
Toutefois, la récupération des données pertinentes ne représente que la moitié du défi. Même si les fonctions fournissent des données précises, celles qu'elles renvoient peuvent être volumineuses. Pour prendre un autre exemple, getDetailedCallTree peut renvoyer un arbre avec des centaines de nœuds. En JSON standard, cela nécessiterait de nombreux { et } juste pour l'imbrication.
Il est donc nécessaire d'utiliser un format suffisamment dense pour être efficace en termes de jetons, mais suffisamment structuré pour qu'un LLM puisse le comprendre et le référencer.
Sérialiser les données
Examinons plus en détail comment nous avons abordé ce défi, en continuant avec l'exemple d'arborescence des appels, car les arborescences des appels représentent la majorité des données dans une trace de performances. Pour référence, les exemples suivants montrent une seule tâche dans une pile d'appels au format JSON :
{
"id": 2,
"name": "animate",
"selected": true,
"duration": 150,
"selfTime": 20,
"children": [3, 5, 6, 7, 10, 11, 12]
}
Une trace de performances peut en contenir des milliers, comme le montre la capture d'écran suivante. Chaque petite boîte de couleur est représentée à l'aide de cette structure d'objet.

Ce format est pratique pour travailler de manière programmatique dans les outils de développement, mais il est inutile pour les LLM pour les raisons suivantes :
- Clés redondantes : les chaînes telles que
"duration","selfTime"et"children"sont répétées pour chaque nœud de l'arborescence des appels. Ainsi, un arbre comportant 500 nœuds envoyé à un modèle consommerait des jetons pour chacune de ces clés 500 fois. - Listes détaillées : lister chaque ID enfant individuellement via
childrenconsomme un nombre considérable de jetons, en particulier pour les tâches qui déclenchent de nombreux événements en aval.
L'implémentation d'un format économe en jetons pour toutes les données utilisées avec l'assistance IA pour les performances a été un processus progressif.
Première itération
Lorsque nous avons commencé à travailler sur l'assistance IA pour les performances, nous avons optimisé la vitesse de livraison. Notre approche de l'optimisation des jetons était basique. Nous avons supprimé les accolades et les virgules du fichier JSON d'origine, ce qui a donné un format comme celui-ci :
allUrls = [...]
Node: 1 - update
Selected: false
Duration: 200
Self Time: 50
Children:
2 - animate
Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children:
3 - calculatePosition
5 - applyStyles
6 - applyStyles
7 - calculateLayout
10 - applyStyles
11 - applyStyles
12 - applyStyles
Node: 3 - calculatePosition
Selected: false
Duration: 15
Self Time: 2
URL: 0
Children:
4 - getBoundingClientRect
...
Mais cette première version n'était qu'une légère amélioration par rapport au JSON brut. Il listait toujours explicitement les enfants des nœuds avec leurs ID et leurs noms, et ajoutait des clés descriptives répétées (Node:, Selected:, Duration:, …) devant chaque ligne.
Optimiser les listes de nœuds enfants
Pour optimiser davantage, nous avons supprimé les noms des enfants de nœuds (calculatePosition, applyStyles, … dans l'exemple précédent). Comme l'assistance IA a accès à tous les nœuds grâce à l'appel de fonction et que ces informations se trouvent déjà dans l'en-tête du nœud (Node: 3 - calculatePosition), il n'est pas nécessaire de les répéter. Cela nous a permis de réduire Children à une simple liste d'entiers :
Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3, 5, 6, 7, 10, 11, 12
..
Bien que cela ait représenté une nette amélioration par rapport à avant, il était encore possible d'optimiser davantage. En examinant l'exemple précédent, vous remarquerez peut-être que Children est presque séquentiel, avec seulement 4, 8 et 9 manquants.
En effet, lors de notre première tentative, nous avons utilisé un algorithme de recherche en profondeur (DFS) pour sérialiser les données arborescentes à partir de la trace de performances. Cela a entraîné des ID non séquentiels pour les nœuds frères, ce qui nous a obligés à lister chaque ID individuellement.
Nous avons réalisé que si nous réindexions l'arborescence à l'aide de la recherche en largeur (BFS), nous obtiendrons des ID séquentiels, ce qui permettra une autre optimisation. Au lieu de lister des ID individuels, nous pouvons désormais représenter des centaines d'enfants avec une seule plage compacte, comme 3-9 pour l'exemple d'origine.
La notation finale du nœud, avec la liste Children optimisée, se présente comme suit :
allUrls = [...]
Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3-9
Réduire le nombre de clés
Une fois les listes de nœuds optimisées, nous nous sommes penchés sur les clés redondantes. Nous avons commencé par supprimer toutes les clés du format précédent, ce qui a donné le résultat suivant :
allUrls = [...]
2;animate;150;20;0;3-10
Bien que ce format soit efficace en termes de jetons, nous devions tout de même donner à Gemini des instructions sur la façon de comprendre ces données. Par conséquent, la première fois que nous avons envoyé un arbre d'appel à Gemini, nous avons inclus la requête suivante :
...
Each call frame is presented in the following format:
'id;name;duration;selfTime;urlIndex;childRange;[S]'
Key definitions:
* id: A unique numerical identifier for the call frame.
* name: A concise string describing the call frame (e.g., 'Evaluate Script', 'render', 'fetchData').
* duration: The total execution time of the call frame, including its children.
* selfTime: The time spent directly within the call frame, excluding its children's execution.
* urlIndex: Index referencing the "All URLs" list. Empty if no specific script URL is associated.
* childRange: Specifies the direct children of this node using their IDs. If empty ('' or 'S' at the end), the node has no children. If a single number (e.g., '4'), the node has one child with that ID. If in the format 'firstId-lastId' (e.g., '4-5'), it indicates a consecutive range of child IDs from 'firstId' to 'lastId', inclusive.
* S: **Optional marker.** The letter 'S' appears at the end of the line **only** for the single call frame selected by the user.
....
Bien que cette description de format entraîne un coût en jetons, il s'agit d'un coût statique payé une seule fois pour l'ensemble de la conversation. Le coût est compensé par les économies réalisées grâce aux optimisations précédentes.
Conclusion
L'optimisation de l'utilisation des jetons est un élément essentiel à prendre en compte lorsque vous créez des applications avec l'IA. En passant du format JSON brut à un format personnalisé spécialisé, en réindexant les arbres avec la recherche en largeur et en utilisant les appels d'outils pour extraire les données à la demande, nous avons considérablement réduit le nombre de jetons consommés par l'assistance IA dans les outils pour les développeurs Chrome.
Ces optimisations étaient un prérequis pour activer l'assistance IA pour les traces de performances. En raison de sa fenêtre de contexte limitée, il ne pourrait pas gérer l'énorme volume de données. Toutefois, le format optimisé permet à un agent performant de conserver un historique de conversation plus long et de fournir des réponses plus précises et contextuelles sans être submergé par le bruit.
Nous espérons que ces techniques vous inciteront à réexaminer vos propres structures de données lorsque vous concevez des produits pour l'IA. Pour commencer à utiliser l'IA dans les applications Web, consultez Apprendre l'IA sur web.dev.