Skip to content

Commit 78d3973

Browse files
jackfranklinDevtools-frontend LUCI CQ
authored andcommitted
Reland "RPP: create new annotations AI agent"
This is a reland of commit 9995b96 Original change's description: > RPP: create new annotations AI agent > > Required mostly for metrics so we can track its usage accurately rather > than bundled in with the existing "Ask AI" integration. > > (Googlers: see the attached bug for a link to the backend CL, this needs > to land first before this can land). > > Bug: 406795908 > Change-Id: Ibf0fff042f72a6e622e9d9d9e87769a71feb23af > Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6407494 > Reviewed-by: Nikolay Vitkov <[email protected]> > Commit-Queue: Jack Franklin <[email protected]> Bug: 406795908 Change-Id: I0c3bc41014a1f1f120fbb4f68f701eafcb4216a0 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6405318 Commit-Queue: Jack Franklin <[email protected]> Reviewed-by: Nikolay Vitkov <[email protected]>
1 parent cdcca1a commit 78d3973

File tree

10 files changed

+88
-57
lines changed

10 files changed

+88
-57
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,7 @@ grd_files_debug_sources = [
991991
"front_end/models/ai_assistance/agents/NetworkAgent.js",
992992
"front_end/models/ai_assistance/agents/PatchAgent.js",
993993
"front_end/models/ai_assistance/agents/PerformanceAgent.js",
994+
"front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.js",
994995
"front_end/models/ai_assistance/agents/PerformanceInsightsAgent.js",
995996
"front_end/models/ai_assistance/agents/StylingAgent.js",
996997
"front_end/models/ai_assistance/data_formatters/FileFormatter.js",

front_end/core/host/AidaClient.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export enum ClientFeature {
124124
CHROME_NETWORK_AGENT = 7,
125125
// Chrome AI Assistance Performance Agent.
126126
CHROME_PERFORMANCE_AGENT = 8,
127+
// Chrome AI Annotations Performance Agent
128+
CHROME_PERFORMANCE_ANNOTATIONS_AGENT = 20,
127129
// Chrome AI Assistance File Agent.
128130
CHROME_FILE_AGENT = 9,
129131
// Chrome AI Patch Agent.

front_end/models/ai_assistance/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ devtools_module("ai_assistance") {
1919
"agents/NetworkAgent.ts",
2020
"agents/PatchAgent.ts",
2121
"agents/PerformanceAgent.ts",
22+
"agents/PerformanceAnnotationsAgent.ts",
2223
"agents/PerformanceInsightsAgent.ts",
2324
"agents/StylingAgent.ts",
2425
"data_formatters/FileFormatter.ts",
@@ -76,6 +77,7 @@ ts_library("unittests") {
7677
"agents/NetworkAgent.test.ts",
7778
"agents/PatchAgent.test.ts",
7879
"agents/PerformanceAgent.test.ts",
80+
"agents/PerformanceAnnotationsAgent.test.ts",
7981
"agents/PerformanceInsightsAgent.test.ts",
8082
"agents/StylingAgent.test.ts",
8183
"data_formatters/FileFormatter.test.ts",

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

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -226,22 +226,4 @@ self: 3
226226
assert.isFalse(enhancedQuery3.includes(mockAiCallTree.serialize()));
227227
});
228228
});
229-
230-
describe('generating an AI entry label', () => {
231-
it('generates a label from the final answer and trims newlines', async function() {
232-
const agent = new PerformanceAgent({
233-
aidaClient: mockAidaClient([[{
234-
explanation: 'hello world\n',
235-
}]]),
236-
});
237-
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
238-
const evalScriptEvent = parsedTrace.Renderer.allTraceEntries.find(
239-
event => event.name === Trace.Types.Events.Name.EVALUATE_SCRIPT && event.ts === 122411195649);
240-
assert.exists(evalScriptEvent);
241-
const aiCallTree = TimelineUtils.AICallTree.AICallTree.fromEvent(evalScriptEvent, parsedTrace);
242-
assert.isOk(aiCallTree);
243-
const label = await agent.generateAIEntryLabel(aiCallTree);
244-
assert.strictEqual(label, 'hello world');
245-
});
246-
});
247229
});

front_end/models/ai_assistance/agents/PerformanceAgent.ts

