chrome-devtools-frontend
Version:
Chrome DevTools UI
231 lines (207 loc) • 8.13 kB
text/typescript
// Copyright 2024 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 * as TimelineUtils from '../../../panels/timeline/utils/utils.js';
import {mockAidaClient} from '../../../testing/AiAssistanceHelpers.js';
import {
describeWithEnvironment,
restoreUserAgentForTesting,
setUserAgentForTesting,
updateHostConfig
} from '../../../testing/EnvironmentHelpers.js';
import {TraceLoader} from '../../../testing/TraceLoader.js';
import * as Trace from '../../trace/trace.js';
import {CallTreeContext, PerformanceAgent, ResponseType} from '../ai_assistance.js';
describeWithEnvironment('PerformanceAgent', () => {
function mockHostConfig(modelId?: string, temperature?: number) {
updateHostConfig({
devToolsAiAssistancePerformanceAgent: {
modelId,
temperature,
},
});
}
describe('getOrigin()', () => {
it('calculates the origin of the selected node when it has a URL associated with it', async function() {
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
// An Evaluate Script event, picked because it has a URL of googletagmanager.com/...
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 callTreeContext = new CallTreeContext(aiCallTree);
assert.strictEqual(callTreeContext.getOrigin(), 'https://www.googletagmanager.com');
});
it('returns a random but deterministic "origin" for nodes that have no URL associated', async function() {
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-with-commit.json.gz');
// A random layout event with no URL associated
const layoutEvent = parsedTrace.Renderer.allTraceEntries.find(
event => event.name === Trace.Types.Events.Name.LAYOUT && event.ts === 122411130078);
assert.exists(layoutEvent);
const aiCallTree = TimelineUtils.AICallTree.AICallTree.fromEvent(layoutEvent, parsedTrace);
assert.isOk(aiCallTree);
const callTreeContext = new CallTreeContext(aiCallTree);
assert.strictEqual(callTreeContext.getOrigin(), 'Layout_90829_259_122411130078');
});
});
describe('buildRequest', () => {
beforeEach(() => {
sinon.restore();
});
it('builds a request with a model id', async () => {
mockHostConfig('test model');
const agent = new PerformanceAgent({
aidaClient: {} as Host.AidaClient.AidaClient,
});
assert.strictEqual(
agent.buildRequest({text: 'test input'}, Host.AidaClient.Role.USER).options?.model_id,
'test model',
);
});
it('builds a request with a temperature', async () => {
mockHostConfig('test model', 1);
const agent = new PerformanceAgent({
aidaClient: {} as Host.AidaClient.AidaClient,
});
assert.strictEqual(
agent.buildRequest({text: 'test input'}, Host.AidaClient.Role.USER).options?.temperature,
1,
);
});
it('structure matches the snapshot', async () => {
mockHostConfig('test model');
sinon.stub(crypto, 'randomUUID').returns('sessionId' as `${string}-${string}-${string}-${string}-${string}`);
const agent = new PerformanceAgent({
aidaClient: mockAidaClient([[{explanation: 'answer'}]]),
serverSideLoggingEnabled: true,
});
await Array.fromAsync(agent.run('question', {selected: null}));
setUserAgentForTesting();
assert.deepEqual(
agent.buildRequest(
{
text: 'test input',
},
Host.AidaClient.Role.USER),
{
current_message: {role: Host.AidaClient.Role.USER, parts: [{text: 'test input'}]},
client: 'CHROME_DEVTOOLS',
preamble: undefined,
historical_contexts: [
{
role: 1,
parts: [{text: 'question'}],
},
{
role: 2,
parts: [{text: 'answer'}],
},
],
facts: undefined,
metadata: {
disable_user_content_logging: false,
string_session_id: 'sessionId',
user_tier: 2,
client_version: 'unit_test',
},
options: {
model_id: 'test model',
temperature: undefined,
},
client_feature: 8,
functionality_type: 1,
},
);
restoreUserAgentForTesting();
});
});
describe('run', function() {
it('generates an answer', async function() {
const {parsedTrace} = await TraceLoader.traceEngine(this, 'web-dev-outermost-frames.json.gz');
// A basic Layout.
const layoutEvt = parsedTrace.Renderer.allTraceEntries.find(event => event.ts === 465457096322);
assert.exists(layoutEvt);
const aiCallTree = TimelineUtils.AICallTree.AICallTree.fromEvent(layoutEvt, parsedTrace);
assert.exists(aiCallTree);
const agent = new PerformanceAgent({
aidaClient: mockAidaClient([[{
explanation: 'This is the answer',
metadata: {
rpcGlobalId: 123,
},
}]]),
});
const responses = await Array.fromAsync(agent.run('test', {selected: new CallTreeContext(aiCallTree)}));
const expectedData = '\n\n' +
`
# Call tree:
Node: 1 – Task
dur: 3
Children:
* 2 – Layout
Node: 2 – Layout
Selected: true
dur: 3
self: 3
`.trim();
assert.deepEqual(responses, [
{
type: ResponseType.USER_QUERY,
query: 'test',
imageInput: undefined,
imageId: undefined,
},
{
type: ResponseType.CONTEXT,
title: 'Analyzing call tree',
details: [
{title: 'Selected call tree', text: expectedData},
],
},
{
type: ResponseType.QUERYING,
},
{
type: ResponseType.ANSWER,
text: 'This is the answer',
complete: true,
suggestions: undefined,
rpcId: 123,
},
]);
assert.deepEqual(agent.buildRequest({text: ''}, Host.AidaClient.Role.USER).historical_contexts, [
{
role: 1,
parts: [{text: `${aiCallTree.serialize()}\n\n# User request\n\ntest`}],
},
{
role: 2,
parts: [{text: 'This is the answer'}],
},
]);
});
});
describe('enhanceQuery', () => {
it('does not send the serialized calltree again if it is a followup chat about the same calltree', async () => {
const agent = new PerformanceAgent({
aidaClient: {} as Host.AidaClient.AidaClient,
});
const mockAiCallTree = {
serialize: () => 'Mock call tree',
} as unknown as TimelineUtils.AICallTree.AICallTree;
const enhancedQuery1 = await agent.enhanceQuery('What is this?', new CallTreeContext(mockAiCallTree));
assert.strictEqual(enhancedQuery1, 'Mock call tree\n\n# User request\n\nWhat is this?');
const query2 = 'But what about this follow-up question?';
const enhancedQuery2 = await agent.enhanceQuery(query2, new CallTreeContext(mockAiCallTree));
assert.strictEqual(enhancedQuery2, query2);
assert.isFalse(enhancedQuery2.includes(mockAiCallTree.serialize()));
// Just making sure any subsequent chat doesnt include it either.
const query3 = 'And this 3rd question?';
const enhancedQuery3 = await agent.enhanceQuery(query3, new CallTreeContext(mockAiCallTree));
assert.strictEqual(enhancedQuery3, query3);
assert.isFalse(enhancedQuery3.includes(mockAiCallTree.serialize()));
});
});
});