From db0b2684fd338f27a835726b2d967c67717ffb82 Mon Sep 17 00:00:00 2001 From: luca cappa Date: Fri, 9 May 2025 14:49:58 -0700 Subject: [PATCH] handle speculative requests with the cache --- .../copilotCompletionContextProvider.ts | 65 ++++++++++++------- .../copilotCompletionContextTelemetry.ts | 5 ++ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/Extension/src/LanguageServer/copilotCompletionContextProvider.ts b/Extension/src/LanguageServer/copilotCompletionContextProvider.ts index 3eef9e431..cdf75308f 100644 --- a/Extension/src/LanguageServer/copilotCompletionContextProvider.ts +++ b/Extension/src/LanguageServer/copilotCompletionContextProvider.ts @@ -79,8 +79,8 @@ export class CopilotCompletionContextProvider implements ContextResolver"}:${copilotCompletionContext.caretOffset} `; + logMessage += `(response.featureFlag:${copilotCompletionContext.featureFlag})`; + logMessage += `(response.uri:${copilotCompletionContext.sourceFileUri || ""}:${copilotCompletionContext.caretOffset})`; } telemetry.addResponseMetadata(copilotCompletionContext.areSnippetsMissing, copilotCompletionContext.snippets.length, @@ -181,7 +180,7 @@ response.uri:${copilotCompletionContext.sourceFileUri || ""}:${copilotC } catch (e: any) { if (e instanceof vscode.CancellationError || e.message === CancellationError.Canceled) { telemetry.addInternalCanceled(CopilotCompletionContextProvider.getRoundedDuration(startTime)); - logMessage += ` (internal cancellation) `; + logMessage += `(internal cancellation)`; throw InternalCancellationError; } @@ -338,11 +337,18 @@ response.uri:${copilotCompletionContext.sourceFileUri || ""}:${copilotC } else { return [defaultValue, defaultValue ? CopilotCompletionKind.GotFromCache : CopilotCompletionKind.MissingCacheMiss]; } } + private static isStaleCacheHit(caretOffset: number, cacheCaretOffset: number, maxCaretDistance: number): boolean { + return Math.abs(caretOffset - caretOffset) > maxCaretDistance; + } + + private static createContextItems(copilotCompletionContext: CopilotCompletionContextResult | undefined): SupportedContextItem[] { + return [...copilotCompletionContext?.snippets ?? [], ...copilotCompletionContext?.traits ?? []] as SupportedContextItem[]; + } + public async resolve(context: ResolveRequest, copilotCancel: vscode.CancellationToken): Promise { const proposedEdits = context.documentContext.proposedEdits; - if (proposedEdits) { return []; } // Ignore the request if there are proposed edits. const resolveStartTime = performance.now(); - let logMessage = `Copilot: resolve(${context.documentContext.uri}: ${context.documentContext.offset}):`; + let logMessage = `Copilot: resolve(${context.documentContext.uri}:${context.documentContext.offset}):`; const cppTimeBudgetMs = await this.fetchTimeBudgetMs(context); const maxCaretDistance = await this.fetchMaxDistanceToCaret(context); const maxSnippetCount = await this.fetchMaxSnippetCount(context); @@ -363,37 +369,49 @@ response.uri:${copilotCompletionContext.sourceFileUri || ""}:${copilotC }); if (featureFlag === undefined) { return []; } const cacheEntry: CacheEntry | undefined = this.completionContextCache.get(docUri.toString()); - const defaultValue = cacheEntry?.[1]; - [copilotCompletionContext, copilotCompletionContextKind] = await this.resolveResultAndKind(context, featureFlag, - telemetry.fork(), defaultValue, resolveStartTime, cppTimeBudgetMs, maxSnippetCount, maxSnippetLength, doAggregateSnippets, copilotCancel); + if (proposedEdits) { + const defaultValue = cacheEntry?.[1]; + const isStaleCache = defaultValue !== undefined ? CopilotCompletionContextProvider.isStaleCacheHit(docOffset, defaultValue.caretOffset, maxCaretDistance) : true; + const contextItems = isStaleCache ? [] : CopilotCompletionContextProvider.createContextItems(defaultValue); + copilotCompletionContext = isStaleCache ? undefined : defaultValue; + copilotCompletionContextKind = isStaleCache ? CopilotCompletionKind.StaleCacheHit : CopilotCompletionKind.GotFromCache; + telemetry.addSpeculativeRequestMetadata(proposedEdits.length); + if (cacheEntry?.[0]) { + telemetry.addCacheHitEntryGuid(cacheEntry[0]); + } + return contextItems; + } + const [resultContext, resultKind] = await this.resolveResultAndKind(context, featureFlag, + telemetry.fork(), cacheEntry?.[1], resolveStartTime, cppTimeBudgetMs, maxSnippetCount, maxSnippetLength, doAggregateSnippets, copilotCancel); + copilotCompletionContext = resultContext; + copilotCompletionContextKind = resultKind; + logMessage += `(id: ${copilotCompletionContext?.requestId})`; // Fix up copilotCompletionContextKind accounting for stale-cache-hits. if (copilotCompletionContextKind === CopilotCompletionKind.GotFromCache && copilotCompletionContext && cacheEntry) { telemetry.addCacheHitEntryGuid(cacheEntry[0]); const cachedData = cacheEntry[1]; - if (Math.abs(cachedData.caretOffset - context.documentContext.offset) > maxCaretDistance) { + if (CopilotCompletionContextProvider.isStaleCacheHit(docOffset, cachedData.caretOffset, maxCaretDistance)) { copilotCompletionContextKind = CopilotCompletionKind.StaleCacheHit; copilotCompletionContext.snippets = []; } } - telemetry.addCompletionContextKind(copilotCompletionContextKind); // Handle cancellation. if (copilotCompletionContextKind === CopilotCompletionKind.Canceled) { const duration: number = CopilotCompletionContextProvider.getRoundedDuration(resolveStartTime); telemetry.addCopilotCanceled(duration); throw new CopilotCancellationError(); } - logMessage += ` (id: ${copilotCompletionContext?.requestId})`; - return [...copilotCompletionContext?.snippets ?? [], ...copilotCompletionContext?.traits ?? []] as SupportedContextItem[]; + return CopilotCompletionContextProvider.createContextItems(copilotCompletionContext); } catch (e: any) { if (e instanceof CopilotCancellationError) { telemetry.addCopilotCanceled(CopilotCompletionContextProvider.getRoundedDuration(resolveStartTime)); - logMessage += ` (copilot cancellation)`; + logMessage += `(copilot cancellation)`; throw e; } if (e instanceof InternalCancellationError) { telemetry.addInternalCanceled(CopilotCompletionContextProvider.getRoundedDuration(resolveStartTime)); - logMessage += ` (internal cancellation) `; + logMessage += `(internal cancellation)`; throw e; } if (e instanceof CancellationError) { throw e; } @@ -403,13 +421,15 @@ response.uri:${copilotCompletionContext.sourceFileUri || ""}:${copilotC throw e; } finally { const duration: number = CopilotCompletionContextProvider.getRoundedDuration(resolveStartTime); - logMessage += `featureFlag:${featureFlag?.toString()}, `; + logMessage += `(featureFlag:${featureFlag?.toString()})`; + if (proposedEdits) { logMessage += `(speculative request, proposedEdits:${proposedEdits.length})`; } if (copilotCompletionContext === undefined) { logMessage += `result is undefined and no code snippets provided(${copilotCompletionContextKind.toString()}), elapsed time:${duration} ms`; } else { logMessage += `for ${docUri}:${docOffset} provided ${copilotCompletionContext.snippets.length} code snippet(s)(${copilotCompletionContextKind.toString()}\ - ${copilotCompletionContext?.areSnippetsMissing ? ", missing code snippets" : ""}) and ${copilotCompletionContext.traits.length} trait(s), elapsed time:${duration} ms`; +${copilotCompletionContext?.areSnippetsMissing ? "(missing code snippets)" : ""}) and ${copilotCompletionContext.traits.length} trait(s), elapsed time:${duration} ms`; } + telemetry.addCompletionContextKind(copilotCompletionContextKind); telemetry.addResponseMetadata(copilotCompletionContext?.areSnippetsMissing ?? true, copilotCompletionContext?.snippets.length, copilotCompletionContext?.traits.length, copilotCompletionContext?.caretOffset, copilotCompletionContext?.featureFlag); @@ -447,4 +467,3 @@ response.uri:${copilotCompletionContext.sourceFileUri || ""}:${copilotC } } } - diff --git a/Extension/src/LanguageServer/copilotCompletionContextTelemetry.ts b/Extension/src/LanguageServer/copilotCompletionContextTelemetry.ts index 802204e9c..e1451007a 100644 --- a/Extension/src/LanguageServer/copilotCompletionContextTelemetry.ts +++ b/Extension/src/LanguageServer/copilotCompletionContextTelemetry.ts @@ -76,6 +76,11 @@ export class CopilotCompletionContextTelemetry { this.addMetric('getClientForElapsedMs', duration); } + public addSpeculativeRequestMetadata(proposedEditsCount: number): void { + this.addProperty('request.isSpeculativeRequest', 'true'); + this.addMetric('request.proposedEditsCount', proposedEditsCount); + } + public addResponseMetadata(areSnippetsMissing: boolean, codeSnippetsCount?: number, traitsCount?: number, caretOffset?: number, featureFlag?: CopilotCompletionContextFeatures): void { this.addProperty('response.areCodeSnippetsMissing', areSnippetsMissing.toString());