Lines changed: 5 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,11 @@ export class CallTreeContext extends ConversationContext<TimelineUtils.AICallTre
201201
export class PerformanceAgent extends AiAgent<TimelineUtils.AICallTree.AICallTree> {
202202
override readonly type = AgentType.PERFORMANCE;
203203
readonly preamble = preamble;
204-
readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_PERFORMANCE_AGENT;
204+
205+
// We have to set the type of clientFeature here to be the entire enum
206+
// because in PerformanceAnnotationsAgent.ts we override it.
207+
// TODO(b/406961576): split the agents apart rather than have one extend the other.
208+
readonly clientFeature: Host.AidaClient.ClientFeature = Host.AidaClient.ClientFeature.CHROME_PERFORMANCE_AGENT;
205209
get userTier(): string|undefined {
206210
return Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.userTier;
207211
}
@@ -251,37 +255,4 @@ export class PerformanceAgent extends AiAgent<TimelineUtils.AICallTree.AICallTre
251255
const perfEnhancementQuery = treeStr ? `${treeStr}\n\n# User request\n\n` : '';
252256
return `${perfEnhancementQuery}${query}`;
253257
}
254-
255-
/**
256-
* Used in the Performance panel to automatically generate a label for a selected entry.
257-
*/
258-
async generateAIEntryLabel(callTree: TimelineUtils.AICallTree.AICallTree): Promise<string> {
259-
const context = new CallTreeContext(callTree);
260-
const response = await Array.fromAsync(this.run(AI_LABEL_GENERATION_PROMPT, {selected: context}));
261-
const lastResponse = response.at(-1);
262-
if (lastResponse && lastResponse.type === ResponseType.ANSWER && lastResponse.complete === true) {
263-
return lastResponse.text.trim();
264-
}
265-
throw new Error('Failed to generate AI entry label');
266-
}
267258
}
268-
269-
const AI_LABEL_GENERATION_PROMPT = `## Instruction:
270-
Generate a concise label (max 60 chars, single line) describing the selected call tree's activity, based solely on the provided call tree data.
271-
272-
You should focus on:
273-
1. What activity is happening within the call tree.
274-
2. What the code within the call tree is doing.
275-
3. What (if any) visible impact to the user there is.
276-
277-
## Strict Constraints:
278-
- Output must be a single line of text.
279-
- Maximum 60 characters.
280-
- No full stops.
281-
- Base the description only on the information present within the call tree data.
282-
- Do not include the name of the selected event.
283-
- Do not make assumptions about when the activity happened.
284-
- Only include details on activity that you are highly confident about.
285-
- Prioritize brevity.
286-
- Only include third-party script names if their identification is highly confident.
287-
`;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2025 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as TimelineUtils from '../../../panels/timeline/utils/utils.js';
6+
import {mockAidaClient} from '../../../testing/AiAssistanceHelpers.js';
7+
import {
8+
describeWithEnvironment,
9+
} from '../../../testing/EnvironmentHelpers.js';
10+
import {TraceLoader} from '../../../testing/TraceLoader.js';
11+
import * as Trace from '../../trace/trace.js';
12+
import {PerformanceAnnotationsAgent} from '../ai_assistance.js';
13+
14+
describeWithEnvironment('PerformanceAnnotationsAgent', () => {
15+
it('generates a label from the response', async function() {
16+
const agent = new PerformanceAnnotationsAgent({
17+
aidaClient: mockAidaClient([[{
18+
explanation: 'hello world\n',
19+
}]]),
20+
});
21+
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
22+
const evalScriptEvent = parsedTrace.Renderer.allTraceEntries.find(
23+
event => event.name === Trace.Types.Events.Name.EVALUATE_SCRIPT && event.ts === 122411195649);
24+
assert.exists(evalScriptEvent);
25+
const aiCallTree = TimelineUtils.AICallTree.AICallTree.fromEvent(evalScriptEvent, parsedTrace);
26+
assert.isOk(aiCallTree);
27+
const label = await agent.generateAIEntryLabel(aiCallTree);
28+
assert.strictEqual(label, 'hello world');
29+
});
30+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2025 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as Host from '../../../core/host/host.js';
6+
import type * as TimelineUtils from '../../../panels/timeline/utils/utils.js';
7+
8+
import {ResponseType} from './AiAgent.js';
9+
import {CallTreeContext, PerformanceAgent} from './PerformanceAgent.js';
10+
11+
export class PerformanceAnnotationsAgent extends PerformanceAgent {
12+
override readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_PERFORMANCE_ANNOTATIONS_AGENT;
13+
14+
/**
15+
* Used in the Performance panel to automatically generate a label for a selected entry.
16+
*/
17+
async generateAIEntryLabel(callTree: TimelineUtils.AICallTree.AICallTree): Promise<string> {
18+
const context = new CallTreeContext(callTree);
19+
const response = await Array.fromAsync(this.run(AI_LABEL_GENERATION_PROMPT, {selected: context}));
20+
const lastResponse = response.at(-1);
21+
if (lastResponse && lastResponse.type === ResponseType.ANSWER && lastResponse.complete === true) {
22+
return lastResponse.text.trim();
23+
}
24+
throw new Error('Failed to generate AI entry label');
25+
}
26+
}
27+
28+
const AI_LABEL_GENERATION_PROMPT = `## Instruction:
29+
Generate a concise label (max 60 chars, single line) describing the *user-visible effect* of the selected call tree's activity, based solely on the provided call tree data.
30+
31+
## Strict Constraints:
32+
- Output must be a single line of text.
33+
- Maximum 60 characters.
34+
- No full stops.
35+
- Focus on user impact, not internal operations.
36+
- Do not include the name of the selected event.
37+
- Do not make assumptions about when the activity happened.
38+
- Base the description only on the information present within the call tree data.
39+
- Prioritize brevity.
40+
- Only include third-party script names if their identification is highly confident.
41+
- Always use "responsiveness" rather than "user interaction responsiveness".
42+
`;

front_end/models/ai_assistance/ai_assistance.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from './agents/AiAgent.js';
88
export * from './agents/FileAgent.js';
99
export * from './agents/NetworkAgent.js';
1010
export * from './agents/PerformanceAgent.js';
11+
export * from './agents/PerformanceAnnotationsAgent.js';
1112
export * from './agents/PerformanceInsightsAgent.js';
1213
export * from './agents/StylingAgent.js';
1314
export * from './agents/PatchAgent.js';

front_end/panels/timeline/overlays/OverlaysImpl.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ describeWithEnvironment('Overlays', () => {
590590
const generateButton = elementsWrapper.querySelector<HTMLElement>('.ai-label-button');
591591
assert.isOk(generateButton, 'could not find "Generate label" button');
592592
assert.isTrue(generateButton.classList.contains('enabled'));
593-
const agent = new AiAssistanceModels.PerformanceAgent({
593+
const agent = new AiAssistanceModels.PerformanceAnnotationsAgent({
594594
aidaClient: mockAidaClient([[{
595595
explanation: 'This is an interesting entry',
596596
metadata: {

front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export class EntryLabelOverlay extends HTMLElement {
189189
#callTree: Utils.AICallTree.AICallTree|null = null;
190190
// Creates or gets the setting if it exists.
191191
#aiAnnotationsEnabledSetting = Common.Settings.Settings.instance().createSetting('ai-annotations-enabled', false);
192-
#performanceAgent = new AiAssistanceModels.PerformanceAgent({
192+
#agent = new AiAssistanceModels.PerformanceAnnotationsAgent({
193193
aidaClient: new Host.AidaClient.AidaClient(),
194194
serverSideLoggingEnabled: isAiAssistanceServerSideLoggingEnabled(),
195195
});
@@ -249,8 +249,8 @@ export class EntryLabelOverlay extends HTMLElement {
249249
/**
250250
* So we can provide a mocked agent in tests. Do not call this method outside of a test!
251251
*/
252-
overrideAIAgentForTest(agent: AiAssistanceModels.PerformanceAgent): void {
253-
this.#performanceAgent = agent;
252+
overrideAIAgentForTest(agent: AiAssistanceModels.PerformanceAnnotationsAgent): void {
253+
this.#agent = agent;
254254
}
255255

256256
connectedCallback(): void {
@@ -521,7 +521,7 @@ export class EntryLabelOverlay extends HTMLElement {
521521
this.#focusInputBox();
522522
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
523523

524-
this.#label = await this.#performanceAgent.generateAIEntryLabel(this.#callTree);
524+
this.#label = await this.#agent.generateAIEntryLabel(this.#callTree);
525525
this.dispatchEvent(new EntryLabelChangeEvent(this.#label));
526526
this.#inputField.innerText = this.#label;
527527

0 commit comments

Comments
 (0)