mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-10 17:43:38 -05:00
fix: align promptCategorization.requestId with panel.request for telemetry joins (#3874)
Co-authored-by: Harald Kirschner <digitarald@gmail.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import { IExperimentationService } from '../../../platform/telemetry/common/null
|
||||
import { DisposableStore, IDisposable } from '../../../util/vs/base/common/lifecycle';
|
||||
import { autorun } from '../../../util/vs/base/common/observableInternal';
|
||||
import { URI } from '../../../util/vs/base/common/uri';
|
||||
import { generateUuid } from '../../../util/vs/base/common/uuid';
|
||||
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
|
||||
import { ChatRequest } from '../../../vscodeTypes';
|
||||
import { Intent, agentsToCommands } from '../../common/constants';
|
||||
@@ -235,8 +236,14 @@ Learn more about [GitHub Copilot](https://docs.github.com/copilot/using-github-c
|
||||
// The user is starting an interaction with the chat
|
||||
this.interactionService.startInteraction();
|
||||
|
||||
// Generate a shared telemetry message ID on the first turn only — subsequent turns have no
|
||||
// categorization event to join and ChatTelemetryBuilder will generate its own ID.
|
||||
const telemetryMessageId = context.history.length === 0 ? generateUuid() : undefined;
|
||||
|
||||
// Categorize the first prompt (fire-and-forget)
|
||||
this.promptCategorizerService.categorizePrompt(request, context);
|
||||
if (telemetryMessageId !== undefined) {
|
||||
this.promptCategorizerService.categorizePrompt(request, context, telemetryMessageId);
|
||||
}
|
||||
|
||||
const defaultIntentId = typeof defaultIntentIdOrGetter === 'function' ?
|
||||
defaultIntentIdOrGetter(request) :
|
||||
@@ -248,7 +255,7 @@ Learn more about [GitHub Copilot](https://docs.github.com/copilot/using-github-c
|
||||
commandsForAgent[request.command] :
|
||||
defaultIntentId;
|
||||
|
||||
const handler = this.instantiationService.createInstance(ChatParticipantRequestHandler, context.history, request, stream, token, { agentName: name, agentId: id, intentId }, () => context.yieldRequested);
|
||||
const handler = this.instantiationService.createInstance(ChatParticipantRequestHandler, context.history, request, stream, token, { agentName: name, agentId: id, intentId }, () => context.yieldRequested, telemetryMessageId);
|
||||
return await handler.getResult();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ suite('Conversation telemetry tests - Integration tests', function () {
|
||||
stream,
|
||||
token,
|
||||
{ agentName: '', agentId: '' },
|
||||
() => false);
|
||||
() => false,
|
||||
undefined);
|
||||
await session.getResult(); // and throw away the result
|
||||
});
|
||||
assert.ok(allEvents(messages));
|
||||
|
||||
@@ -382,7 +382,7 @@ function fetchSuggestion(accessor: ServicesAccessor, thread: vscode.CommentThrea
|
||||
agentId: getChatParticipantIdFromName(editorAgentName),
|
||||
agentName: editorAgentName,
|
||||
intentId: request.command,
|
||||
}, () => false);
|
||||
}, () => false, undefined);
|
||||
const result = await requestHandler.getResult();
|
||||
if (result.errorDetails) {
|
||||
throw new Error(result.errorDetails.message);
|
||||
|
||||
@@ -79,6 +79,7 @@ describe('AgentIntent /summarize command', () => {
|
||||
undefined,
|
||||
true,
|
||||
request,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const result = await intent.handleRequest(
|
||||
|
||||
@@ -73,6 +73,7 @@ export class ChatParticipantRequestHandler {
|
||||
private readonly token: CancellationToken,
|
||||
private readonly chatAgentArgs: IChatAgentArgs,
|
||||
private readonly yieldRequested: () => boolean,
|
||||
telemetryMessageId: string | undefined,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IEndpointProvider private readonly _endpointProvider: IEndpointProvider,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@@ -117,7 +118,8 @@ export class ChatParticipantRequestHandler {
|
||||
actualSessionId,
|
||||
this.documentContext,
|
||||
turns.length === 0,
|
||||
this.request
|
||||
this.request,
|
||||
telemetryMessageId
|
||||
);
|
||||
|
||||
const latestTurn = Turn.fromRequest(
|
||||
|
||||
@@ -12,6 +12,7 @@ import { isAutoModel } from '../../../platform/endpoint/node/autoChatEndpoint';
|
||||
import { ILanguageDiagnosticsService } from '../../../platform/languages/common/languageDiagnosticsService';
|
||||
import { IChatEndpoint } from '../../../platform/networking/common/networking';
|
||||
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
|
||||
import { TelemetryData as PlatformTelemetryData } from '../../../platform/telemetry/common/telemetryData';
|
||||
import { isNotebookCellOrNotebookChatInput } from '../../../util/common/notebooks';
|
||||
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
|
||||
import { isBYOKModel } from '../../byok/node/openAIEndpoint';
|
||||
@@ -29,7 +30,7 @@ import { IToolCall, IToolCallRound } from '../common/intents';
|
||||
import { IDocumentContext } from './documentContext';
|
||||
import { IIntent, TelemetryData } from './intents';
|
||||
import { RepoInfoTelemetry } from './repoInfoTelemetry';
|
||||
import { ConversationalBaseTelemetryData, createTelemetryWithId, extendUserMessageTelemetryData, getCodeBlocks, sendModelMessageTelemetry, sendOffTopicMessageTelemetry, sendUserActionTelemetry, sendUserMessageTelemetry } from './telemetry';
|
||||
import { ConversationalBaseTelemetryData, ConversationalTelemetryData, createTelemetryWithId, extendUserMessageTelemetryData, getCodeBlocks, sendModelMessageTelemetry, sendOffTopicMessageTelemetry, sendUserActionTelemetry, sendUserMessageTelemetry } from './telemetry';
|
||||
|
||||
// #region: internal telemetry for responses
|
||||
|
||||
@@ -206,7 +207,7 @@ type RequestInlineTelemetryMeasurements = RequestTelemetryMeasurements & {
|
||||
|
||||
export class ChatTelemetryBuilder {
|
||||
|
||||
public readonly baseUserTelemetry: ConversationalBaseTelemetryData = createTelemetryWithId();
|
||||
public readonly baseUserTelemetry: ConversationalBaseTelemetryData;
|
||||
|
||||
private readonly _repoInfoTelemetry: RepoInfoTelemetry;
|
||||
|
||||
@@ -220,8 +221,12 @@ export class ChatTelemetryBuilder {
|
||||
private readonly _documentContext: IDocumentContext | undefined,
|
||||
private readonly _firstTurn: boolean,
|
||||
private readonly _request: vscode.ChatRequest,
|
||||
telemetryMessageId: string | undefined,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
this.baseUserTelemetry = telemetryMessageId
|
||||
? new ConversationalTelemetryData(PlatformTelemetryData.createAndMarkAsIssued({ messageId: telemetryMessageId }))
|
||||
: createTelemetryWithId();
|
||||
// Repo info telemetry is held here as the begin event should be sent only by the first PanelChatTelemetry instance created for a user request.
|
||||
// and a new PanelChatTelemetry instance is created per step in the request.
|
||||
this._repoInfoTelemetry = this.instantiationService.createInstance(RepoInfoTelemetry, this.baseUserTelemetry.properties.messageId);
|
||||
|
||||
@@ -33,8 +33,10 @@ export interface IPromptCategorizerService {
|
||||
* This runs as a fire-and-forget operation and sends results to telemetry.
|
||||
* Only runs for panel location, first attempt, non-subagent requests.
|
||||
* Requires telemetry to be enabled and experiment flag to be set.
|
||||
*
|
||||
* @param telemetryMessageId The extension-generated request ID (shared with panel.request telemetry)
|
||||
*/
|
||||
categorizePrompt(request: vscode.ChatRequest, context: vscode.ChatContext): void;
|
||||
categorizePrompt(request: vscode.ChatRequest, context: vscode.ChatContext, telemetryMessageId: string): void;
|
||||
}
|
||||
|
||||
// Categorization outcome values for telemetry
|
||||
@@ -128,7 +130,7 @@ export class PromptCategorizerService implements IPromptCategorizerService {
|
||||
@ICopilotTokenStore private readonly copilotTokenStore: ICopilotTokenStore,
|
||||
) { }
|
||||
|
||||
categorizePrompt(request: vscode.ChatRequest, context: vscode.ChatContext): void {
|
||||
categorizePrompt(request: vscode.ChatRequest, context: vscode.ChatContext, telemetryMessageId: string): void {
|
||||
// Always enable for internal users; external users require experiment flag
|
||||
const isInternal = this.copilotTokenStore.copilotToken?.isInternal === true;
|
||||
if (!isInternal && !this.experimentationService.getTreatmentVariable<boolean>(EXP_FLAG_PROMPT_CATEGORIZATION)) {
|
||||
@@ -152,12 +154,12 @@ export class PromptCategorizerService implements IPromptCategorizerService {
|
||||
}
|
||||
|
||||
// Fire and forget - don't await
|
||||
this._categorizePromptAsync(request, context).catch(err => {
|
||||
this._categorizePromptAsync(request, context, telemetryMessageId).catch(err => {
|
||||
this.logService.error(`[PromptCategorizer] Error categorizing prompt: ${err instanceof Error ? err.message : String(err)}`);
|
||||
});
|
||||
}
|
||||
|
||||
private async _categorizePromptAsync(request: vscode.ChatRequest, _context: vscode.ChatContext): Promise<void> {
|
||||
private async _categorizePromptAsync(request: vscode.ChatRequest, _context: vscode.ChatContext, telemetryMessageId: string): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
let outcome: typeof CATEGORIZATION_OUTCOMES[keyof typeof CATEGORIZATION_OUTCOMES] = CATEGORIZATION_OUTCOMES.ERROR;
|
||||
let errorDetail = '';
|
||||
@@ -284,7 +286,8 @@ export class PromptCategorizerService implements IPromptCategorizerService {
|
||||
"owner": "digitarald",
|
||||
"comment": "Classifies agent requests for understanding user intent and response quality",
|
||||
"sessionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The chat session identifier" },
|
||||
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The unique request identifier within the session" },
|
||||
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The extension-generated request identifier, matches panel.request requestId" },
|
||||
"vscodeRequestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The VS Code chat request id, for joining with VS Code telemetry events" },
|
||||
"modeName": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The chat mode name being used" },
|
||||
"currentLanguage": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The language ID of the active editor" },
|
||||
"outcome": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Classification outcome: empty string for success, partialClassification for recovered core fields, or error kind (timeout, requestFailed, noToolCall, parseError, invalidClassification, error)" },
|
||||
@@ -304,7 +307,8 @@ export class PromptCategorizerService implements IPromptCategorizerService {
|
||||
'promptCategorization',
|
||||
{
|
||||
sessionId: request.sessionId ?? '',
|
||||
requestId: request.id ?? '',
|
||||
requestId: telemetryMessageId,
|
||||
vscodeRequestId: request.id ?? '',
|
||||
modeName: request.modeInstructions2?.name,
|
||||
currentLanguage: currentLanguage ?? '',
|
||||
outcome,
|
||||
@@ -334,7 +338,8 @@ export class PromptCategorizerService implements IPromptCategorizerService {
|
||||
'promptCategorization',
|
||||
{
|
||||
sessionId: request.sessionId ?? '',
|
||||
requestId: request.id ?? '',
|
||||
requestId: telemetryMessageId,
|
||||
vscodeRequestId: request.id ?? '',
|
||||
modeName: request.modeInstructions2?.name,
|
||||
currentLanguage: currentLanguage ?? '',
|
||||
outcome,
|
||||
|
||||
@@ -161,7 +161,7 @@ suite('defaultIntentRequestHandler', () => {
|
||||
CancellationToken.None,
|
||||
undefined,
|
||||
ChatLocation.Panel,
|
||||
instaService.createInstance(ChatTelemetryBuilder, Date.now(), sessionId, undefined, turns.length > 1, request),
|
||||
instaService.createInstance(ChatTelemetryBuilder, Date.now(), sessionId, undefined, turns.length > 1, request, undefined),
|
||||
{ maxToolCallIterations },
|
||||
undefined,
|
||||
);
|
||||
|
||||
@@ -77,7 +77,7 @@ suite('Copilot Chat Sanity Test', function () {
|
||||
try {
|
||||
conversationFeature.activated = true;
|
||||
let stream = new SpyChatResponseStream();
|
||||
let interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('Write me a for loop in javascript'), stream, fakeToken, { agentName: '', agentId: '', intentId: '' }, () => false);
|
||||
let interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('Write me a for loop in javascript'), stream, fakeToken, { agentName: '', agentId: '', intentId: '' }, () => false, undefined);
|
||||
|
||||
await interactiveSession.getResult();
|
||||
|
||||
@@ -85,7 +85,7 @@ suite('Copilot Chat Sanity Test', function () {
|
||||
const oldText = stream.currentProgress;
|
||||
|
||||
stream = new SpyChatResponseStream();
|
||||
interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('Can you make it in typescript instead'), stream, fakeToken, { agentName: '', agentId: '', intentId: '' }, () => false);
|
||||
interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('Can you make it in typescript instead'), stream, fakeToken, { agentName: '', agentId: '', intentId: '' }, () => false, undefined);
|
||||
const result2 = await interactiveSession.getResult();
|
||||
|
||||
assert.ok(stream.currentProgress, 'Expected progress after second request');
|
||||
@@ -117,7 +117,7 @@ suite('Copilot Chat Sanity Test', function () {
|
||||
let stream = new SpyChatResponseStream();
|
||||
const testRequest = new TestChatRequest(`You must use the get_errors tool to check the window for errors. It may fail, that's ok, just testing, don't retry.`);
|
||||
testRequest.tools.set(ContributedToolName.GetErrors, true);
|
||||
let interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], testRequest, stream, fakeToken, { agentName: '', agentId: '', intentId: Intent.Agent }, () => false);
|
||||
let interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], testRequest, stream, fakeToken, { agentName: '', agentId: '', intentId: Intent.Agent }, () => false, undefined);
|
||||
|
||||
const onWillInvokeTool = Event.toPromise(toolsService.onWillInvokeTool);
|
||||
const getResultPromise = interactiveSession.getResult();
|
||||
@@ -128,7 +128,7 @@ suite('Copilot Chat Sanity Test', function () {
|
||||
const oldText = stream.currentProgress;
|
||||
|
||||
stream = new SpyChatResponseStream();
|
||||
interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('And what is 1+1'), stream, fakeToken, { agentName: '', agentId: '', intentId: Intent.Agent }, () => false);
|
||||
interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('And what is 1+1'), stream, fakeToken, { agentName: '', agentId: '', intentId: Intent.Agent }, () => false, undefined);
|
||||
const result2 = await interactiveSession.getResult();
|
||||
|
||||
assert.ok(stream.currentProgress, 'Expected progress after second request');
|
||||
@@ -152,7 +152,7 @@ suite('Copilot Chat Sanity Test', function () {
|
||||
try {
|
||||
conversationFeature.activated = true;
|
||||
const progressReport = new SpyChatResponseStream();
|
||||
const interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('What is a fibonacci sequence?'), progressReport, fakeToken, { agentName: '', agentId: '', intentId: 'explain' }, () => false);
|
||||
const interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('What is a fibonacci sequence?'), progressReport, fakeToken, { agentName: '', agentId: '', intentId: 'explain' }, () => false, undefined);
|
||||
|
||||
// Ask a `/explain` question
|
||||
await interactiveSession.getResult();
|
||||
|
||||
@@ -93,6 +93,7 @@ export function generateScenarioTestRunner(scenario: Scenario, evaluator: Scenar
|
||||
parsedQuery.participantName,
|
||||
},
|
||||
() => false,
|
||||
undefined,
|
||||
);
|
||||
const result = await interactiveSession.getResult();
|
||||
assert.ok(!result.errorDetails, result.errorDetails?.message);
|
||||
|
||||
@@ -490,7 +490,7 @@ export async function simulateEditingScenario(
|
||||
intentId: request.command
|
||||
};
|
||||
|
||||
const requestHandler = instaService.createInstance(ChatParticipantRequestHandler, history, request, stream, CancellationToken.None, agentArgs, () => false);
|
||||
const requestHandler = instaService.createInstance(ChatParticipantRequestHandler, history, request, stream, CancellationToken.None, agentArgs, () => false, undefined);
|
||||
const result = await requestHandler.getResult();
|
||||
history.push(new ChatRequestTurn(request.prompt, request.command, [...request.references], '', []));
|
||||
history.push(new ChatResponseTurn([new ChatResponseMarkdownPart(markdownChunks.join(''))], result, ''));
|
||||
|
||||
Reference in New Issue
Block a user