-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cody completion: Mega bug bash thread #52365
Changes from all commits
ca6c40b
8427863
d247bb6
bcc2727
b75a8d0
42ef05a
ec6c6b5
454201c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
import { LRUCache } from 'lru-cache' | ||
import * as vscode from 'vscode' | ||
|
||
import { Message } from '@sourcegraph/cody-shared/src/sourcegraph-api' | ||
import { SourcegraphNodeCompletionsClient } from '@sourcegraph/cody-shared/src/sourcegraph-api/completions/nodeClient' | ||
|
||
import { logEvent } from '../event-logger' | ||
import { debug } from '../log' | ||
|
||
import { CompletionsCache } from './cache' | ||
import { getContext } from './context' | ||
|
@@ -19,7 +21,6 @@ function lastNLines(text: string, n: number): string { | |
return lines.slice(Math.max(0, lines.length - n)).join('\n') | ||
} | ||
|
||
const estimatedLLMResponseLatencyMS = 700 | ||
const inlineCompletionsCache = new CompletionsCache() | ||
|
||
export class CodyCompletionItemProvider implements vscode.InlineCompletionItemProvider { | ||
|
@@ -28,6 +29,9 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
private maxSuffixTokens: number | ||
private abortOpenInlineCompletions: () => void = () => {} | ||
private abortOpenMultilineCompletion: () => void = () => {} | ||
private lastContentChanges: LRUCache<string, 'add' | 'del'> = new LRUCache<string, 'add' | 'del'>({ | ||
max: 10, | ||
}) | ||
|
||
constructor( | ||
private webviewErrorMessager: (error: string) => Promise<void>, | ||
|
@@ -43,6 +47,13 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
this.promptTokens = this.contextWindowTokens - this.responseTokens | ||
this.maxPrefixTokens = Math.floor(this.promptTokens * this.prefixPercentage) | ||
this.maxSuffixTokens = Math.floor(this.promptTokens * this.suffixPercentage) | ||
|
||
vscode.workspace.onDidChangeTextDocument(event => { | ||
const document = event.document | ||
const text = event.contentChanges[0].text | ||
|
||
this.lastContentChanges.set(document.fileName, text.length > 0 ? 'add' : 'del') | ||
}) | ||
} | ||
|
||
public async provideInlineCompletionItems( | ||
|
@@ -54,12 +65,11 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
try { | ||
return await this.provideInlineCompletionItemsInner(document, position, context, token) | ||
} catch (error) { | ||
console.error(error) | ||
|
||
if (error.message === 'aborted') { | ||
return [] | ||
} | ||
|
||
console.error(error) | ||
debug('CodyCompletionProvider:inline:error', `${error.toString()}\n${error.stack}`) | ||
return [] | ||
} | ||
} | ||
|
@@ -96,6 +106,21 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
|
||
const { prefix, suffix, prevLine: precedingLine } = docContext | ||
|
||
// Avoid showing completions when we're deleting code (Cody can only insert code at the | ||
// moment) | ||
const lastChange = this.lastContentChanges.get(document.fileName) ?? 'add' | ||
if (lastChange === 'del') { | ||
// When a line was deleted, only look up cached items and only include them if the | ||
// untruncated prefix matches. This fixes some weird issues where the completion would | ||
// render if you insert whitespace but not on the original place when you delete it | ||
// again | ||
const cachedCompletions = inlineCompletionsCache.get(prefix, false) | ||
if (cachedCompletions) { | ||
return cachedCompletions.map(toInlineCompletionItem) | ||
} | ||
return [] | ||
} | ||
|
||
const cachedCompletions = inlineCompletionsCache.get(prefix) | ||
if (cachedCompletions) { | ||
return cachedCompletions.map(toInlineCompletionItem) | ||
|
@@ -125,7 +150,7 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
contextChars | ||
) | ||
|
||
let waitMs: number | ||
let timeout: number | ||
const completers: CompletionProvider[] = [] | ||
|
||
// VS Code does not show completions if we are in the process of writing a word or if a | ||
|
@@ -138,17 +163,16 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
|
||
// TODO(philipp-spiess): Add a better detection for start-of-block and don't require C like | ||
// languages. | ||
// TODO(philipp-spiess): Temporary delete multi-line inline completions while I fix a bug | ||
const multilineEnabledLanguage = false | ||
// document.languageId === 'typescript' || document.languageId === 'javascript' || document.languageId === 'go' | ||
const multilineEnabledLanguage = | ||
document.languageId === 'typescript' || document.languageId === 'javascript' || document.languageId === 'go' | ||
if ( | ||
multilineEnabledLanguage && | ||
// Only trigger multiline inline suggestions for empty lines | ||
precedingLine.trim() === '' && | ||
// Only trigger multiline inline suggestions for the beginning of blocks | ||
prefix.trim().at(prefix.trim().length - 1) === '{' | ||
) { | ||
waitMs = 500 | ||
timeout = 500 | ||
completers.push( | ||
new EndOfLineCompletionProvider( | ||
this.completionsClient, | ||
|
@@ -159,12 +183,12 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
suffix, | ||
'', | ||
3, | ||
true // multiline | ||
'block' // multiline | ||
) | ||
) | ||
} else if (precedingLine.trim() === '') { | ||
// Start of line: medium debounce | ||
waitMs = 500 | ||
timeout = 200 | ||
completers.push( | ||
new EndOfLineCompletionProvider( | ||
this.completionsClient, | ||
|
@@ -182,7 +206,7 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
return [] | ||
} else { | ||
// End of line: long debounce, complete until newline | ||
waitMs = 1000 | ||
timeout = 500 | ||
completers.push( | ||
new EndOfLineCompletionProvider( | ||
this.completionsClient, | ||
|
@@ -209,11 +233,7 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
) | ||
} | ||
|
||
// TODO(beyang): trigger on context quality (better context means longer completion) | ||
|
||
await new Promise<void>(resolve => | ||
setTimeout(() => resolve(), Math.max(0, waitMs - estimatedLLMResponseLatencyMS)) | ||
) | ||
Comment on lines
-214
to
-216
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug fix: The previous heuristics would use the I've tweaked the timeouts again. It will unfortunlaty be a bit slower but in most cases it's not noticeable. Start of the line completions are now properly debounced by 200ms though! |
||
await new Promise<void>(resolve => setTimeout(resolve, timeout)) | ||
|
||
// We don't need to make a request at all if the signal is already aborted after the | ||
// debounce | ||
|
@@ -222,14 +242,19 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr | |
} | ||
|
||
logEvent('CodyVSCodeExtension:completion:started', LOG_INLINE, LOG_INLINE) | ||
const start = Date.now() | ||
|
||
const results = (await Promise.all(completers.map(c => c.generateCompletions(abortController.signal)))).flat() | ||
|
||
inlineCompletionsCache.add(results) | ||
|
||
logEvent('CodyVSCodeExtension:completion:suggested', LOG_INLINE, LOG_INLINE) | ||
const results = rankCompletions( | ||
(await Promise.all(completers.map(c => c.generateCompletions(abortController.signal)))).flat() | ||
) | ||
|
||
return results.map(toInlineCompletionItem) | ||
if (hasVisibleCompletions(results)) { | ||
const params = { ...LOG_INLINE, latency: Date.now() - start, timeout } | ||
logEvent('CodyVSCodeExtension:completion:suggested', params, params) | ||
inlineCompletionsCache.add(results) | ||
return results.map(toInlineCompletionItem) | ||
Comment on lines
+251
to
+255
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug fix: We were recording completions as being suggested even though they might all be empty. I've also added some logging to get a better understanding of latency and timeouts used. |
||
} | ||
return [] | ||
} | ||
|
||
public async fetchAndShowCompletions(): Promise<void> { | ||
|
@@ -410,3 +435,12 @@ function toInlineCompletionItem(completion: Completion): vscode.InlineCompletion | |
command: 'cody.completions.inline.accepted', | ||
}) | ||
} | ||
|
||
function rankCompletions(completions: Completion[]): Completion[] { | ||
// TODO(philipp-spiess): Improve ranking to something more complex then just length | ||
return completions.sort((a, b) => b.content.split('\n').length - a.content.split('\n').length) | ||
} | ||
Comment on lines
+439
to
+442
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New heuristics: Added ranking for completions. It's very stupid now and prefers whatever has the longes lines of code. Lol. Got some more ideas lined up to try out, though! |
||
|
||
function hasVisibleCompletions(completions: Completion[]): boolean { | ||
return completions.length > 0 && !!completions.find(c => c.content.trim() !== '') | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New heuristics: Avoid showing completions when we're deleting code
There is only one exception and that is when we have a cached completion at exactly the same prompt. This necessary since you could be inserting spaces and then pressing backspace to get back to were you were before (exactly when you triggered the completion) and we currently show the same completion while you insert more backspace but then would hide it when you go back.
Here's a video of the new behavior:
Screen.Recording.2023-05-24.at.14.12.59.mov
Notice there is a bug right now because we add a cache entry for the completion with a trimmed prefix and that is now also matching in this logic. You can see this at the end of the video where removing all indentation from the line reveals the suggestion again. We need some extra bookkeeping to fix this so I decided it's not worth doing it just yet. WDYT?
Todo for myself: Add a todo about that in code