Skip to content

Commit 81f5d04

Browse files
Connor ClarkDevtools-frontend LUCI CQ
authored andcommitted
[AI] Add new Performance agent behind experiment
Adds a new conversation type for the new Performance agent, and a button to access it in the Performance panel toolbar (behind an experiment). This CL implements most of the planned "high-level" data as facts, and a few of the planned agent functions. More to come. The only impact on existing behavior should be no longer repeating the context as a "context" ResponseType on every user message (which seemed unintentional). Bypass-Check-License: the presubmit check is wrong Bug: 425270067 Change-Id: I4002688b5a202f674c05c4f30f6fc89b296e3aa8 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6819863 Reviewed-by: Jack Franklin <[email protected]> Auto-Submit: Connor Clark <[email protected]> Commit-Queue: Connor Clark <[email protected]>
1 parent 0401dc7 commit 81f5d04

34 files changed

+1339
-236
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ grd_files_unbundled_sources = [
10161016
"front_end/models/ai_assistance/data_formatters/FileFormatter.js",
10171017
"front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js",
10181018
"front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js",
1019+
"front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js",
10191020
"front_end/models/ai_assistance/debug.js",
10201021
"front_end/models/ai_assistance/injected.js",
10211022
"front_end/models/ai_code_completion/AiCodeCompletion.js",

front_end/core/host/AidaClient.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export enum FunctionalityType {
113113
AGENTIC_CHAT = 5,
114114
}
115115

116+
// See: cs/aida.proto (google3).
116117
export enum ClientFeature {
117118
// Unspecified client feature.
118119
CLIENT_FEATURE_UNSPECIFIED = 0,
@@ -132,6 +133,8 @@ export enum ClientFeature {
132133
CHROME_PATCH_AGENT = 12,
133134
// Chrome AI Assistance Performance Insights Agent.
134135
CHROME_PERFORMANCE_INSIGHTS_AGENT = 13,
136+
// Chrome AI Assistance Performance Agent.
137+
CHROME_PERFORMANCE_FULL_AGENT = 24,
135138
}
136139

137140
export enum UserTier {

front_end/core/host/UserMetrics.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ export enum Action {
526526
AiAssistanceOpenedFromNetworkPanel = 170,
527527
AiAssistanceOpenedFromSourcesPanel = 171,
528528
AiAssistanceOpenedFromSourcesPanelFloatingButton = 172,
529-
AiAssistanceOpenedFromPerformancePanel = 173,
529+
AiAssistanceOpenedFromPerformancePanelCallTree = 173,
530530
AiAssistanceOpenedFromNetworkPanelFloatingButton = 174,
531531
AiAssistancePanelOpened = 175,
532532
AiAssistanceQuerySubmitted = 176,
@@ -536,7 +536,8 @@ export enum Action {
536536
AiAssistanceSideEffectRejected = 180,
537537
AiAssistanceError = 181,
538538
AiAssistanceOpenedFromPerformanceInsight = 182,
539-
MAX_VALUE = 183,
539+
AiAssistanceOpenedFromPerformanceFullButton = 183,
540+
MAX_VALUE = 184,
540541
/* eslint-enable @typescript-eslint/naming-convention */
541542
}
542543

@@ -824,14 +825,15 @@ export enum DevtoolsExperiments {
824825
'use-source-map-scopes' = 76,
825826
'timeline-show-postmessage-events' = 86,
826827
'timeline-save-as-gz' = 108,
828+
'timeline-ask-ai-full-button' = 109,
827829
'timeline-enhanced-traces' = 90,
828830
'timeline-compiled-sources' = 91,
829831
'timeline-debug-mode' = 93,
830832
'vertical-drawer' = 107,
831833
/* eslint-enable @typescript-eslint/naming-convention */
832834

833835
// Increment this when new experiments are added.
834-
MAX_VALUE = 109,
836+
MAX_VALUE = 110,
835837
}
836838

837839
// Update DevToolsIssuesPanelIssueExpanded from tools/metrics/histograms/enums.xml if new enum is added.

front_end/core/root/Runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ export const enum ExperimentName {
323323
TIMELINE_DEBUG_MODE = 'timeline-debug-mode',
324324
TIMELINE_ENHANCED_TRACES = 'timeline-enhanced-traces',
325325
TIMELINE_COMPILED_SOURCES = 'timeline-compiled-sources',
326+
TIMELINE_ASK_AI_FULL_BUTTON = 'timeline-ask-ai-full-button',
326327
TIMELINE_SAVE_AS_GZ = 'timeline-save-as-gz',
327328
VERTICAL_DRAWER = 'vertical-drawer',
328329
// Adding or removing an entry from this enum?

front_end/entrypoints/main/MainImpl.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,9 @@ export class MainImpl {
361361
);
362362
Root.Runtime.experiments.register(
363363
Root.Runtime.ExperimentName.TIMELINE_SAVE_AS_GZ, 'Performance panel: enable saving traces as .gz');
364+
Root.Runtime.experiments.register(
365+
Root.Runtime.ExperimentName.TIMELINE_ASK_AI_FULL_BUTTON,
366+
'Performance panel: enable new, more powerful Ask AI in trace view');
364367

365368
Root.Runtime.experiments.enableExperimentsByDefault([
366369
Root.Runtime.ExperimentName.FULL_ACCESSIBILITY_TREE,

front_end/models/ai_assistance/AiHistoryStorage.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ export const enum ConversationType {
1313
STYLING = 'freestyler',
1414
FILE = 'drjones-file',
1515
NETWORK = 'drjones-network-request',
16-
PERFORMANCE = 'drjones-performance',
16+
PERFORMANCE_CALL_TREE = 'drjones-performance',
1717
PERFORMANCE_INSIGHT = 'performance-insight',
18+
PERFORMANCE_FULL = 'drjones-performance-full',
1819
}
1920

2021
export const NOT_FOUND_IMAGE_DATA = '';

front_end/models/ai_assistance/BUILD.gn

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ devtools_module("ai_assistance") {
2626
"data_formatters/FileFormatter.ts",
2727
"data_formatters/NetworkRequestFormatter.ts",
2828
"data_formatters/PerformanceInsightFormatter.ts",
29+
"data_formatters/PerformanceTraceFormatter.ts",
2930
"debug.ts",
3031
"injected.ts",
3132
]
@@ -86,6 +87,7 @@ ts_library("unittests") {
8687
"data_formatters/FileFormatter.test.ts",
8788
"data_formatters/NetworkRequestFormatter.test.ts",
8889
"data_formatters/PerformanceInsightFormatter.test.ts",
90+
"data_formatters/PerformanceTraceFormatter.test.ts",
8991
]
9092

9193
deps = [
@@ -102,5 +104,8 @@ ts_library("unittests") {
102104
}
103105

104106
copy_to_gen("snapshots") {
105-
sources = [ "data_formatters/PerformanceInsightFormatter.snapshot.txt" ]
107+
sources = [
108+
"data_formatters/PerformanceInsightFormatter.snapshot.txt",
109+
"data_formatters/PerformanceTraceFormatter.snapshot.txt",
110+
]
106111
}

front_end/models/ai_assistance/ConversationHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,9 @@ export class ConversationHandler {
343343
agent = new FileAgent(options);
344344
break;
345345
}
346+
case ConversationType.PERFORMANCE_FULL:
346347
case ConversationType.PERFORMANCE_INSIGHT:
347-
case ConversationType.PERFORMANCE: {
348+
case ConversationType.PERFORMANCE_CALL_TREE: {
348349
agent = new PerformanceAgent(options, conversationType);
349350
break;
350351
}

front_end/models/ai_assistance/agents/PerformanceAgent.test.ts

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describeWithEnvironment('PerformanceAgent', () => {
4848
{
4949
aidaClient: {} as Host.AidaClient.AidaClient,
5050
},
51-
ConversationType.PERFORMANCE);
51+
ConversationType.PERFORMANCE_CALL_TREE);
5252
assert.strictEqual(
5353
agent.buildRequest({text: 'test input'}, Host.AidaClient.Role.USER).options?.model_id,
5454
'test model',
@@ -61,7 +61,7 @@ describeWithEnvironment('PerformanceAgent', () => {
6161
{
6262
aidaClient: {} as Host.AidaClient.AidaClient,
6363
},
64-
ConversationType.PERFORMANCE);
64+
ConversationType.PERFORMANCE_CALL_TREE);
6565
assert.strictEqual(
6666
agent.buildRequest({text: 'test input'}, Host.AidaClient.Role.USER).options?.temperature,
6767
1,
@@ -76,7 +76,7 @@ describeWithEnvironment('PerformanceAgent', () => {
7676
aidaClient: mockAidaClient([[{explanation: 'answer'}]]),
7777
serverSideLoggingEnabled: true,
7878
},
79-
ConversationType.PERFORMANCE);
79+
ConversationType.PERFORMANCE_CALL_TREE);
8080

8181
await Array.fromAsync(agent.run('question', {selected: null}));
8282
setUserAgentForTesting();
@@ -168,7 +168,7 @@ describeWithEnvironment('PerformanceAgent – call tree focus', () => {
168168
},
169169
}]]),
170170
},
171-
ConversationType.PERFORMANCE);
171+
ConversationType.PERFORMANCE_CALL_TREE);
172172

173173
const context = PerformanceTraceContext.fromCallTree(aiCallTree);
174174
const responses = await Array.fromAsync(agent.run('test', {selected: context}));
@@ -226,7 +226,7 @@ describeWithEnvironment('PerformanceAgent – call tree focus', () => {
226226
{
227227
aidaClient: {} as Host.AidaClient.AidaClient,
228228
},
229-
ConversationType.PERFORMANCE);
229+
ConversationType.PERFORMANCE_CALL_TREE);
230230

231231
const mockAiCallTree = {
232232
serialize: () => 'Mock call tree',
@@ -289,7 +289,7 @@ describeWithEnvironment('PerformanceAgent – insight focus', () => {
289289
const lcpBreakdown = getInsightOrError('LCPBreakdown', insights, firstNav);
290290
const insightSet = getInsightSetOrError(insights, firstNav);
291291
const context = PerformanceTraceContext.fromInsight(parsedTrace, lcpBreakdown, insightSet.bounds);
292-
assert.strictEqual(context.getOrigin(), 'trace-658799706428-658804825864');
292+
assert.strictEqual(context.getOrigin(), 'insight-658799706428-658804825864');
293293
});
294294

295295
it('outputs the right title for the selected insight', async () => {
@@ -546,8 +546,8 @@ Help me understand?`;
546546
const action = responses.find(response => response.type === ResponseType.ACTION);
547547
assert.exists(action);
548548

549-
const expectedTree =
550-
TimelineUtils.InsightAIContext.AIQueries.mainThreadActivity(lcpBreakdown, insightSet.bounds, parsedTrace);
549+
const expectedTree = TimelineUtils.InsightAIContext.AIQueries.mainThreadActivityForInsight(
550+
lcpBreakdown, insightSet.bounds, parsedTrace);
551551
assert.isOk(expectedTree);
552552

553553
const expectedBytesSize = Platform.StringUtilities.countWtf8Bytes(expectedTree.serialize());
@@ -631,8 +631,8 @@ Help me understand?`;
631631
const mainThreadActivityFact = Array.from(agent.currentFacts()).at(0);
632632
assert.exists(mainThreadActivityFact);
633633

634-
const expectedTree =
635-
TimelineUtils.InsightAIContext.AIQueries.mainThreadActivity(lcpBreakdown, insightSet.bounds, parsedTrace);
634+
const expectedTree = TimelineUtils.InsightAIContext.AIQueries.mainThreadActivityForInsight(
635+
lcpBreakdown, insightSet.bounds, parsedTrace);
636636
assert.isOk(expectedTree);
637637
assert.include(mainThreadActivityFact.text, expectedTree.serialize());
638638

@@ -729,20 +729,31 @@ Help me understand?`;
729729

730730
const expectedFormatDescription =
731731
`The tree is represented as a call frame with a root task and a series of children.
732-
The format of each callframe is:
732+
The format of each callframe is:
733733
734-
'id;name;duration;selfTime;urlIndex;childRange;[S]'
734+
'id;name;duration;selfTime;urlIndex;childRange;[S]'
735735
736-
The fields are:
736+
The fields are:
737737
738-
* id: A unique numerical identifier for the call frame.
739-
* name: A concise string describing the call frame (e.g., 'Evaluate Script', 'render', 'fetchData').
740-
* duration: The total execution time of the call frame, including its children.
741-
* selfTime: The time spent directly within the call frame, excluding its children's execution.
742-
* urlIndex: Index referencing the "All URLs" list. Empty if no specific script URL is associated.
743-
* 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.
744-
* S: **Optional marker.** The letter 'S' appears at the end of the line **only** for the single call frame selected by the user.`;
738+
* id: A unique numerical identifier for the call frame.
739+
* name: A concise string describing the call frame (e.g., 'Evaluate Script', 'render', 'fetchData').
740+
* duration: The total execution time of the call frame, including its children.
741+
* selfTime: The time spent directly within the call frame, excluding its children's execution.
742+
* urlIndex: Index referencing the "All URLs" list. Empty if no specific script URL is associated.
743+
* 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.
744+
* S: **Optional marker.** The letter 'S' appears at the end of the line **only** for the single call frame selected by the user.`;
745745
assert.deepEqual(mainThreadActivityDescriptionFact.text, expectedFormatDescription);
746746
});
747747
});
748748
});
749+
750+
describeWithEnvironment('PerformanceAgent – all focus', () => {
751+
it('uses the min and max bounds of the trace as the origin', async function() {
752+
const {parsedTrace, insights, metadata} = await TraceLoader.traceEngine(this, 'lcp-images.json.gz');
753+
assert.isOk(insights);
754+
const [firstNav] = parsedTrace.Meta.mainFrameNavigations;
755+
const insightSet = getInsightSetOrError(insights, firstNav);
756+
const context = PerformanceTraceContext.full(parsedTrace, insightSet, metadata);
757+
assert.strictEqual(context.getOrigin(), 'trace-658799706428-658804825864');
758+
});
759+
});

0 commit comments

Comments
 (0)