mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-09-29 16:57:00 +08:00
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 <nvitkov@chromium.org> Commit-Queue: Jack Franklin <jacktfranklin@chromium.org>
This commit is contained in:
committed by
Devtools-frontend LUCI CQ
parent
5f7e033b32
commit
9995b96203
@@ -989,6 +989,7 @@ grd_files_debug_sources = [
|
||||
"front_end/models/ai_assistance/agents/NetworkAgent.js",
|
||||
"front_end/models/ai_assistance/agents/PatchAgent.js",
|
||||
"front_end/models/ai_assistance/agents/PerformanceAgent.js",
|
||||
"front_end/models/ai_assistance/agents/PerformanceAnnotationsAgent.js",
|
||||
"front_end/models/ai_assistance/agents/PerformanceInsightsAgent.js",
|
||||
"front_end/models/ai_assistance/agents/StylingAgent.js",
|
||||
"front_end/models/ai_assistance/data_formatters/FileFormatter.js",
|
||||
|
||||
@@ -124,6 +124,8 @@ export enum ClientFeature {
|
||||
CHROME_NETWORK_AGENT = 7,
|
||||
// Chrome AI Assistance Performance Agent.
|
||||
CHROME_PERFORMANCE_AGENT = 8,
|
||||
// Chrome AI Annotations Performance Agent
|
||||
CHROME_PERFORMANCE_ANNOTATIONS_AGENT = 20,
|
||||
// Chrome AI Assistance File Agent.
|
||||
CHROME_FILE_AGENT = 9,
|
||||
// Chrome AI Patch Agent.
|
||||
|
||||
@@ -19,6 +19,7 @@ devtools_module("ai_assistance") {
|
||||
"agents/NetworkAgent.ts",
|
||||
"agents/PatchAgent.ts",
|
||||
"agents/PerformanceAgent.ts",
|
||||
"agents/PerformanceAnnotationsAgent.ts",
|
||||
"agents/PerformanceInsightsAgent.ts",
|
||||
"agents/StylingAgent.ts",
|
||||
"data_formatters/FileFormatter.ts",
|
||||
@@ -76,6 +77,7 @@ ts_library("unittests") {
|
||||
"agents/NetworkAgent.test.ts",
|
||||
"agents/PatchAgent.test.ts",
|
||||
"agents/PerformanceAgent.test.ts",
|
||||
"agents/PerformanceAnnotationsAgent.test.ts",
|
||||
"agents/PerformanceInsightsAgent.test.ts",
|
||||
"agents/StylingAgent.test.ts",
|
||||
"data_formatters/FileFormatter.test.ts",
|
||||
|
||||
@@ -226,22 +226,4 @@ self: 3
|
||||
assert.isFalse(enhancedQuery3.includes(mockAiCallTree.serialize()));
|
||||
});
|
||||
});
|
||||
|
||||
describe('generating an AI entry label', () => {
|
||||
it('generates a label from the final answer and trims newlines', async function() {
|
||||
const agent = new PerformanceAgent({
|
||||
aidaClient: mockAidaClient([[{
|
||||
explanation: 'hello world\n',
|
||||
}]]),
|
||||
});
|
||||
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
|
||||
const evalScriptEvent = parsedTrace.Renderer.allTraceEntries.find(
|
||||
event => event.name === Trace.Types.Events.Name.EVALUATE_SCRIPT && event.ts === 122411195649);
|
||||
assert.exists(evalScriptEvent);
|
||||
const aiCallTree = TimelineUtils.AICallTree.AICallTree.fromEvent(evalScriptEvent, parsedTrace);
|
||||
assert.isOk(aiCallTree);
|
||||
const label = await agent.generateAIEntryLabel(aiCallTree);
|
||||
assert.strictEqual(label, 'hello world');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -201,7 +201,11 @@ export class CallTreeContext extends ConversationContext<TimelineUtils.AICallTre
|
||||
export class PerformanceAgent extends AiAgent<TimelineUtils.AICallTree.AICallTree> {
|
||||
override readonly type = AgentType.PERFORMANCE;
|
||||
readonly preamble = preamble;
|
||||
readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_PERFORMANCE_AGENT;
|
||||
|
||||
// We have to set the type of clientFeature here to be the entire enum
|
||||
// because in PerformanceAnnotationsAgent.ts we override it.
|
||||
// TODO(b/406961576): split the agents apart rather than have one extend the other.
|
||||
readonly clientFeature: Host.AidaClient.ClientFeature = Host.AidaClient.ClientFeature.CHROME_PERFORMANCE_AGENT;
|
||||
get userTier(): string|undefined {
|
||||
return Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.userTier;
|
||||
}
|
||||
@@ -251,33 +255,4 @@ export class PerformanceAgent extends AiAgent<TimelineUtils.AICallTree.AICallTre
|
||||
const perfEnhancementQuery = treeStr ? `${treeStr}\n\n# User request\n\n` : '';
|
||||
return `${perfEnhancementQuery}${query}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in the Performance panel to automatically generate a label for a selected entry.
|
||||
*/
|
||||
async generateAIEntryLabel(callTree: TimelineUtils.AICallTree.AICallTree): Promise<string> {
|
||||
const context = new CallTreeContext(callTree);
|
||||
const response = await Array.fromAsync(this.run(AI_LABEL_GENERATION_PROMPT, {selected: context}));
|
||||
const lastResponse = response.at(-1);
|
||||
if (lastResponse && lastResponse.type === ResponseType.ANSWER && lastResponse.complete === true) {
|
||||
return lastResponse.text.trim();
|
||||
}
|
||||
throw new Error('Failed to generate AI entry label');
|
||||
}
|
||||
}
|
||||
|
||||
const AI_LABEL_GENERATION_PROMPT = `## Instruction:
|
||||
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.
|
||||
|
||||
## Strict Constraints:
|
||||
- Output must be a single line of text.
|
||||
- Maximum 60 characters.
|
||||
- No full stops.
|
||||
- Focus on user impact, not internal operations.
|
||||
- Do not include the name of the selected event.
|
||||
- Do not make assumptions about when the activity happened.
|
||||
- Base the description only on the information present within the call tree data.
|
||||
- Prioritize brevity.
|
||||
- Only include third-party script names if their identification is highly confident.
|
||||
- Always use "responsiveness" rather than "user interaction responsiveness".
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import * as TimelineUtils from '../../../panels/timeline/utils/utils.js';
|
||||
import {mockAidaClient} from '../../../testing/AiAssistanceHelpers.js';
|
||||
import {
|
||||
describeWithEnvironment,
|
||||
} from '../../../testing/EnvironmentHelpers.js';
|
||||
import {TraceLoader} from '../../../testing/TraceLoader.js';
|
||||
import * as Trace from '../../trace/trace.js';
|
||||
import {PerformanceAnnotationsAgent} from '../ai_assistance.js';
|
||||
|
||||
describeWithEnvironment('PerformanceAnnotationsAgent', () => {
|
||||
it('generates a label from the response', async function() {
|
||||
const agent = new PerformanceAnnotationsAgent({
|
||||
aidaClient: mockAidaClient([[{
|
||||
explanation: 'hello world\n',
|
||||
}]]),
|
||||
});
|
||||
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
|
||||
const evalScriptEvent = parsedTrace.Renderer.allTraceEntries.find(
|
||||
event => event.name === Trace.Types.Events.Name.EVALUATE_SCRIPT && event.ts === 122411195649);
|
||||
assert.exists(evalScriptEvent);
|
||||
const aiCallTree = TimelineUtils.AICallTree.AICallTree.fromEvent(evalScriptEvent, parsedTrace);
|
||||
assert.isOk(aiCallTree);
|
||||
const label = await agent.generateAIEntryLabel(aiCallTree);
|
||||
assert.strictEqual(label, 'hello world');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2025 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import * as Host from '../../../core/host/host.js';
|
||||
import type * as TimelineUtils from '../../../panels/timeline/utils/utils.js';
|
||||
|
||||
import {ResponseType} from './AiAgent.js';
|
||||
import {CallTreeContext, PerformanceAgent} from './PerformanceAgent.js';
|
||||
|
||||
export class PerformanceAnnotationsAgent extends PerformanceAgent {
|
||||
override readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_PERFORMANCE_ANNOTATIONS_AGENT;
|
||||
|
||||
/**
|
||||
* Used in the Performance panel to automatically generate a label for a selected entry.
|
||||
*/
|
||||
async generateAIEntryLabel(callTree: TimelineUtils.AICallTree.AICallTree): Promise<string> {
|
||||
const context = new CallTreeContext(callTree);
|
||||
const response = await Array.fromAsync(this.run(AI_LABEL_GENERATION_PROMPT, {selected: context}));
|
||||
const lastResponse = response.at(-1);
|
||||
if (lastResponse && lastResponse.type === ResponseType.ANSWER && lastResponse.complete === true) {
|
||||
return lastResponse.text.trim();
|
||||
}
|
||||
throw new Error('Failed to generate AI entry label');
|
||||
}
|
||||
}
|
||||
|
||||
const AI_LABEL_GENERATION_PROMPT = `## Instruction:
|
||||
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.
|
||||
|
||||
## Strict Constraints:
|
||||
- Output must be a single line of text.
|
||||
- Maximum 60 characters.
|
||||
- No full stops.
|
||||
- Focus on user impact, not internal operations.
|
||||
- Do not include the name of the selected event.
|
||||
- Do not make assumptions about when the activity happened.
|
||||
- Base the description only on the information present within the call tree data.
|
||||
- Prioritize brevity.
|
||||
- Only include third-party script names if their identification is highly confident.
|
||||
- Always use "responsiveness" rather than "user interaction responsiveness".
|
||||
`;
|
||||
@@ -8,6 +8,7 @@ export * from './agents/AiAgent.js';
|
||||
export * from './agents/FileAgent.js';
|
||||
export * from './agents/NetworkAgent.js';
|
||||
export * from './agents/PerformanceAgent.js';
|
||||
export * from './agents/PerformanceAnnotationsAgent.js';
|
||||
export * from './agents/PerformanceInsightsAgent.js';
|
||||
export * from './agents/StylingAgent.js';
|
||||
export * from './agents/PatchAgent.js';
|
||||
|
||||
@@ -590,7 +590,7 @@ describeWithEnvironment('Overlays', () => {
|
||||
const generateButton = elementsWrapper.querySelector<HTMLElement>('.ai-label-button');
|
||||
assert.isOk(generateButton, 'could not find "Generate label" button');
|
||||
assert.isTrue(generateButton.classList.contains('enabled'));
|
||||
const agent = new AiAssistanceModels.PerformanceAgent({
|
||||
const agent = new AiAssistanceModels.PerformanceAnnotationsAgent({
|
||||
aidaClient: mockAidaClient([[{
|
||||
explanation: 'This is an interesting entry',
|
||||
metadata: {
|
||||
|
||||
@@ -189,7 +189,7 @@ export class EntryLabelOverlay extends HTMLElement {
|
||||
#callTree: Utils.AICallTree.AICallTree|null = null;
|
||||
// Creates or gets the setting if it exists.
|
||||
#aiAnnotationsEnabledSetting = Common.Settings.Settings.instance().createSetting('ai-annotations-enabled', false);
|
||||
#performanceAgent = new AiAssistanceModels.PerformanceAgent({
|
||||
#agent = new AiAssistanceModels.PerformanceAnnotationsAgent({
|
||||
aidaClient: new Host.AidaClient.AidaClient(),
|
||||
serverSideLoggingEnabled: isAiAssistanceServerSideLoggingEnabled(),
|
||||
});
|
||||
@@ -249,8 +249,8 @@ export class EntryLabelOverlay extends HTMLElement {
|
||||
/**
|
||||
* So we can provide a mocked agent in tests. Do not call this method outside of a test!
|
||||
*/
|
||||
overrideAIAgentForTest(agent: AiAssistanceModels.PerformanceAgent): void {
|
||||
this.#performanceAgent = agent;
|
||||
overrideAIAgentForTest(agent: AiAssistanceModels.PerformanceAnnotationsAgent): void {
|
||||
this.#agent = agent;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
@@ -521,7 +521,7 @@ export class EntryLabelOverlay extends HTMLElement {
|
||||
this.#focusInputBox();
|
||||
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
|
||||
|
||||
this.#label = await this.#performanceAgent.generateAIEntryLabel(this.#callTree);
|
||||
this.#label = await this.#agent.generateAIEntryLabel(this.#callTree);
|
||||
this.dispatchEvent(new EntryLabelChangeEvent(this.#label));
|
||||
this.#inputField.innerText = this.#label;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user