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

chore(audoedit): decouple codeToReplaceData from getPromptForModelType #6474

Merged
merged 3 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion vscode/src/autoedits/analytics-logger/analytics-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,6 @@ export class AutoeditAnalyticsLogger {

export const autoeditAnalyticsLogger = new AutoeditAnalyticsLogger()

function getTimeNowInMillis(): number {
export function getTimeNowInMillis(): number {
return Math.floor(performance.now())
}
21 changes: 14 additions & 7 deletions vscode/src/autoedits/autoedits-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ import { getCurrentDocContext } from '../completions/get-current-doc-context'

import type { AutoeditsModelAdapter, AutoeditsPrompt } from './adapters/base'
import { createAutoeditsModelAdapter } from './adapters/create-adapter'
import { getTimeNowInMillis } from './analytics-logger'
import { autoeditsProviderConfig } from './autoedits-config'
import { FilterPredictionBasedOnRecentEdits } from './filter-prediction-edits'
import { autoeditsOutputChannelLogger } from './output-channel-logger'
import type { CodeToReplaceData } from './prompt/prompt-utils'
import { type CodeToReplaceData, getCurrentFilePromptComponents } from './prompt/prompt-utils'
import { ShortTermPromptStrategy } from './prompt/short-term-diff-prompt-strategy'
import type { DecorationInfo } from './renderer/decorators/base'
import { DefaultDecorator } from './renderer/decorators/default-decorator'
Expand Down Expand Up @@ -139,7 +140,7 @@ export class AutoeditsProvider implements vscode.InlineCompletionItemProvider, v
inlineCompletionContext: vscode.InlineCompletionContext,
token?: vscode.CancellationToken
): Promise<vscode.InlineCompletionItem[] | vscode.InlineCompletionList | null> {
const start = Date.now()
const start = getTimeNowInMillis()
const controller = new AbortController()
const abortSignal = controller.signal
token?.onCancellationRequested(() => controller.abort())
Expand All @@ -164,6 +165,13 @@ export class AutoeditsProvider implements vscode.InlineCompletionItemProvider, v
maxSuffixLength: tokensToChars(autoeditsProviderConfig.tokenLimit.suffixTokens),
})

const { fileWithMarkerPrompt, areaPrompt, codeToReplaceData } = getCurrentFilePromptComponents({
Copy link
Contributor

@hitesh-1997 hitesh-1997 Dec 27, 2024

Choose a reason for hiding this comment

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

Why do we need this decoupling, do autoedits-provider need any additional fields from the codeToReplaceData?

I think fileWithMarkerPrompt and areaPrompt are the prompt components which are used to construct the final prompt. Earlier structure abstracts the individual prompt components from the autoedits-provider so that, autoedits-provider only has to know about the final prompt and not the individual components, and final prompt-strateges (eg: default-prompt-strategy.ts or short-term-diff-prompt-strategy.ts) can call and use these prompt component fileWithMarkerPrompt and integrate/manipulate however it sees fit.

  1. autoedit-provider first receiving the these prompt components, then it sending to the prompt constructors (like default-prompt-strategy) imposes some risk, for example what if the prompt gets modified in the autoedits-provider itself.
  2. Also, this introduces another layer in the final prompt construction, where I need to aware of how the prompt flows in the autoedits-provider as well, where earlier, I could just read prompt-constructor for the final prompt creation and had to not at all look at the autoedits-provider.

I think if we need some additional fields from the prompt constructor we could send them in the codeToReplaceData itself, but can keep the responsibility of creating final prompt with individual prompt constructors.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I agree. I started moving fileWithMarkerPrompt, areaPrompt out of getCurrentFilePromptComponents so that we can get only codeToReplaceData and other parts would be calculated inside of the getPromptForModelType method but it involved too many additional changes.

For now, my goal is to extract the codeToReplaceData calculation because it's helpful to have it early. I can hide fileWithMarkerPrompt, areaPrompt from the autoedits provider in a follow-up PR. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah totally, we can do that in a follow up PR.
Approving to unblock !!

Copy link
Member Author

Choose a reason for hiding this comment

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

docContext,
document,
position,
tokenBudget: autoeditsProviderConfig.tokenLimit,
})

