Gepubliceerd: 30 januari 2026
Bij het ontwikkelen van AI-ondersteuning voor prestatieanalyse was de grootste technische uitdaging ervoor te zorgen dat Gemini soepel samenwerkte met prestatiegegevens die in DevTools waren vastgelegd.
Grote taalmodellen (LLM's) werken binnen een 'contextvenster', wat verwijst naar een strikte limiet op de hoeveelheid informatie die ze tegelijk kunnen verwerken. Deze capaciteit wordt gemeten in tokens. Voor Gemini-modellen is één token ongeveer een groep van vier tekens.
Prestatietraceringen zijn enorme JSON-bestanden, vaak van meerdere megabytes groot. Het versturen van een onbewerkte tracering zou het contextvenster van een model direct uitputten en geen ruimte meer overlaten voor uw vragen.
Om AI-ondersteuning voor prestatieverbetering mogelijk te maken, moesten we een systeem ontwerpen dat de hoeveelheid bruikbare data voor een LLM maximaliseert met minimaal tokengebruik. In deze blog lees je meer over de technieken die we hiervoor hebben gebruikt en kun je ze toepassen in je eigen projecten.
Pas de begincontext aan
Het debuggen van de prestaties van een website is een complexe taak. Een ontwikkelaar kan de volledige trace bekijken voor context, zich concentreren op Core Web Vitals en de bijbehorende tijdsperioden in de trace, of zelfs tot in detail ingaan op individuele gebeurtenissen zoals klikken of scrollen en de bijbehorende aanroepstacks.
Om het debugproces te ondersteunen, moet de AI-ondersteuning van DevTools aansluiten bij de specifieke behoeften van ontwikkelaars en alleen de relevante gegevens gebruiken om advies te geven dat is afgestemd op de focus van de ontwikkelaar. In plaats van altijd de volledige trace te versturen, hebben we daarom AI-ondersteuning ontwikkeld die de gegevens filtert op basis van uw debugtaak:
| Foutopsporingstaak | Gegevens die aanvankelijk naar AI-ondersteuning zijn verzonden |
|---|---|
| Bespreking van een prestatietrace | Trace-samenvatting : Een tekstgebaseerd rapport met belangrijke informatie uit de trace- en debugsessie. Bevat de URL van de pagina, de beperkingen voor de doorvoer, belangrijke prestatie-indicatoren (LCP, INP, CLS), een lijst met beschikbare inzichten en, indien beschikbaar, een CrUX-samenvatting. |
| Bespreek een inzicht in prestaties | Trace-samenvatting en de naam van het geselecteerde prestatie-inzicht. |
| Bespreek een taak aan de hand van een trace. | Trace-samenvatting en de geserialiseerde aanroepstructuur waarin de geselecteerde taak zich bevindt. |
| Chatten over een netwerkverzoek | Trace-samenvatting, inclusief de geselecteerde aanvraagsleutel en tijdstempel. |
| Genereer trace-annotaties | De geserialiseerde aanroepstructuur waarin de geselecteerde taak zich bevindt. De geserialiseerde structuur identificeert welke taak de geselecteerde taak is. |
De trace-samenvatting wordt bijna altijd meegestuurd om Gemini, het onderliggende model voor AI-ondersteuning, van initiële context te voorzien. Bij door AI gegenereerde annotaties wordt deze weggelaten.
AI de juiste tools geven
De AI-ondersteuning in DevTools functioneert als een agent. Dit betekent dat het autonoom meer gegevens kan opvragen, gebaseerd op de eerste prompt van de ontwikkelaar en de initiële context die ermee gedeeld wordt. Om meer gegevens op te vragen, hebben we de AI-ondersteuning een set vooraf gedefinieerde functies gegeven die het kan aanroepen. Dit patroon staat bekend als functieaanroeping of toolgebruik .
Op basis van de eerder beschreven debugprocessen hebben we een reeks gedetailleerde functies voor de agent gedefinieerd. Deze functies gaan dieper in op specifieke details die belangrijk worden geacht op basis van de initiële context, vergelijkbaar met hoe een menselijke ontwikkelaar prestatieproblemen zou aanpakken. De functies zijn als volgt:
| Functie | Beschrijving |
|---|---|
getInsightDetails(name) | Geeft gedetailleerde informatie over een specifiek prestatie-inzicht (bijvoorbeeld details over waarom LCP is gemarkeerd). |
getEventByKey(key) | Geeft gedetailleerde eigenschappen weer voor één specifiek evenement. |
getMainThreadTrackSummary(start, end) | Geeft een samenvatting van de activiteit van de hoofdthread binnen de opgegeven grenzen, inclusief top-down, bottom-up en samenvattingen van derden. |
getNetworkTrackSummary(start, end) | Geeft een samenvatting van de netwerkactiviteit voor de opgegeven tijdsperiode. |
getDetailedCallTree(event_key) | Retourneert de volledige aanroepstructuur voor een specifieke gebeurtenis in de hoofdthread op de prestatietrace. |
getFunctionCode(url, line, col) | Retourneert de broncode van een functie die is gedefinieerd op een specifieke locatie in een resource, voorzien van runtime-prestatiegegevens uit de prestatietrace. |
getResourceContent(url) | Geeft de inhoud terug van een tekstbron die door de pagina wordt gebruikt (bijvoorbeeld HTML of CSS). |
Door het ophalen van gegevens strikt te beperken tot deze functieaanroepen, zorgen we ervoor dat alleen relevante informatie in een goed gedefinieerd formaat in het contextvenster terechtkomt, waardoor het tokengebruik wordt geoptimaliseerd.
Voorbeeld van een agentoperatie
Laten we eens kijken naar een praktisch voorbeeld van hoe AI-assistentie functieaanroepen gebruikt om meer informatie op te halen. Na een eerste vraag als "Waarom duurt dit verzoek zo lang?", kan de AI-assistentie de volgende functies stapsgewijs aanroepen:
-
getEventByKey: Haal de gedetailleerde tijdsverdeling (TTFB, downloadtijd) op van het specifieke verzoek dat door de gebruiker is geselecteerd. -
getMainThreadTrackSummary: Controleer of de hoofdthread bezet (geblokkeerd) was toen het verzoek had moeten starten. -
getNetworkTrackSummary: Analyseer of andere bronnen tegelijkertijd om bandbreedte concurreerden. -
getInsightDetails: Controleer of de traceersamenvatting al een inzicht vermeldt dat verband houdt met dit verzoek als knelpunt.
Door de resultaten van deze aanroepen te combineren, kan AI-ondersteuning vervolgens een diagnose stellen en concrete stappen voorstellen, zoals het suggereren van codeverbeteringen met behulp van getFunctionCode of het optimaliseren van het laden van resources op basis van getResourceContent .
Het ophalen van relevante gegevens is echter slechts de helft van de uitdaging. Zelfs met functies die gedetailleerde gegevens leveren, kunnen de gegevens die door die functies worden geretourneerd erg groot zijn. Om een ander voorbeeld te geven: getDetailedCallTree kan een boomstructuur met honderden knooppunten retourneren. In standaard JSON zouden dit vele { en } accolades zijn, alleen al voor de nesting!
Er is daarom behoefte aan een formaat dat compact genoeg is om efficiënt met tokens om te gaan, maar toch gestructureerd genoeg is zodat een LLM-student het kan begrijpen en ernaar kan verwijzen.
Serialiseer de gegevens
Laten we dieper ingaan op hoe we deze uitdaging hebben aangepakt, en verdergaan met het voorbeeld van de aanroepstructuur, aangezien aanroepstructuren het grootste deel van de gegevens in een prestatietrace vormen. Ter referentie toont het volgende voorbeeld een enkele taak in een aanroepstack in JSON-formaat:
{
"id": 2,
"name": "animate",
"selected": true,
"duration": 150,
"selfTime": 20,
"children": [3, 5, 6, 7, 10, 11, 12]
}
Eén prestatietrace kan er duizenden van bevatten, zoals te zien is in de volgende schermafbeelding. Elk klein gekleurd vakje wordt weergegeven met behulp van deze objectstructuur.

Dit formaat is handig om programmatisch mee te werken in DevTools, maar het is inefficiënt voor LLM's om de volgende redenen:
- Overbodige sleutels: Strings zoals
"duration","selfTime"en"children"worden herhaald voor elk knooppunt in de aanroepstructuur. Een boom met 500 knooppunten die naar een model wordt gestuurd, verbruikt dus 500 tokens voor elk van deze sleutels. - Uitgebreide lijsten: Het afzonderlijk weergeven van elke kind-ID via
childrenverbruikt een enorm aantal tokens, met name voor taken die veel vervolggebeurtenissen activeren.
De implementatie van een token-efficiënt formaat voor alle data die met behulp van AI voor prestatieanalyse wordt gebruikt, was een stapsgewijs proces.
Eerste iteratie
Toen we begonnen met het ontwikkelen van AI-ondersteuning voor Performance, optimaliseerden we voor verzendsnelheid. Onze aanpak voor tokenoptimalisatie was eenvoudig: we verwijderden accolades en komma's uit de originele JSON, wat resulteerde in een formaat zoals het volgende:
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
...
Maar deze eerste versie was slechts een kleine verbetering ten opzichte van de onbewerkte JSON. Het gaf nog steeds expliciet de kinderen van knooppunten weer met ID's en namen, en voegde beschrijvende, herhaalde sleutels ( Node: , Selected: , Duration: , …) toe aan het begin van elke regel.
Optimaliseer de lijsten met onderliggende knooppunten
Als volgende stap om verder te optimaliseren, hebben we de namen voor de onderliggende knooppunten ( calculatePosition , applyStyles , ... in het vorige voorbeeld) verwijderd. Omdat de AI-assistent via functieaanroepen toegang heeft tot alle knooppunten en deze informatie al in de kop van het knooppunt aanwezig is ( Node: 3 - calculatePosition ), is het niet nodig deze informatie te herhalen. Hierdoor konden we de Children reduceren tot een eenvoudige lijst met gehele getallen.
Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3, 5, 6, 7, 10, 11, 12
..
Hoewel dit een duidelijke verbetering was ten opzichte van voorheen, was er nog ruimte voor verdere optimalisatie. Als je naar het vorige voorbeeld kijkt, zie je misschien dat Children bijna opeenvolgend is, met alleen 4 , 8 en 9 die ontbreken.
De reden hiervoor is dat we bij onze eerste poging een Depth-First Search (DFS) -algoritme gebruikten om boomdata uit de Performance trace te serialiseren. Dit resulteerde in niet-opeenvolgende ID's voor sibling nodes, waardoor we elke ID afzonderlijk moesten vermelden.
We realiseerden ons dat als we de boom opnieuw indexeerden met behulp van breedte-eerst zoeken (BFS), we sequentiële ID's zouden krijgen, waardoor een andere optimalisatie mogelijk werd. In plaats van individuele ID's op te sommen, konden we nu zelfs honderden kinderen weergeven met één compact bereik, zoals 3-9 voor het oorspronkelijke voorbeeld.
De uiteindelijke knooppuntnotatie, met de geoptimaliseerde lijst Children , ziet er als volgt uit:
allUrls = [...]
Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3-9
Verminder het aantal sleutels
Nadat de knooppuntlijsten waren geoptimaliseerd, gingen we verder met de redundante sleutels. We begonnen met het verwijderen van alle sleutels uit het vorige formaat, wat resulteerde in het volgende:
allUrls = [...]
2;animate;150;20;0;3-10
Hoewel het token-efficiënt was, moesten we Gemini nog steeds instructies geven over hoe deze gegevens te interpreteren. Daarom hebben we de eerste keer dat we een oproepstructuur naar Gemini stuurden, de volgende prompt toegevoegd:
...
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.
....
Hoewel deze formaatbeschrijving een symbolische kostenpost met zich meebrengt, betreft het een vast bedrag dat eenmalig voor het hele gesprek wordt betaald. De kosten worden ruimschoots gecompenseerd door de besparingen die zijn gerealiseerd door de eerdere optimalisaties.
Conclusie
Het optimaliseren van het tokengebruik is cruciaal bij het ontwikkelen met AI. Door over te stappen van ruwe JSON naar een gespecialiseerd, aangepast formaat, bomen opnieuw te indexeren met behulp van breedte-eerst zoeken en toolaanroepen te gebruiken om gegevens op aanvraag op te halen, hebben we de hoeveelheid tokens die AI-ondersteuning in Chrome DevTools verbruikt aanzienlijk verminderd.
Deze optimalisaties waren een voorwaarde voor het mogelijk maken van AI-ondersteuning bij prestatietraceringen. Door het beperkte contextvenster kon het systeem anders de enorme hoeveelheid data niet verwerken. Maar het geoptimaliseerde formaat maakt een prestatieagent mogelijk die een langere conversatiegeschiedenis kan bijhouden en nauwkeurigere, contextbewuste antwoorden kan geven zonder overweldigd te raken door ruis.
We hopen dat deze technieken je inspireren om je eigen datastructuren nog eens kritisch te bekijken bij het ontwerpen voor AI. Om aan de slag te gaan met AI in webapplicaties, kun je Learn AI op web.dev verkennen.