Diseño de Herramientas para desarrolladores: Uso eficiente de tokens en la asistencia de IA

Publicado: 30 de enero de 2026

Cuando creamos la asistencia de IA para el rendimiento, el principal desafío de ingeniería fue hacer que Gemini funcionara correctamente con los registros de rendimiento grabados en Herramientas para desarrolladores.

Los modelos de lenguaje grandes (LLM) operan dentro de una "ventana de contexto", que se refiere a un límite estricto en la cantidad de información que pueden procesar a la vez. Esta capacidad se mide en tokens. En el caso de los modelos de Gemini, un token equivale aproximadamente a un grupo de cuatro caracteres.

Los registros de rendimiento son archivos JSON enormes que suelen ocupar varios megabytes. Enviar un registro sin procesar agotaría instantáneamente la ventana de contexto de un modelo y no dejaría espacio para tus preguntas.

Para que la asistencia de IA para el rendimiento fuera posible, tuvimos que diseñar un sistema que maximizara la cantidad de datos útiles para un LLM con el mínimo uso de tokens. En este blog, puedes obtener información sobre las técnicas que usamos para hacerlo y adoptarlas en tus propios proyectos.

Adapta el contexto inicial

Depurar el rendimiento de un sitio web es una tarea compleja. Un desarrollador puede observar el registro completo para obtener contexto, enfocarse en las Métricas web principales y los períodos relacionados del registro, o incluso analizar los detalles y enfocarse en eventos individuales, como clics o desplazamientos, y sus pilas de llamadas relacionadas.

Para ayudar en el proceso de depuración, la asistencia de IA de Herramientas para desarrolladores debe coincidir con esos recorridos del desarrollador y solo funcionar con los datos pertinentes para brindar asesoramiento específico según el enfoque del desarrollador. Por lo tanto, en lugar de enviar siempre el registro completo, creamos asistencia con IA para segmentar los datos según tu tarea de depuración:

Tarea de depuración Datos que se envían inicialmente a la asistencia de IA
Chatear sobre un registro de rendimiento Resumen del registro: Es un informe basado en texto que incluye información de alto nivel del registro y la sesión de depuración. Incluye la URL de la página, las condiciones de limitación, las métricas de rendimiento clave (LCP, INP, CLS), una lista de las estadísticas disponibles y, si está disponible, un resumen de CrUX.
Chatea sobre una estadística de rendimiento Resumen del registro y nombre de la estadística de rendimiento seleccionada
Chatear sobre una tarea a partir de un registro Resumen del registro y árbol de llamadas serializado en el que se encuentra la tarea seleccionada
Chatea sobre una solicitud de red Resumen del registro y clave y marca de tiempo de la solicitud seleccionadas
Cómo generar anotaciones de seguimiento Es el árbol de llamadas serializado en el que se encuentra la tarea seleccionada. El árbol serializado identifica qué tarea es la seleccionada.

El resumen del registro casi siempre se envía para proporcionar contexto inicial a Gemini, el modelo subyacente de la asistencia de IA. En el caso de las anotaciones generadas por IA, se omite.

Cómo proporcionar herramientas a la IA

La asistencia de IA en DevTools funciona como un agente. Esto significa que puede consultar de forma autónoma más datos, según la instrucción inicial del desarrollador y el contexto inicial que se compartió con él. Para consultar más datos, le proporcionamos a la asistencia de IA un conjunto de funciones predefinidas que puede llamar. Un patrón conocido como llamada a función o uso de herramientas.

Según los recorridos de depuración que se describieron anteriormente, definimos un conjunto de funciones detalladas para el agente. Estas funciones profundizan en los detalles que se consideran importantes según el contexto inicial, de manera similar a como un desarrollador humano abordaría la depuración del rendimiento. El conjunto de funciones es el siguiente:

