Skip to content

Commit e04f2f9

Browse files
authored
Clean up telemetry code a bit more in ChatService (microsoft#259449)
* Simplify telemetry in ChatService * Use ChatRequestTelemetry
1 parent 84b0f1c commit e04f2f9

File tree

2 files changed

+188
-153
lines changed

2 files changed

+188
-153
lines changed

src/vs/workbench/contrib/chat/common/chatServiceImpl.ts

Lines changed: 26 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@ import { autorun, derived, IObservable, ObservableMap } from '../../../../base/c
1717
import { StopWatch } from '../../../../base/common/stopwatch.js';
1818
import { URI } from '../../../../base/common/uri.js';
1919
import { OffsetRange } from '../../../../editor/common/core/ranges/offsetRange.js';
20-
import { isLocation } from '../../../../editor/common/languages.js';
2120
import { localize } from '../../../../nls.js';
2221
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
2322
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
2423
import { ILogService } from '../../../../platform/log/common/log.js';
2524
import { Progress } from '../../../../platform/progress/common/progress.js';
2625
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
27-
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
2826
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
2927
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
3028
import { IMcpService } from '../../mcp/common/mcpTypes.js';
@@ -33,15 +31,15 @@ import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, ICha
3331
import { chatAgentLeader, ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, chatSubcommandLeader, getPromptText, IParsedChatRequest } from './chatParserTypes.js';
3432
import { ChatRequestParser } from './chatRequestParser.js';
3533
import { IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatSendRequestData, IChatSendRequestOptions, IChatSendRequestResponseState, IChatService, IChatTransferredSessionData, IChatUserActionEvent } from './chatService.js';
36-
import { ChatServiceTelemetry } from './chatServiceTelemetry.js';
34+
import { ChatRequestTelemetry, ChatServiceTelemetry } from './chatServiceTelemetry.js';
3735
import { IChatSessionsService } from './chatSessionsService.js';
3836
import { ChatSessionStore, IChatTransfer2 } from './chatSessionStore.js';
3937
import { IChatSlashCommandService } from './chatSlashCommands.js';
4038
import { IChatTransferService } from './chatTransferService.js';
4139
import { ChatSessionUri } from './chatUri.js';
42-
import { IChatRequestVariableEntry, isImageVariableEntry } from './chatVariableEntries.js';
40+
import { IChatRequestVariableEntry } from './chatVariableEntries.js';
4341
import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from './constants.js';
44-
import { ChatMessageRole, IChatMessage, ILanguageModelsService } from './languageModels.js';
42+
import { ChatMessageRole, IChatMessage } from './languageModels.js';
4543
import { ILanguageModelToolsService } from './languageModelToolsService.js';
4644

4745
const serializedChatKey = 'interactive.sessions';
@@ -50,44 +48,6 @@ const globalChatKey = 'chat.workspaceTransfer';
5048

5149
const SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS = 1000 * 60;
5250

53-
type ChatProviderInvokedEvent = {
54-
timeToFirstProgress: number | undefined;
55-
totalTime: number | undefined;
56-
result: 'success' | 'error' | 'errorWithOutput' | 'cancelled' | 'filtered';
57-
requestType: 'string' | 'followup' | 'slashCommand';
58-
chatSessionId: string;
59-
agent: string;
60-
agentExtensionId: string | undefined;
61-
slashCommand: string | undefined;
62-
location: ChatAgentLocation;
63-
citations: number;
64-
numCodeBlocks: number;
65-
isParticipantDetected: boolean;
66-
enableCommandDetection: boolean;
67-
attachmentKinds: string[];
68-
model: string | undefined;
69-
};
70-
71-
type ChatProviderInvokedClassification = {
72-
timeToFirstProgress: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time in milliseconds from invoking the provider to getting the first data.' };
73-
totalTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The total time it took to run the provider\'s `provideResponseWithProgress`.' };
74-
result: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether invoking the ChatProvider resulted in an error.' };
75-
requestType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of request that the user made.' };
76-
chatSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A random ID for the session.' };
77-
agent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of agent used.' };
78-
agentExtensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension that contributed the agent.' };
79-
slashCommand?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of slashCommand used.' };
80-
location: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The location at which chat request was made.' };
81-
citations: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of public code citations that were returned with the response.' };
82-
numCodeBlocks: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of code blocks in the response.' };
83-
isParticipantDetected: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the participant was automatically detected.' };
84-
enableCommandDetection: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether participation detection was disabled for this invocation.' };
85-
attachmentKinds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The types of variables/attachments that the user included with their query.' };
86-
model: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The model used to generate the response.' };
87-
owner: 'roblourens';
88-
comment: 'Provides insight into the performance of Chat agents.';
89-
};
90-
9151
const maxPersistedSessions = 25;
9252