autoeditsOutputChannelLogger.logDebug(
'provideInlineCompletionItems',
'Calculating context from contextMixer...'
Expand All @@ -186,10 +194,9 @@ export class AutoeditsProvider implements vscode.InlineCompletionItemProvider, v
'provideInlineCompletionItems',
'Calculating prompt from promptStrategy...'
)
const { codeToReplaceData, prompt } = this.promptStrategy.getPromptForModelType({
document,
position,
docContext,
const prompt = this.promptStrategy.getPromptForModelType({
fileWithMarkerPrompt,
areaPrompt,
context,
tokenBudget: autoeditsProviderConfig.tokenLimit,
isChatModel: autoeditsProviderConfig.isChatModel,
Expand All @@ -216,7 +223,7 @@ export class AutoeditsProvider implements vscode.InlineCompletionItemProvider, v
autoeditsOutputChannelLogger.logDebug(
'provideInlineCompletionItems',
`========================== Response:\n${initialPrediction}\n` +
`========================== Time Taken: ${Date.now() - start}ms`
`========================== Time Taken: ${getTimeNowInMillis() - start}ms`
)

const prediction = shrinkPredictionUntilSuffix({
Expand Down
32 changes: 7 additions & 25 deletions vscode/src/autoedits/prompt/base.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,41 @@
import type * as vscode from 'vscode'

import type {
AutoEditsTokenLimit,
AutocompleteContextSnippet,
DocumentContext,
PromptString,
} from '@sourcegraph/cody-shared'

import type { AutoeditsPrompt } from '../adapters/base'

import { SYSTEM_PROMPT } from './constants'
import { type CodeToReplaceData, getCompletionsPromptWithSystemPrompt } from './prompt-utils'
import { type CurrentFilePromptComponents, getCompletionsPromptWithSystemPrompt } from './prompt-utils'

export interface UserPromptArgs {
docContext: DocumentContext
document: vscode.TextDocument
position: vscode.Position
export interface UserPromptArgs
extends Pick<CurrentFilePromptComponents, 'areaPrompt' | 'fileWithMarkerPrompt'> {
context: AutocompleteContextSnippet[]
tokenBudget: AutoEditsTokenLimit
}

export interface UserPromptResponse {
codeToReplaceData: CodeToReplaceData
prompt: PromptString
}

export interface UserPromptForModelArgs extends UserPromptArgs {
isChatModel: boolean
}

export interface UserPromptForModelResponse {
codeToReplaceData: CodeToReplaceData
prompt: AutoeditsPrompt
}

/**
* Class for generating user prompts in auto-edits functionality.
* The major difference between different strategy is the prompt rendering.
*/
export abstract class AutoeditsUserPromptStrategy {
protected abstract getUserPrompt(args: UserPromptArgs): UserPromptResponse
protected abstract getUserPrompt(args: UserPromptArgs): PromptString

public getPromptForModelType({
isChatModel,
...userPromptArgs
}: UserPromptForModelArgs): UserPromptForModelResponse {
const { codeToReplaceData, prompt } = this.getUserPrompt(userPromptArgs)
}: UserPromptForModelArgs): AutoeditsPrompt {
const prompt = this.getUserPrompt(userPromptArgs)

const adjustedPrompt: AutoeditsPrompt = isChatModel
? { systemMessage: SYSTEM_PROMPT, userMessage: prompt }
: { userMessage: getCompletionsPromptWithSystemPrompt(SYSTEM_PROMPT, prompt) }

return {
codeToReplaceData,
prompt: adjustedPrompt,
}
return adjustedPrompt
}
}
19 changes: 13 additions & 6 deletions vscode/src/autoedits/prompt/default-prompt-strategy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getCurrentDocContext } from '../../completions/get-current-doc-context'
import { documentAndPosition } from '../../completions/test-helpers'
import type { UserPromptArgs } from './base'
import { DefaultUserPromptStrategy } from './default-prompt-strategy'
import { getCurrentFilePromptComponents } from './prompt-utils'

describe('DefaultUserPromptStrategy', () => {
const promptProvider = new DefaultUserPromptStrategy()
Expand Down Expand Up @@ -42,7 +43,6 @@ describe('DefaultUserPromptStrategy', () => {
maxPrefixLength: 100,
maxSuffixLength: 100,
})

const tokenBudget: AutoEditsTokenLimit = {
prefixTokens: 10,
suffixTokens: 10,
Expand All @@ -58,6 +58,14 @@ describe('DefaultUserPromptStrategy', () => {
[RetrieverIdentifier.DiagnosticsRetriever]: 100,
},
}

const { fileWithMarkerPrompt, areaPrompt } = getCurrentFilePromptComponents({
docContext,
document,
position,
tokenBudget,
})

const context: AutocompleteContextSnippet[] = shouldIncludeContext
? [
getContextItem(
Expand Down Expand Up @@ -164,17 +172,16 @@ describe('DefaultUserPromptStrategy', () => {
: []

return {
docContext,
document,
position,
context,
fileWithMarkerPrompt,
areaPrompt,
tokenBudget,
}
}

it('creates prompt with the context source with context', () => {
const userPromptData = getUserPromptData({ shouldIncludeContext: true })
const { prompt } = promptProvider.getUserPrompt(userPromptData)
const prompt = promptProvider.getUserPrompt(userPromptData)

expect(prompt.toString()).toEqual(dedent`
Help me finish a coding change. In particular, you will see a series of snippets from current open files in my editor, files I have recently viewed, the file I am editing, then a history of my recent codebase changes, then current compiler and linter errors, content I copied from my codebase. You will then rewrite the <code_to_rewrite>, to match what you think I would do next in the codebase. Note: I might have stopped in the middle of typing.
Expand Down Expand Up @@ -304,7 +311,7 @@ describe('DefaultUserPromptStrategy', () => {

it('creates a prompt in the correct format', () => {
const userPromptData = getUserPromptData({ shouldIncludeContext: false })
const { prompt } = promptProvider.getUserPrompt(userPromptData)
const prompt = promptProvider.getUserPrompt(userPromptData)

const expectedPrompt = dedent`
Help me finish a coding change. In particular, you will see a series of snippets from current open files in my editor, files I have recently viewed, the file I am editing, then a history of my recent codebase changes, then current compiler and linter errors, content I copied from my codebase. You will then rewrite the <code_to_rewrite>, to match what you think I would do next in the codebase. Note: I might have stopped in the middle of typing.
Expand Down
30 changes: 8 additions & 22 deletions vscode/src/autoedits/prompt/default-prompt-strategy.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,33 @@
import { ps } from '@sourcegraph/cody-shared'
import { type PromptString, ps } from '@sourcegraph/cody-shared'

import { RetrieverIdentifier } from '../../completions/context/utils'
import { autoeditsOutputChannelLogger } from '../output-channel-logger'

import { AutoeditsUserPromptStrategy, type UserPromptArgs, type UserPromptResponse } from './base'
import { AutoeditsUserPromptStrategy, type UserPromptArgs } from './base'
import * as constants from './constants'
import {
getContextItemMappingWithTokenLimit,
getCurrentFilePromptComponents,
getJaccardSimilarityPrompt,
getLintErrorsPrompt,
getPromptForTheContextSource,
getPromptWithNewline,
getRecentCopyPrompt,
getRecentEditsPrompt,
getRecentlyViewedSnippetsPrompt,
joinPromptsWithNewlineSeperator,
joinPromptsWithNewlineSeparator,
} from './prompt-utils'

export class DefaultUserPromptStrategy extends AutoeditsUserPromptStrategy {
getUserPrompt({
docContext,
document,
position,
context,
tokenBudget,
}: UserPromptArgs): UserPromptResponse {
fileWithMarkerPrompt,
areaPrompt,
}: UserPromptArgs): PromptString {
const contextItemMapping = getContextItemMappingWithTokenLimit(
context,
tokenBudget.contextSpecificTokenLimit
)
const { fileWithMarkerPrompt, areaPrompt, codeToReplaceData } = getCurrentFilePromptComponents({
docContext,
document,
position,
maxPrefixLinesInArea: tokenBudget.maxPrefixLinesInArea,
maxSuffixLinesInArea: tokenBudget.maxSuffixLinesInArea,
codeToRewritePrefixLines: tokenBudget.codeToRewritePrefixLines,
codeToRewriteSuffixLines: tokenBudget.codeToRewriteSuffixLines,
})
const recentViewsPrompt = getPromptForTheContextSource(
contextItemMapping.get(RetrieverIdentifier.RecentViewPortRetriever) || [],
constants.LONG_TERM_SNIPPET_VIEWS_INSTRUCTION,
Expand Down Expand Up @@ -71,7 +60,7 @@ export class DefaultUserPromptStrategy extends AutoeditsUserPromptStrategy {

const currentFilePrompt = ps`${constants.CURRENT_FILE_INSTRUCTION}${fileWithMarkerPrompt}`

const finalPrompt = joinPromptsWithNewlineSeperator(
const finalPrompt = joinPromptsWithNewlineSeparator(
getPromptWithNewline(constants.BASE_USER_PROMPT),
getPromptWithNewline(jaccardSimilarityPrompt),
getPromptWithNewline(recentViewsPrompt),
Expand All @@ -84,9 +73,6 @@ export class DefaultUserPromptStrategy extends AutoeditsUserPromptStrategy {
)

autoeditsOutputChannelLogger.logDebug('getUserPrompt', 'Prompt\n', finalPrompt)
return {
codeToReplaceData,
prompt: finalPrompt,
}
return finalPrompt
}
}
Loading
Loading