Función Descripción
getInsightDetails(name) Devuelve información detallada sobre una métrica de rendimiento específica (por ejemplo,detalles sobre por qué se marcó el LCP).
getEventByKey(key) Devuelve propiedades detalladas para un solo evento específico.
getMainThreadTrackSummary(start, end) Devuelve un resumen de la actividad del subproceso principal para los límites determinados, incluidos los resúmenes de arriba hacia abajo, de abajo hacia arriba y de terceros.
getNetworkTrackSummary(start, end) Devuelve un resumen de la actividad de la red para los límites de tiempo determinados.
getDetailedCallTree(event_key) Devuelve el árbol de llamadas completo para un evento específico del subproceso principal en el registro de rendimiento.
getFunctionCode(url, line, col) Devuelve el código fuente de una función definida en una ubicación específica de un recurso, anotado con datos de rendimiento del tiempo de ejecución del registro de rendimiento.
getResourceContent(url) Devuelve el contenido de un recurso de texto que usa la página (por ejemplo, HTML o CSS).

Al limitar estrictamente la recuperación de datos a estas llamadas a funciones, nos aseguramos de que solo la información pertinente ingrese a la ventana de contexto en un formato bien definido, lo que optimiza el uso de tokens.

Ejemplo de una operación del agente

Veamos un ejemplo práctico de cómo la asistencia de IA usa la llamada a funciones para recuperar más información. Después de una instrucción inicial de "¿Por qué esta solicitud es lenta?", La asistencia de IA puede llamar a las siguientes funciones de forma incremental:

  1. getEventByKey: Recupera el desglose detallado del tiempo (TTFB, tiempo de descarga) de la solicitud específica seleccionada por el usuario.
  2. getMainThreadTrackSummary: Comprueba si el subproceso principal estaba ocupado (bloqueado) cuando debería haber comenzado la solicitud.
  3. getNetworkTrackSummary: Analiza si otros recursos competían por el ancho de banda al mismo tiempo.
  4. getInsightDetails: Comprueba si el resumen de la ruta ya menciona una estadística relacionada con esta solicitud como un cuello de botella.

Al combinar los resultados de estas llamadas, la asistencia de IA puede proporcionar un diagnóstico y proponer medidas prácticas, como sugerir mejoras de código con getFunctionCode o optimizar la carga de recursos según getResourceContent.

Sin embargo, recuperar los datos pertinentes es solo la mitad del desafío. Incluso con las funciones que proporcionan datos detallados, los datos que devuelven esas funciones pueden ser de gran tamaño. Para tomar otro ejemplo, getDetailedCallTree puede devolver un árbol con cientos de nodos. En JSON estándar, esto implicaría muchos { y } solo para el anidamiento.

Por lo tanto, se necesita un formato lo suficientemente denso como para ser eficiente en términos de tokens, pero lo suficientemente estructurado para que un LLM lo comprenda y lo use como referencia.

Serializa los datos

Profundicemos en cómo abordamos este desafío, continuando con el ejemplo del árbol de llamadas, ya que los árboles de llamadas constituyen la mayoría de los datos en un registro de rendimiento. Como referencia, en los siguientes ejemplos, se muestra una sola tarea en una pila de llamadas en JSON:

{
  "id": 2,
  "name": "animate",
  "selected": true,
  "duration": 150,
  "selfTime": 20,
  "children": [3, 5, 6, 7, 10, 11, 12]
}

Un registro de rendimiento puede contener miles de esos eventos, como se muestra en la siguiente captura de pantalla. Cada pequeña caja de color se representa con esta estructura de objeto.

Una pila de llamadas en un registro de rendimiento grabado en Herramientas para desarrolladores

Este formato es adecuado para trabajar con él de forma programática en Herramientas para desarrolladores, pero es ineficiente para los LLM por los siguientes motivos:

  1. Claves redundantes: Las cadenas como "duration", "selfTime" y "children" se repiten para cada nodo del árbol de llamadas. Por lo tanto, un árbol con 500 nodos enviado a un modelo consumirá tokens para cada una de esas claves 500 veces.
  2. Listas detalladas: Enumerar cada ID secundario de forma individual a través de children consume una gran cantidad de tokens, en especial para las tareas que activan muchos eventos posteriores.

La implementación de un formato eficiente en tokens para todos los datos que se usan con la asistencia de IA para el rendimiento fue un proceso paso a paso.

Primera iteración

Cuando comenzamos a trabajar en la asistencia de IA para el rendimiento, optimizamos la velocidad de lanzamiento. Nuestro enfoque de optimización de tokens era básico, y quitamos las llaves y las comas del JSON original, lo que generó un formato como el siguiente:

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

...

Sin embargo, esta primera versión solo representó una pequeña mejora en comparación con el formato JSON sin procesar. Aún enumeraba explícitamente los nodos secundarios con IDs y nombres, y anteponía claves descriptivas y repetidas (Node:, Selected:, Duration:, …) delante de cada línea.

Optimiza las listas de nodos secundarios

Como siguiente paso para optimizar aún más, quitamos los nombres de los nodos secundarios (calculatePosition, applyStyles… en el ejemplo anterior). Como la asistencia de IA tiene acceso a todos los nodos a través de las llamadas a funciones y esta información ya está en el encabezado del nodo (Node: 3 - calculatePosition), no es necesario repetir esta información. Esto nos permitió reducir Children a una simple lista de números enteros:

Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3, 5, 6, 7, 10, 11, 12

..

Si bien esto representó una mejora notable con respecto a la situación anterior, aún había margen para optimizar el proceso. Cuando observes el ejemplo anterior, notarás que Children es casi secuencial, ya que solo faltan 4, 8 y 9.

El motivo es que, en nuestro primer intento, usamos un algoritmo de búsqueda en profundidad (DFS) para serializar los datos del árbol del registro de rendimiento. Esto generó IDs no secuenciales para los nodos hermanos, lo que nos obligó a enumerar cada ID de forma individual.

Nos dimos cuenta de que, si volvíamos a indexar el árbol con la búsqueda en amplitud (BFS), obtendríamos IDs secuenciales, lo que permitiría otra optimización. En lugar de enumerar IDs individuales, ahora podríamos representar incluso cientos de elementos secundarios con un solo rango compacto, como 3-9 para el ejemplo original.

La notación final del nodo, con la lista optimizada de Children, se ve de la siguiente manera:

allUrls = [...]

Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3-9

Reduce la cantidad de teclas

Una vez optimizadas las listas de nodos, pasamos a las claves redundantes. Comenzamos por quitar todas las claves del formato anterior, lo que dio como resultado lo siguiente:

allUrls = [...]

2;animate;150;20;0;3-10

Si bien es eficiente en cuanto a tokens, aún necesitábamos darle instrucciones a Gemini sobre cómo comprender estos datos. Por lo tanto, la primera vez que enviamos un árbol de llamadas a Gemini, incluimos la siguiente instrucción:

...
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.

....

Si bien esta descripción del formato genera un costo de token, es un costo estático que se paga una sola vez por toda la conversación. El costo se compensa con los ahorros obtenidos a través de las optimizaciones anteriores.

Conclusión

Optimizar el uso de tokens es una consideración fundamental cuando se compila con IA. Al cambiar de JSON sin procesar a un formato personalizado especializado, volver a indexar árboles con la búsqueda en amplitud y usar llamadas a herramientas para recuperar datos a pedido, redujimos significativamente la cantidad de tokens que consume la asistencia de IA en las Herramientas para desarrolladores de Chrome.

Estas optimizaciones eran un requisito previo para habilitar la asistencia de IA para los registros de rendimiento. De lo contrario, debido a su ventana de contexto limitada, no podría manejar el gran volumen de datos. Sin embargo, el formato optimizado permite que un agente de rendimiento mantenga un historial de conversaciones más largo y proporcione respuestas más precisas y contextuales sin verse abrumado por el ruido.

Esperamos que estas técnicas te inspiren a revisar tus propias estructuras de datos cuando diseñes para la IA. Para comenzar a usar la IA en aplicaciones web, explora Learn AI on web.dev.