mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-05 06:15:42 +01:00
Merge pull request #319941 from microsoft/fb/copilot-restricted-image-telemetry
Emit restricted image metadata for Copilot telemetry
This commit is contained in:
@@ -9,7 +9,7 @@ import { getImageTelemetryEventMeasurements, type ImageTelemetryMeasurements } f
|
||||
import { FetcherId } from '../../../platform/networking/common/fetcherService';
|
||||
import { IChatEndpoint, IChatRequestTelemetryProperties, IEndpointBody } from '../../../platform/networking/common/networking';
|
||||
import { ChatCompletion } from '../../../platform/networking/common/openai';
|
||||
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
|
||||
import { ITelemetryService, type TelemetryEventMeasurements, type TelemetryEventProperties } from '../../../platform/telemetry/common/telemetry';
|
||||
import { TelemetryData } from '../../../platform/telemetry/common/telemetryData';
|
||||
import { isBYOKModel } from '../../byok/node/openAIEndpoint';
|
||||
|
||||
@@ -99,6 +99,19 @@ function getTurnFromBaseTelemetry(baseTelemetry: TelemetryData): number | undefi
|
||||
return Number.isFinite(parsedTurnIndex) ? parsedTurnIndex : undefined;
|
||||
}
|
||||
|
||||
function sendResponseTelemetryEvent(
|
||||
telemetryService: ITelemetryService,
|
||||
eventName: string,
|
||||
properties: TelemetryEventProperties,
|
||||
measurements: TelemetryEventMeasurements,
|
||||
imageTelemetryMeasurements: ImageTelemetryMeasurements,
|
||||
): void {
|
||||
telemetryService.sendTelemetryEvent(eventName, { github: true, microsoft: true }, properties, measurements);
|
||||
if (imageTelemetryMeasurements.imageCount > 0) {
|
||||
telemetryService.sendEnhancedGHTelemetryEvent(eventName, properties, measurements);
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatMLFetcherTelemetrySender {
|
||||
|
||||
public static sendSuccessTelemetry(
|
||||
@@ -196,7 +209,7 @@ export class ChatMLFetcherTelemetrySender {
|
||||
"iterationNumber": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Iteration number within the tool calling loop" }
|
||||
}
|
||||
*/
|
||||
telemetryService.sendTelemetryEvent('response.success', { github: true, microsoft: true }, {
|
||||
sendResponseTelemetryEvent(telemetryService, 'response.success', {
|
||||
reason: chatCompletion.finishReason,
|
||||
filterReason: chatCompletion.filterReason,
|
||||
source: baseTelemetry?.properties.messageSource ?? 'unknown',
|
||||
@@ -248,7 +261,7 @@ export class ChatMLFetcherTelemetrySender {
|
||||
bytesReceived,
|
||||
suspendEventSeen: suspendEventSeen ? 1 : 0,
|
||||
resumeEventSeen: resumeEventSeen ? 1 : 0,
|
||||
});
|
||||
}, imageTelemetryMeasurements);
|
||||
}
|
||||
|
||||
public static sendCancellationTelemetry(
|
||||
@@ -339,7 +352,7 @@ export class ChatMLFetcherTelemetrySender {
|
||||
"resumeEventSeen": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Whether a system resume event was seen during the request", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
telemetryService.sendTelemetryEvent('response.cancelled', { github: true, microsoft: true }, {
|
||||
sendResponseTelemetryEvent(telemetryService, 'response.cancelled', {
|
||||
apiType,
|
||||
source,
|
||||
requestId,
|
||||
@@ -371,7 +384,7 @@ export class ChatMLFetcherTelemetrySender {
|
||||
bytesReceived,
|
||||
suspendEventSeen: suspendEventSeen ? 1 : 0,
|
||||
resumeEventSeen: resumeEventSeen ? 1 : 0,
|
||||
});
|
||||
}, imageTelemetryMeasurements);
|
||||
}
|
||||
|
||||
public static sendResponseErrorTelemetry(
|
||||
@@ -453,7 +466,7 @@ export class ChatMLFetcherTelemetrySender {
|
||||
"resumeEventSeen": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "comment": "Whether a system resume event was seen during the request", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
telemetryService.sendTelemetryEvent('response.error', { github: true, microsoft: true }, {
|
||||
sendResponseTelemetryEvent(telemetryService, 'response.error', {
|
||||
type: processed.type,
|
||||
reason: processed.reasonDetail || processed.reason,
|
||||
source: telemetryProperties?.messageSource ?? 'unknown',
|
||||
@@ -489,6 +502,6 @@ export class ChatMLFetcherTelemetrySender {
|
||||
bytesReceived,
|
||||
suspendEventSeen: suspendEventSeen ? 1 : 0,
|
||||
resumeEventSeen: resumeEventSeen ? 1 : 0,
|
||||
});
|
||||
}, imageTelemetryMeasurements);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ChatLocation } from '../../../vscodeTypes';
|
||||
import { IAuthenticationService } from '../../authentication/common/authentication';
|
||||
import { ConfigKey, IConfigurationService } from '../../configuration/common/configurationService';
|
||||
import { IEnvService } from '../../env/common/envService';
|
||||
import { getImageTelemetryEventMeasurements, getImageTelemetryMeasurementsFromReferences } from '../../image/common/imageTelemetry';
|
||||
import { getImageTelemetryEventMeasurements, getImageTelemetryMeasurementsFromReferences, type ImageTelemetryMeasurements } from '../../image/common/imageTelemetry';
|
||||
import { ILogService } from '../../log/common/logService';
|
||||
import { createCapiClientFetchedValue } from '../../networking/common/capiClientFetchedValue';
|
||||
import { isAbortError } from '../../networking/common/fetcherService';
|
||||
@@ -191,12 +191,12 @@ export class AutomodeService extends Disposable implements IAutomodeService {
|
||||
if (entry?.needsReEval) {
|
||||
entry.needsReEval = false;
|
||||
}
|
||||
const imageTelemetryMeasurements = getImageTelemetryMeasurementsFromReferences(chatRequest?.references);
|
||||
const imageTelemetryEventMeasurements = getImageTelemetryEventMeasurements(imageTelemetryMeasurements);
|
||||
|
||||
const routerResult = skipRouter
|
||||
? { lastRoutedPrompt: chatRequest?.prompt?.trim() ?? entry?.lastRoutedPrompt }
|
||||
: await this._tryRouterSelection(chatRequest, conversationId, entry, token, knownEndpoints);
|
||||
const imageTelemetryMeasurements = getImageTelemetryMeasurementsFromReferences(chatRequest?.references);
|
||||
const imageTelemetryEventMeasurements = getImageTelemetryEventMeasurements(imageTelemetryMeasurements);
|
||||
: await this._tryRouterSelection(chatRequest, conversationId, entry, token, knownEndpoints, imageTelemetryEventMeasurements);
|
||||
let selectedModel = routerResult.selectedModel;
|
||||
const lastRoutedPrompt = routerResult.lastRoutedPrompt;
|
||||
const routerFallbackReason = routerResult.fallbackReason;
|
||||
@@ -314,6 +314,7 @@ export class AutomodeService extends Disposable implements IAutomodeService {
|
||||
entry: AutoModelCacheEntry | undefined,
|
||||
token: AutoModeAPIResponse,
|
||||
knownEndpoints: IChatEndpoint[],
|
||||
imageTelemetryEventMeasurements: Partial<ImageTelemetryMeasurements>,
|
||||
): Promise<{ selectedModel?: IChatEndpoint; lastRoutedPrompt?: string; fallbackReason?: string; candidateModel?: string }> {
|
||||
const prompt = chatRequest?.prompt?.trim();
|
||||
const lastRoutedPrompt = entry?.lastRoutedPrompt ?? prompt;
|
||||
@@ -360,7 +361,7 @@ export class AutomodeService extends Disposable implements IAutomodeService {
|
||||
this._logService.info(`[AutomodeService] Filtered ${droppedModels.length} unresolvable model(s) before routing: [${droppedModels.join(', ')}]`);
|
||||
}
|
||||
|
||||
const result = await this._routerDecisionFetcher.getRouterDecision(prompt, token.session_token, routableModels, undefined, contextSignals, conversationId, chatRequest?.id, routingMethod, hasImage(chatRequest));
|
||||
const result = await this._routerDecisionFetcher.getRouterDecision(prompt, token.session_token, routableModels, undefined, contextSignals, conversationId, chatRequest?.id, routingMethod, hasImage(chatRequest), imageTelemetryEventMeasurements);
|
||||
|
||||
if (result.fallback) {
|
||||
this._logService.info(`[AutomodeService] Router signaled fallback: ${result.fallback_reason ?? 'unknown'}, routing_method=${result.routing_method ?? 'n/a'}`);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { RequestType } from '@vscode/copilot-api';
|
||||
import { Codicon } from '../../../util/vs/base/common/codicons';
|
||||
import { IAuthenticationService } from '../../authentication/common/authentication';
|
||||
import type { ImageTelemetryMeasurements } from '../../image/common/imageTelemetry';
|
||||
import { ILogService } from '../../log/common/logService';
|
||||
import { Response } from '../../networking/common/fetcherService';
|
||||
import { IRequestLogger, LoggedRequestKind } from '../../requestLogger/common/requestLogger';
|
||||
@@ -66,7 +67,7 @@ export class RouterDecisionFetcher {
|
||||
) {
|
||||
}
|
||||
|
||||
async getRouterDecision(query: string, autoModeToken: string, availableModels: string[], stickyThreshold?: number, contextSignals?: RoutingContextSignals, conversationId?: string, vscodeRequestId?: string, routingMethod?: string, hasImage?: boolean): Promise<RouterDecisionResponse> {
|
||||
async getRouterDecision(query: string, autoModeToken: string, availableModels: string[], stickyThreshold?: number, contextSignals?: RoutingContextSignals, conversationId?: string, vscodeRequestId?: string, routingMethod?: string, hasImage?: boolean, imageTelemetryEventMeasurements?: Partial<ImageTelemetryMeasurements>): Promise<RouterDecisionResponse> {
|
||||
const startTime = Date.now();
|
||||
const requestBody: Record<string, unknown> = { prompt: query, available_models: availableModels, ...contextSignals };
|
||||
if (stickyThreshold !== undefined) {
|
||||
@@ -204,6 +205,7 @@ export class RouterDecisionFetcher {
|
||||
chosenShortfall: result.chosen_shortfall,
|
||||
scoreNeedsReasoning: result.scores.needs_reasoning,
|
||||
scoreNoReasoning: result.scores.no_reasoning,
|
||||
...imageTelemetryEventMeasurements,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('AutomodeService', () => {
|
||||
let configurationService: IConfigurationService;
|
||||
let mockChatEndpoint: IChatEndpoint;
|
||||
let envService: NullEnvService;
|
||||
let mockTelemetryService: ITelemetryService & { sendMSFTTelemetryEvent: ReturnType<typeof vi.fn> };
|
||||
let mockTelemetryService: ITelemetryService & { sendEnhancedGHTelemetryEvent: ReturnType<typeof vi.fn>; sendMSFTTelemetryEvent: ReturnType<typeof vi.fn> };
|
||||
|
||||
function createEndpoint(model: string, provider: string, overrides?: Partial<IChatEndpoint>): IChatEndpoint {
|
||||
return {
|
||||
@@ -154,7 +154,7 @@ describe('AutomodeService', () => {
|
||||
sendMSFTTelemetryErrorEvent: vi.fn(),
|
||||
sendSharedTelemetryEvent: vi.fn(),
|
||||
sendEnhancedGHTelemetryEvent: vi.fn(),
|
||||
} as unknown as ITelemetryService & { sendMSFTTelemetryEvent: ReturnType<typeof vi.fn> };
|
||||
} as unknown as ITelemetryService & { sendEnhancedGHTelemetryEvent: ReturnType<typeof vi.fn>; sendMSFTTelemetryEvent: ReturnType<typeof vi.fn> };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -1142,6 +1142,20 @@ describe('AutomodeService', () => {
|
||||
imagePngCount: 1,
|
||||
imageClipboardCount: 1,
|
||||
});
|
||||
|
||||
const restrictedEvent = mockTelemetryService.sendEnhancedGHTelemetryEvent.mock.calls.find((call: unknown[]) => call[0] === 'automode.routerDecisionRestricted');
|
||||
expect(restrictedEvent).toBeDefined();
|
||||
expect(restrictedEvent![2]).toMatchObject({
|
||||
imageCount: 1,
|
||||
totalImageBytes: 24,
|
||||
maxImageBytes: 24,
|
||||
maxImageWidth: 7,
|
||||
maxImageHeight: 11,
|
||||
maxImagePixels: 77,
|
||||
totalImagePixels: 77,
|
||||
imagePngCount: 1,
|
||||
imageClipboardCount: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not emit routerModelSelection when router fails', async () => {
|
||||
|
||||
Reference in New Issue
Block a user