Опубликовано: 30 января 2026 г.
При разработке системы искусственного интеллекта для анализа производительности основной инженерной задачей было обеспечение корректной работы Gemini с трассировками производительности, записанными в DevTools.
Большие языковые модели (БЛМ) работают в рамках «контекстного окна», которое обозначает строгое ограничение на объем информации, которую они могут обрабатывать одновременно. Эта способность измеряется в токенах. Для моделей Gemini один токен — это приблизительно группа из четырех символов.
Трассировки производительности представляют собой огромные JSON-файлы, часто занимающие несколько мегабайт. Отправка необработанной трассировки мгновенно исчерпает контекстное окно модели и не оставит места для ваших вопросов.
Для реализации системы искусственного интеллекта для повышения производительности нам пришлось разработать систему, которая бы максимально увеличивала объем полезных данных для магистерской программы при минимальном использовании токенов. В этом блоге вы можете узнать о методах, которые мы использовали для этого, и применить их в своих собственных проектах.
Адаптируйте исходный контекст.
Отладка производительности веб-сайта — сложная задача. Разработчик может либо изучить полную трассировку для контекста, либо сосредоточиться на основных параметрах веб-процесса и соответствующих временных интервалах трассировки, либо даже углубиться в детали и сосредоточиться на отдельных событиях, таких как клики или прокрутка, и связанных с ними стеках вызовов.
Для облегчения процесса отладки функция искусственного интеллекта в DevTools должна соответствовать сценариям работы разработчиков и работать только с релевантными данными, предоставляя рекомендации, специфичные для конкретной задачи отладки. Поэтому вместо того, чтобы всегда отправлять полный трассировочный файл, мы разработали функцию искусственного интеллекта, которая разделяет данные в зависимости от задачи отладки:
| Задача отладки | Данные первоначально были отправлены в систему искусственного интеллекта. |
|---|---|
| Обсудите трассировку производительности. | Сводка трассировки : текстовый отчет, содержащий общую информацию из сеанса трассировки и отладки. Включает URL страницы, условия регулирования, ключевые показатели производительности (LCP, INP, CLS), список доступных аналитических данных и, если имеется, сводку CrUX. |
| Обсудите анализ производительности. | Сводка трассировки и название выбранного показателя производительности. |
| Обсуждение задачи из трассировки | Сводка трассировки и сериализованное дерево вызовов, в котором находится выбранная задача. |
| Обсуждение сетевого запроса | Сводка трассировки, а также ключ и временная метка выбранного запроса. |
| Сгенерировать аннотации трассировки | Последовательное дерево вызовов, в котором находится выбранная задача. Последовательное дерево определяет, какая именно задача выбрана. |
Сводная информация о трассировке почти всегда отправляется для предоставления первоначального контекста Gemini, базовой модели помощи ИИ. Для аннотаций, сгенерированных ИИ, она опускается.
Предоставление инструментов для ИИ
Искусственный интеллект в инструментах разработчика функционирует как агент. Это означает, что он может автономно запрашивать дополнительные данные, основываясь на первоначальном запросе разработчика и исходном контексте, предоставленном ему. Для запроса дополнительных данных мы предоставили искусственному интеллекту набор предопределенных функций, которые он может вызывать. Этот подход известен как вызов функций или использование инструментов .
На основе описанных ранее алгоритмов отладки мы определили набор детализированных функций для агента. Эти функции углубляются в конкретные моменты, которые считаются важными на основе исходного контекста, подобно тому, как разработчик-человек подходит к отладке производительности. Набор функций выглядит следующим образом:
| Функция | Описание |
|---|---|
getInsightDetails(name) | Возвращает подробную информацию о конкретном показателе производительности (например, подробности о причинах срабатывания LCP). |
getEventByKey(key) | Возвращает подробные свойства для одного конкретного события. |
getMainThreadTrackSummary(start, end) | Возвращает сводку активности основного потока для заданных границ, включая сводки сверху вниз, снизу вверх и данные от сторонних источников. |
getNetworkTrackSummary(start, end) | Возвращает сводку сетевой активности за заданный период времени. |
getDetailedCallTree(event_key) | Возвращает полное дерево вызовов для конкретного события основного потока в трассировке производительности. |
getFunctionCode(url, line, col) | Возвращает исходный код функции, определенной в определенном месте ресурса, с аннотациями, содержащими данные о производительности во время выполнения из трассировки производительности. |
getResourceContent(url) | Возвращает содержимое текстового ресурса, используемого страницей (например, HTML или CSS). |
Строго ограничивая извлечение данных только этими вызовами функций, мы гарантируем, что в контекстное окно поступает только релевантная информация в четко определенном формате, оптимизируя использование токенов.
Пример работы агента
Рассмотрим практический пример того, как система искусственного интеллекта использует вызов функций для получения дополнительной информации. После первоначального запроса "Почему этот запрос выполняется медленно?", система искусственного интеллекта может последовательно вызывать следующие функции:
-
getEventByKey: Получает подробную информацию о времени выполнения (TTFB, время загрузки) конкретного запроса, выбранного пользователем. -
getMainThreadTrackSummary: Проверяет, был ли основной поток занят (заблокирован) в момент, когда запрос должен был начаться. -
getNetworkTrackSummary: Анализ того, конкурировали ли другие ресурсы за пропускную способность одновременно. -
getInsightDetails: Проверьте, упоминается ли в сводке трассировки уже какое-либо сообщение, связанное с этим запросом, как узкое место.
Объединив результаты этих запросов, система искусственного интеллекта может затем поставить диагноз и предложить конкретные шаги, например, предложить улучшения кода с помощью getFunctionCode или оптимизировать загрузку ресурсов на основе getResourceContent .
Однако извлечение релевантных данных — это лишь половина дела. Даже при использовании функций, предоставляющих детализированные данные, возвращаемые ими данные могут быть очень большими. В качестве другого примера, getDetailedCallTree может вернуть дерево с сотнями узлов. В стандартном JSON это было бы множество фигурных скобок { и } только для обозначения вложенности!
Таким образом, существует необходимость в формате, достаточно плотном для эффективного использования токенов, но при этом достаточно структурированном для понимания и использования в качестве справочного материала студентами магистратуры.
Сериализуйте данные
Давайте подробнее рассмотрим наш подход к решению этой задачи, продолжив пример с деревом вызовов, поскольку деревья вызовов составляют большую часть данных в трассировке производительности. Для справки, следующие примеры показывают отдельную задачу в стеке вызовов в формате JSON:
{
"id": 2,
"name": "animate",
"selected": true,
"duration": 150,
"selfTime": 20,
"children": [3, 5, 6, 7, 10, 11, 12]
}
Одна трассировка производительности может содержать тысячи таких элементов, как показано на следующем снимке экрана. Каждый маленький цветной квадратик представлен с использованием этой объектной структуры.

Этот формат удобен для программной работы в DevTools, но он неэффективен для магистров права по следующим причинам:
- Избыточные ключи: Строки типа
"duration","selfTime"и"children"повторяются для каждого узла в дереве вызовов. Таким образом, дерево с 500 узлами, отправленное модели, будет расходовать токены для каждого из этих ключей 500 раз. - Подробный список: перечисление идентификаторов каждого дочернего элемента по отдельности через
childrenпотребляет огромное количество токенов, особенно для задач, которые запускают множество последующих событий.
Внедрение формата, эффективного с точки зрения использования токенов, для всех данных, используемых с помощью ИИ для повышения производительности, представляло собой поэтапный процесс.
Первая итерация
Когда мы начали работать над поддержкой ИИ для повышения производительности, мы оптимизировали процесс для ускорения доставки. Наш подход к оптимизации токенов был простым: мы удалили из исходного JSON фигурные скобки и запятые, в результате чего получился формат, подобный следующему:
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
...
Однако эта первая версия представляла собой лишь незначительное улучшение по сравнению с чистым JSON. Она по-прежнему явно перечисляла дочерние узлы с идентификаторами и именами, а также добавляла описательные повторяющиеся ключи ( Node: , Selected: , Duration: , …) перед каждой строкой.
Оптимизировать списки дочерних узлов
В качестве следующего шага для дальнейшей оптимизации мы удалили имена дочерних узлов ( calculatePosition , applyStyles и т. д. в предыдущем примере). Поскольку ИИ-помощник имеет доступ ко всем узлам через вызов функций, и эта информация уже содержится в заголовке узла ( Node: 3 - calculatePosition ), нет необходимости повторять эту информацию. Это позволило нам свести Children к простому списку целых чисел:
Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3, 5, 6, 7, 10, 11, 12
..
Хотя это и было заметным улучшением по сравнению с предыдущим вариантом, возможности для дальнейшей оптимизации всё ещё оставались. Рассматривая предыдущий пример, вы можете заметить, что последовательность Children почти последовательна, отсутствуют только 4 , 8 и 9 .
Причина в том, что в нашей первой попытке мы использовали алгоритм поиска в глубину (DFS) для сериализации данных дерева из трассировки производительности. Это привело к непоследовательным идентификаторам для соседних узлов, что потребовало от нас перечислять каждый идентификатор по отдельности.
Мы поняли, что если переиндексировать дерево с помощью поиска в ширину (BFS), то получим последовательные идентификаторы, что позволит провести еще одну оптимизацию. Вместо перечисления отдельных идентификаторов мы теперь можем представлять даже сотни дочерних элементов одним компактным диапазоном, например, от 3-9 в исходном примере.
Итоговая запись узла с оптимизированным списком Children выглядит следующим образом:
allUrls = [...]
Node: 2 - animate
Selected: true
Duration: 150
Self Time: 20
URL: 0
Children: 3-9
Уменьшите количество клавиш.
После оптимизации списков узлов мы перешли к избыточным ключам. Начали с удаления всех ключей из предыдущего формата, в результате чего получили следующее:
allUrls = [...]
2;animate;150;20;0;3-10
Несмотря на то, что это было экономно с точки зрения токенов, нам все же нужно было дать Gemini инструкции по распознаванию этих данных. В результате, при первой отправке дерева вызовов в Gemini мы включили следующую подсказку:
...
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.
....
Хотя такое описание формата влечет за собой затраты в виде токена, это фиксированные затраты, оплачиваемые один раз за весь разговор. Эти затраты компенсируются экономией, полученной благодаря предыдущим оптимизациям.
Заключение
Оптимизация использования токенов — критически важный аспект при разработке с использованием ИИ. За счет перехода от необработанного JSON к специализированному пользовательскому формату, переиндексации деревьев с помощью поиска в ширину и использования вызовов инструментов для получения данных по запросу, мы значительно сократили количество токенов, потребляемых ИИ-помощью в инструментах разработчика Chrome .
Эти оптимизации были необходимым условием для включения помощи ИИ в анализе производительности. Из-за ограниченного контекстного окна он не мог бы справиться с огромным объемом данных. Но оптимизированный формат позволяет агенту производительности поддерживать более длительную историю диалога и предоставлять более точные, контекстно-зависимые ответы, не перегружаясь шумом.
Мы надеемся, что эти методы вдохновят вас по-новому взглянуть на собственные структуры данных при проектировании приложений для ИИ. Чтобы начать работу с ИИ в веб-приложениях, изучите раздел «Изучение ИИ» на web.dev .