9353
class CancellableRequest implements IDisposable {
@@ -160,13 +120,11 @@ export class ChatService extends Disposable implements IChatService {
160120
@ILogService private readonly logService: ILogService,
161121
@IExtensionService private readonly extensionService: IExtensionService,
162122
@IInstantiationService private readonly instantiationService: IInstantiationService,
163-
@ITelemetryService private readonly telemetryService: ITelemetryService,
164123
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
165124
@IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService,
166125
@IChatAgentService private readonly chatAgentService: IChatAgentService,
167126
@IConfigurationService private readonly configurationService: IConfigurationService,
168127
@IChatTransferService private readonly chatTransferService: IChatTransferService,
169-
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService,
170128
@IChatSessionsService private readonly chatSessionService: IChatSessionsService,
171129
@IMcpService private readonly mcpService: IMcpService,
172130
) {
@@ -550,8 +508,6 @@ export class ChatService extends Disposable implements IChatService {
550508

551509
const isTransferred = this.transferredSessionData?.sessionId === sessionId;
552510
if (isTransferred) {
553-
// TODO
554-
// this.chatAgentService.toggleToolsAgentMode(this.transferredSessionData.toolsAgentModeEnabled);
555511
this._transferredSessionData = undefined;
556512
}
557513

@@ -779,6 +735,15 @@ export class ChatService extends Disposable implements IChatService {
779735
const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart);
780736
const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart);
781737
const requests = [...model.getRequests()];
738+
const requestTelemetry = this.instantiationService.createInstance(ChatRequestTelemetry, {
739+
agentPart,
740+
agentSlashCommandPart,
741+
commandPart,
742+
sessionId: model.sessionId,
743+
location: model.initialLocation,
744+
options,
745+
enableCommandDetection
746+
});
782747

783748
let gotProgress = false;
784749
const requestType = commandPart ? 'slashCommand' : 'string';
@@ -828,23 +793,14 @@ export class ChatService extends Disposable implements IChatService {
828793
return;
829794
}
830795

831-
this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', {
796+
requestTelemetry.complete({
832797
timeToFirstProgress: undefined,
798+
result: 'cancelled',
833799
// Normally timings happen inside the EH around the actual provider. For cancellation we can measure how long the user waited before cancelling
834800
totalTime: stopWatch.elapsed(),
835-
result: 'cancelled',
836801
requestType,
837-
agent: detectedAgent?.id ?? agentPart?.agent.id ?? '',
838-
agentExtensionId: detectedAgent?.extensionId.value ?? agentPart?.agent.extensionId.value ?? '',
839-
slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command,
840-
chatSessionId: model.sessionId,
841-
location,
842-
citations: request?.response?.codeCitations.length ?? 0,
843-
numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length,
844-
isParticipantDetected: !!detectedAgent,
845-
enableCommandDetection,
846-
attachmentKinds: this.attachmentKindsForTelemetry(request.variableData),
847-
model: this.resolveModelId(options?.userSelectedModelId),
802+
detectedAgent,
803+
request,
848804
});
849805

850806
model.cancelRequest(request);
@@ -982,24 +938,16 @@ export class ChatService extends Disposable implements IChatService {
982938
rawResult.errorDetails && gotProgress ? 'errorWithOutput' :
983939
rawResult.errorDetails ? 'error' :
984940
'success';
985-
const commandForTelemetry = agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command;
986-
this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', {
941+
942+
requestTelemetry.complete({
987943
timeToFirstProgress: rawResult.timings?.firstProgress,
988944
totalTime: rawResult.timings?.totalElapsed,
989945
result,
990946
requestType,
991-
agent: detectedAgent?.id ?? agentPart?.agent.id ?? '',
992-
agentExtensionId: detectedAgent?.extensionId.value ?? agentPart?.agent.extensionId.value ?? '',
993-
slashCommand: commandForTelemetry,
994-
chatSessionId: model.sessionId,
995-
enableCommandDetection,
996-
isParticipantDetected: !!detectedAgent,
997-
location,
998-
citations: request.response?.codeCitations.length ?? 0,
999-
numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length,
1000-
attachmentKinds: this.attachmentKindsForTelemetry(request.variableData),
1001-
model: this.resolveModelId(options?.userSelectedModelId),
947+
detectedAgent,
948+
request,
1002949
});
950+
1003951
model.setResponse(request, rawResult);
1004952
completeResponseCreated();
1005953
this.trace('sendRequest', `Provider returned response for session ${model.sessionId}`);
@@ -1008,6 +956,7 @@ export class ChatService extends Disposable implements IChatService {
1008956
if (agentOrCommandFollowups) {
1009957
agentOrCommandFollowups.then(followups => {
1010958
model.setFollowups(request, followups);
959+
const commandForTelemetry = agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command;
1011960
this._chatServiceTelemetry.retrievedFollowups(agentPart?.agent.id ?? '', commandForTelemetry, followups?.length ?? 0);
1012961
});
1013962
}
@@ -1019,23 +968,13 @@ export class ChatService extends Disposable implements IChatService {
1019968
}
1020969
} catch (err) {
1021970
this.logService.error(`Error while handling chat request: ${toErrorMessage(err, true)}`);
1022-
const result = 'error';
1023-
this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', {
971+
requestTelemetry.complete({
1024972
timeToFirstProgress: undefined,
1025973
totalTime: undefined,
1026-
result,
974+
result: 'error',
1027975
requestType,
1028-
agent: detectedAgent?.id ?? agentPart?.agent.id ?? '',
1029-
agentExtensionId: detectedAgent?.extensionId.value ?? agentPart?.agent.extensionId.value ?? '',
1030-
slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command,
1031-
chatSessionId: model.sessionId,
1032-
location,
1033-
citations: 0,
1034-
numCodeBlocks: 0,
1035-
enableCommandDetection,
1036-
isParticipantDetected: !!detectedAgent,
1037-
attachmentKinds: request ? this.attachmentKindsForTelemetry(request.variableData) : [],
1038-
model: this.resolveModelId(options?.userSelectedModelId)
976+
detectedAgent,
977+
request,
1039978
});
1040979
if (request) {
1041980
const rawResult: IChatAgentResult = { errorDetails: { message: err.message } };
@@ -1060,10 +999,6 @@ export class ChatService extends Disposable implements IChatService {
1060999
};
10611000
}
10621001

1063-
private resolveModelId(userSelectedModelId: string | undefined): string | undefined {
1064-
return userSelectedModelId && this.languageModelsService.lookupLanguageModel(userSelectedModelId)?.id;
1065-
}
1066-
10671002
private prepareContext(attachedContextVariables: IChatRequestVariableEntry[] | undefined): IChatRequestVariableEntry[] {
10681003
attachedContextVariables ??= [];
10691004

@@ -1085,44 +1020,6 @@ export class ChatService extends Disposable implements IChatService {
10851020
return attachedContextVariables;
10861021
}
10871022

1088-
private attachmentKindsForTelemetry(variableData: IChatRequestVariableData): string[] {
1089-
// TODO this shows why attachments still have to be cleaned up somewhat
1090-
return variableData.variables.map(v => {
1091-
if (v.kind === 'implicit') {
1092-
return 'implicit';
1093-
} else if (v.range) {
1094-
// 'range' is range within the prompt text
1095-
if (v.kind === 'tool') {
1096-
return 'toolInPrompt';
1097-
} else if (v.kind === 'toolset') {
1098-
return 'toolsetInPrompt';
1099-
} else {
1100-
return 'fileInPrompt';
1101-
}
1102-
} else if (v.kind === 'command') {
1103-
return 'command';
1104-
} else if (v.kind === 'symbol') {
1105-
return 'symbol';
1106-
} else if (isImageVariableEntry(v)) {
1107-
return 'image';
1108-
} else if (v.kind === 'directory') {
1109-
return 'directory';
1110-
} else if (v.kind === 'tool') {
1111-
return 'tool';
1112-
} else if (v.kind === 'toolset') {
1113-
return 'toolset';
1114-
} else {
1115-
if (URI.isUri(v.value)) {
1116-
return 'file';
1117-
} else if (isLocation(v.value)) {
1118-
return 'location';
1119-
} else {
1120-
return 'otherAttachment';
1121-
}
1122-
}
1123-
});
1124-
}
1125-
11261023
private getHistoryEntriesFromModel(requests: IChatRequestModel[], sessionId: string, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] {
11271024
const history: IChatAgentHistoryEntry[] = [];
11281025
const agent = this.chatAgentService.getAgent(forAgentId);
@@ -1294,26 +1191,3 @@ export class ChatService extends Disposable implements IChatService {
12941191
this._chatSessionStore.logIndex();
12951192
}
12961193
}
1297-
1298-
function getCodeBlocks(text: string): string[] {
1299-
const lines = text.split('\n');
1300-
const codeBlockLanguages: string[] = [];
1301-
1302-
let codeBlockState: undefined | { readonly delimiter: string; readonly languageId: string };
1303-
for (let i = 0; i < lines.length; i++) {
1304-
const line = lines[i];
1305-
1306-
if (codeBlockState) {
1307-
if (new RegExp(`^\\s*${codeBlockState.delimiter}\\s*$`).test(line)) {
1308-
codeBlockLanguages.push(codeBlockState.languageId);
1309-
codeBlockState = undefined;
1310-
}
1311-
} else {
1312-
const match = line.match(/^(\s*)(`{3,}|~{3,})(\w*)/);
1313-
if (match) {
1314-
codeBlockState = { delimiter: match[2], languageId: match[3] };
1315-
}
1316-
}
1317-
}
1318-
return codeBlockLanguages;
1319-
}

0 commit comments

Comments
 (0)