Skip to content
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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cody completion: Mega bug bash thread #52365

Merged
merged 8 commits into from
May 24, 2023
Merged

Conversation

philipp-spiess
Copy link
Contributor

@philipp-spiess philipp-spiess commented May 24, 2023

This PR includes a bunch of fixes regarding #51344

I've not done a good job at splitting this up into commits and I will also need to add some tests for this (I have some ideas for how to make an effective test setup for this already) but for now, I did some extensive explanation of the changes and why they are added inline 🙂

Test plan

See test plan added to the inline discussions.

@cla-bot cla-bot bot added the cla-signed label May 24, 2023
@github-actions github-actions bot added the team/code-exploration Issues owned by the Code Exploration team label May 24, 2023
@philipp-spiess philipp-spiess self-assigned this May 24, 2023
@philipp-spiess philipp-spiess requested review from beyang, valerybugakov and a team May 24, 2023 13:52
if (error.message === 'aborted') {
return []
}

console.error(error)
debug('CodyCompletionProvider:inline:error', `${error}\n${error.stack()}`)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugfix: Makes sure that inline completion errors are added to the new debug log. This is going to be helpful if we have to debug customer issues.

Screenshot 2023-05-24 at 15 35 09

@@ -28,6 +28,7 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr
private maxSuffixTokens: number
private abortOpenInlineCompletions: () => void = () => {}
private abortOpenMultilineCompletion: () => void = () => {}
private lastContentChanges: Map<string, 'add' | 'del'> = new Map()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: This map can grow with the number of files changes in one session. Do you think we should rather use a LRU cache here instead right away or is it fine if we get back to that later?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since I barely ever restart vscode, it could be that this grows unboundedly 🤔 Maybe if it's not a ton of effort rather do it now vs bad surprises later?

Comment on lines +106 to +119
// 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 []
}
Copy link
Contributor Author

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

Comment on lines -214 to -216
await new Promise<void>(resolve =>
setTimeout(() => resolve(), Math.max(0, waitMs - estimatedLLMResponseLatencyMS))
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug fix: The previous heuristics would use the waitMs and subtract the estimated LLM response. I looked at prod data for dotcom and the estimate was overshooting it but also I noticed that the waitMs was set to 500 before and the latency to 700 so we would not debounce in some situations 😬

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!

Comment on lines +248 to +252
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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

@@ -213,7 +213,7 @@ export class EndOfLineCompletionProvider extends CompletionProvider {
suffix: string,
injectPrefix: string,
defaultN: number = 1,
protected multiline: boolean = false
protected multilineMode: null | 'block' | 'statement' = null
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: While not needed yet, this adds support for a statement delimited multiline request as well. This will use different conditions to truncate the response and will be necessary to allow completions in these scenarios:

const result =

Comment on lines 303 to 313
// If odd indentation is detected (i.e Claude adds a space to every line),
// we fix it for the whole multiline block first.
//
// We can skip the first line as it was already corrected above
if (hasOddIndentation) {
for (let i = 1; i < lines.length; i++) {
if (indentation(lines[i]) >= startIndent) {
lines[i] = lines[i].replace(/^\ /, '')
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New heuristics: We already identified "odd" space insertions before. After more trial and error, I noticed that when this case happens (where Claude inserts a space before the response even though the indentation would match), it will actually do so for every line that is part of this indentation in a multiline request.

This tries to fix it. 🤞

@@ -302,7 +323,7 @@ export class EndOfLineCompletionProvider extends CompletionProvider {
if (indentation(line) < startIndent) {
// When we find the first block below the start indentation, only include it if
// it is an end block
if (line.trim() === '}') {
if (line.trim().startsWith('}')) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: This makes it so that if something closes in e.g. }), it would also be emitted.

Comment on lines 338 to 354
// If a completed line matches the next line of the suffix 1:1, we remove
const lines = completion.split('\n')
const matchedLineIndex = lines.findIndex((line, index) => {
if (index === 0) {
line = currentLinePrefix + line
}
if (line.trim() !== '' && nextLine.trim() !== '') {
// We need a trimRight here because the machine likes to add trailing whitespace.
//
// TODO: Fix this earlier in the post process run but this needs to be careful not
// to alter the meaning
return line.trimRight() === nextLine
}
return false
})
if (matchedLineIndex !== -1) {
completion = lines.slice(0, matchedLineIndex).join('\n')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New heuristics: Single and multi-line completions are now being checked to exactly match the next line. In case of a single line completion, they will be removed. In case of multi-line, we truncate the response. See this comparison:

Before:

Screenshot 2023-05-24 at 15 46 34

After:

Screenshot 2023-05-24 at 15 17 19

(added some logging to the second screenshot to show you this actually worked)

Comment on lines +357 to +363
// Ignore completions that start with a whitespace. These are handled oddly in VS Code
// since you can't accept them via tab.
//
// TODO: Should we trim the response instead?
if (completion.trimStart().length !== completion.length) {
return null
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New heuristics: We have gotten complaints the the suggestions would start with whitespace which will make it impossible to accept them via tab (since that would instead add a tab character if that is the case).

So for now, filter them out.

@philipp-spiess philipp-spiess marked this pull request as ready for review May 24, 2023 13:52
@sourcegraph-bot
Copy link
Contributor

sourcegraph-bot commented May 24, 2023

📖 Storybook live preview

@@ -28,6 +28,7 @@ export class CodyCompletionItemProvider implements vscode.InlineCompletionItemPr
private maxSuffixTokens: number
private abortOpenInlineCompletions: () => void = () => {}
private abortOpenMultilineCompletion: () => void = () => {}
private lastContentChanges: Map<string, 'add' | 'del'> = new Map()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since I barely ever restart vscode, it could be that this grows unboundedly 🤔 Maybe if it's not a ton of effort rather do it now vs bad surprises later?

@philipp-spiess
Copy link
Contributor Author

Shipping behind the existing feature flag.

@philipp-spiess philipp-spiess merged commit cedfa50 into main May 24, 2023
22 of 23 checks passed
@philipp-spiess philipp-spiess deleted the ps/completion-fixes branch May 24, 2023 17:00
ErikaRS pushed a commit that referenced this pull request Jun 22, 2023
This PR includes a bunch of fixes regarding #51344 

I've not done a good job at splitting this up into commits and I will
also need to add some tests for this (I have some ideas for how to make
an effective test setup for this already) but for now, I did some
extensive explanation of the changes and why they are added inline 🙂

## Test plan

See test plan added to the inline discussions.

<!-- All pull requests REQUIRE a test plan:
https://docs.sourcegraph.com/dev/background-information/testing_principles
-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla-signed team/code-exploration Issues owned by the Code Exploration team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants