From fce2b561d5cc341df5185d23a0cd958c16275059 Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Fri, 31 Jan 2025 16:33:51 +0100 Subject: [PATCH 01/11] update autocomplete templates and snippets handling --- .../templating/AutocompleteTemplate.ts | 53 +++++++++---------- core/autocomplete/templating/filtering.ts | 3 +- .../RecentlyVisitedRangesService.ts | 11 ++-- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index 8f7bbd66d4..92dbf76fa8 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -21,16 +21,16 @@ export interface AutocompleteTemplate { workspaceUris: string[], ) => [string, string]; template: - | string - | (( - prefix: string, - suffix: string, - filepath: string, - reponame: string, - language: string, - snippets: AutocompleteSnippet[], - workspaceUris: string[], - ) => string); + | string + | (( + prefix: string, + suffix: string, + filepath: string, + reponame: string, + language: string, + snippets: AutocompleteSnippet[], + workspaceUris: string[], + ) => string); completionOptions?: Partial; } @@ -87,14 +87,16 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { ): [string, string] => { if (snippets.length === 0) { if (suffix.trim().length === 0 && prefix.trim().length === 0) { - return [ - `+++++ ${getLastNUriRelativePathParts(workspaceUris, filepath, 2)}\n${prefix}`, - suffix, - ]; + return [`${getLastNUriRelativePathParts(workspaceUris, filepath, 2)}\n\n[PREFIX]\n${prefix}`, suffix]; } return [prefix, suffix]; } + //snippets = snippets.filter((snippet) => "filepath" in snippet); + + // reverse the snippets so that the most recent snippet is last + snippets = [...snippets].reverse(); + const relativePaths = getShortestUniqueRelativeUriPaths( [ ...snippets.map((snippet) => @@ -111,19 +113,17 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { return snippet.content; } - return `+++++ ${relativePaths[i].uri} \n${snippet.content}`; + return `// ${relativePaths[i].uri} \n${snippet.content}`; }) .join("\n\n"); return [ - `${otherFiles}\n\n+++++ ${ - relativePaths[relativePaths.length - 1].uri - }\n${prefix}`, - suffix, + `${otherFiles}[PREFIX]${prefix}`, + `${suffix}`, ]; }, template: (prefix: string, suffix: string): string => { - return `[SUFFIX]${suffix}[PREFIX]${prefix}`; + return "NOT USED!"; //`[SUFFIX]${suffix}[PREFIX]${prefix}`; }, completionOptions: { stop: ["[PREFIX]", "[SUFFIX]"], @@ -160,10 +160,10 @@ const starcoder2FimTemplate: AutocompleteTemplate = { snippets.length === 0 ? "" : `${snippets - .map((snippet) => { - return snippet.content; - }) - .join("")}`; + .map((snippet) => { + return snippet.content; + }) + .join("")}`; const prompt = `${otherFiles}${prefix}${suffix}`; return prompt; @@ -218,9 +218,8 @@ const codegeexFimTemplate: AutocompleteTemplate = { [...snippets.map((snippet) => snippet.filepath), filepath], workspaceUris, ); - const baseTemplate = `###PATH:${ - relativePaths[relativePaths.length - 1] - }\n###LANGUAGE:${language}\n###MODE:BLOCK\n<|code_suffix|>${suffix}<|code_prefix|>${prefix}<|code_middle|>`; + const baseTemplate = `###PATH:${relativePaths[relativePaths.length - 1] + }\n###LANGUAGE:${language}\n###MODE:BLOCK\n<|code_suffix|>${suffix}<|code_prefix|>${prefix}<|code_middle|>`; if (snippets.length === 0) { return `<|user|>\n${baseTemplate}<|assistant|>\n`; } diff --git a/core/autocomplete/templating/filtering.ts b/core/autocomplete/templating/filtering.ts index c741dedc8b..7dc16c9d09 100644 --- a/core/autocomplete/templating/filtering.ts +++ b/core/autocomplete/templating/filtering.ts @@ -43,9 +43,10 @@ export const getSnippets = ( payload: SnippetPayload, ): AutocompleteSnippet[] => { const snippets = [ - ...payload.diffSnippets, ...payload.clipboardSnippets, ...payload.recentlyVisitedRangesSnippets, + ...payload.recentlyEditedRangeSnippets, + //...payload.diffSnippets, ...shuffleArray( filterSnippetsAlreadyInCaretWindow( [...payload.rootPathSnippets, ...payload.importDefinitionSnippets], diff --git a/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts b/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts index 2dba327ef7..6f850d0ce1 100644 --- a/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts +++ b/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts @@ -16,10 +16,10 @@ export class RecentlyVisitedRangesService { Array >; // Default value, we override in initWithPostHog - private numSurroundingLines = 5; + private numSurroundingLines = 20; private maxRecentFiles = 3; private maxSnippetsPerFile = 3; - private isEnabled = false; + private isEnabled = true; constructor(private readonly ide: IDE) { this.cache = new LRUCache< @@ -38,12 +38,11 @@ export class RecentlyVisitedRangesService { PosthogFeatureFlag.RecentlyVisitedRangesNumSurroundingLines, ); - if (!recentlyVisitedRangesNumSurroundingLines) { - return; + if (recentlyVisitedRangesNumSurroundingLines) { + this.isEnabled = true; + this.numSurroundingLines = recentlyVisitedRangesNumSurroundingLines; } - this.isEnabled = true; - this.numSurroundingLines = recentlyVisitedRangesNumSurroundingLines; vscode.window.onDidChangeTextEditorSelection( this.cacheCurrentSelectionContext, ); From d92a7e08b924c63b876904194a8702c30c7dbee0 Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Sat, 1 Feb 2025 12:19:42 +0100 Subject: [PATCH 02/11] Snippet handling with experimental hidden autocomplete options, Qwen coder template with ollama --- .../templating/AutocompleteTemplate.ts | 56 ++++++++-- core/autocomplete/templating/filtering.ts | 100 +++++++++++++++--- core/index.d.ts | 49 +++++---- core/llm/llms/Ollama.ts | 10 +- core/util/parameters.ts | 4 + 5 files changed, 174 insertions(+), 45 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index 92dbf76fa8..afb2c61d12 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -52,8 +52,47 @@ const stableCodeFimTemplate: AutocompleteTemplate = { // https://github.com/QwenLM/Qwen2.5-Coder?tab=readme-ov-file#3-file-level-code-completion-fill-in-the-middle const qwenCoderFimTemplate: AutocompleteTemplate = { - template: - "<|fim_prefix|>{{{prefix}}}<|fim_suffix|>{{{suffix}}}<|fim_middle|>", + compilePrefixSuffix: ( + prefix: string, + suffix: string, + filepath: string, + reponame: string, + snippets: AutocompleteSnippet[], + workspaceUris: string[] + ): [string, string] => { + // Helper function to get file name from snippet + function getFileName(snippet: { uri: string; uniquePath: string }) { + return snippet.uri.startsWith("file://") ? snippet.uniquePath : snippet.uri; + } + + // Start building the prompt with repo name + let prompt = `<|repo_name|>${reponame}`; + + const relativePaths = getShortestUniqueRelativeUriPaths( + [ + ...snippets.map((snippet) => + "filepath" in snippet ? snippet.filepath : "file:///Untitled.txt" + ), + filepath, + ], + workspaceUris + ); + + // Add each snippet with its file path + snippets.forEach((snippet, i) => { + const content = snippet.type === AutocompleteSnippetType.Diff + ? snippet.content + : snippet.content; + prompt += `\n<|file_sep|>${getFileName(relativePaths[i])}\n${content}`; + }); + + // Add the current file's prefix and suffix + prompt += `<|fim_prefix|>${prefix}<|fim_suffix|>${suffix}<|fim_middle|>`; + + // Empty suffix will make the prefix be used as a single prompt + return [prompt, ""]; + }, + template: "<|fim_prefix|>{{{prefix}}}<|fim_suffix|>{{{suffix}}}<|fim_middle|>", completionOptions: { stop: [ "<|endoftext|>", @@ -85,9 +124,14 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { snippets, workspaceUris, ): [string, string] => { + + function getFileName(snippet: { uri: string, uniquePath: string }) { + return snippet.uri.startsWith("file://") ? snippet.uniquePath : snippet.uri + } + if (snippets.length === 0) { if (suffix.trim().length === 0 && prefix.trim().length === 0) { - return [`${getLastNUriRelativePathParts(workspaceUris, filepath, 2)}\n\n[PREFIX]\n${prefix}`, suffix]; + return [`+++++ ${getLastNUriRelativePathParts(workspaceUris, filepath, 2)}\n\n[PREFIX]\n${prefix}`, suffix]; } return [prefix, suffix]; } @@ -113,12 +157,12 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { return snippet.content; } - return `// ${relativePaths[i].uri} \n${snippet.content}`; + return `+++++ ${getFileName(relativePaths[i])} \n${snippet.content}`; }) .join("\n\n"); return [ - `${otherFiles}[PREFIX]${prefix}`, + `${otherFiles}\n\n+++++ ${getFileName(relativePaths[relativePaths.length - 1])}\n[PREFIX]${prefix}`, `${suffix}`, ]; }, @@ -126,7 +170,7 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { return "NOT USED!"; //`[SUFFIX]${suffix}[PREFIX]${prefix}`; }, completionOptions: { - stop: ["[PREFIX]", "[SUFFIX]"], + stop: ["[PREFIX]", "[SUFFIX]", "\n+++++ "], }, }; diff --git a/core/autocomplete/templating/filtering.ts b/core/autocomplete/templating/filtering.ts index 7dc16c9d09..12c3ac6ce3 100644 --- a/core/autocomplete/templating/filtering.ts +++ b/core/autocomplete/templating/filtering.ts @@ -42,25 +42,95 @@ export const getSnippets = ( helper: HelperVars, payload: SnippetPayload, ): AutocompleteSnippet[] => { - const snippets = [ - ...payload.clipboardSnippets, - ...payload.recentlyVisitedRangesSnippets, - ...payload.recentlyEditedRangeSnippets, - //...payload.diffSnippets, - ...shuffleArray( - filterSnippetsAlreadyInCaretWindow( - [...payload.rootPathSnippets, ...payload.importDefinitionSnippets], - helper.prunedCaretWindow, - ), - ), - ]; - const finalSnippets = []; + const snippets = { + "clipboard": payload.clipboardSnippets, + "recentlyVisitedRanges": payload.recentlyVisitedRangesSnippets, + "recentlyEditedRanges": payload.recentlyEditedRangeSnippets, + "diff": payload.diffSnippets, + "base": shuffleArray(filterSnippetsAlreadyInCaretWindow( + [...payload.rootPathSnippets, ...payload.importDefinitionSnippets], + helper.prunedCaretWindow, + )), + } + + // Define snippets with their priorities + const snippetConfigs: { + key: keyof typeof snippets; + enabledOrPriority: boolean | number; + defaultPriority: number; + snippets: AutocompleteSnippet[]; + }[] = [ + { + key: "clipboard", + enabledOrPriority: helper.options.experimental_includeClipboard, + defaultPriority: 1, + snippets: payload.clipboardSnippets, + }, + { + key: "recentlyVisitedRanges", + enabledOrPriority: helper.options.experimental_includeRecentlyVisitedRanges, + defaultPriority: 2, + snippets: payload.recentlyVisitedRangesSnippets, + /* TODO: recentlyVisitedRanges also contain contents from other windows like terminal or output + if they are visible. We should handle them separately so that we can control their priority + and whether they should be included or not. */ + }, + { + key: "recentlyEditedRanges", + enabledOrPriority: helper.options.experimental_includeRecentlyEditedRanges, + defaultPriority: 3, + snippets: payload.recentlyEditedRangeSnippets, + }, + { + key: "diff", + enabledOrPriority: helper.options.experimental_includeDiff, + defaultPriority: 4, + snippets: payload.diffSnippets, + // TODO: diff is commonly too large, thus anything lower in priority is not included. + }, + { + key: "base", + enabledOrPriority: true, + defaultPriority: 99, // make sure it's the last one to be processed, but still possible to override + snippets: shuffleArray(filterSnippetsAlreadyInCaretWindow( + [...payload.rootPathSnippets, ...payload.importDefinitionSnippets], + helper.prunedCaretWindow, + )), + // TODO: Add this too to experimental config, maybe move upper in the order, since it's almost + // always not inlucded due to diff being commonly large + }, + ]; + + // Create a readable order of enabled snippets + const snippetOrder = snippetConfigs + .filter(({ enabledOrPriority }) => enabledOrPriority) + .map(({ key, enabledOrPriority, defaultPriority }) => ({ + key, + priority: typeof enabledOrPriority === 'number' ? enabledOrPriority : defaultPriority, + })) + .sort((a, b) => a.priority - b.priority); + // Log the snippet order for debugging - uncomment if needed + /* console.log( + 'Snippet processing order:', + snippetOrder + .map(({ key, priority }) => `${key} (priority: ${priority})`).join("\n") + ); */ + + // Convert configs to prioritized snippets + const prioritizedSnippets = snippetOrder + .flatMap(({ key, priority }) => + snippets[key].map(snippet => ({ snippet, priority })) + ) + .sort((a, b) => a.priority - b.priority) + .map(({ snippet }) => snippet); + + const finalSnippets = []; let remainingTokenCount = getRemainingTokenCount(helper); - while (remainingTokenCount > 0 && snippets.length > 0) { - const snippet = snippets.shift(); + while (remainingTokenCount > 0 && prioritizedSnippets.length > 0) { + const snippet = prioritizedSnippets.shift(); if (!snippet || !isValidSnippet(snippet)) { continue; } diff --git a/core/index.d.ts b/core/index.d.ts index f65c106227..040233c62a 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -43,13 +43,13 @@ export interface IndexingProgressUpdate { desc: string; shouldClearIndexes?: boolean; status: - | "loading" - | "indexing" - | "done" - | "failed" - | "paused" - | "disabled" - | "cancelled"; + | "loading" + | "indexing" + | "done" + | "failed" + | "paused" + | "disabled" + | "cancelled"; debugInfo?: string; } @@ -666,10 +666,10 @@ export interface IDE { getCurrentFile(): Promise< | undefined | { - isUntitled: boolean; - path: string; - contents: string; - } + isUntitled: boolean; + path: string; + contents: string; + } >; getPinnedFiles(): Promise; @@ -848,11 +848,11 @@ export interface CustomCommand { export interface Prediction { type: "content"; content: - | string - | { - type: "text"; - text: string; - }[]; + | string + | { + type: "text"; + text: string; + }[]; } export interface ToolExtras { @@ -971,6 +971,11 @@ export interface TabAutocompleteOptions { disableInFiles?: string[]; useImports?: boolean; showWhateverWeHaveAtXMs?: number; + // true = enabled, false = disabled, number = enabled with priority + experimental_includeClipboard: boolean | number; + experimental_includeRecentlyVisitedRanges: boolean | number; + experimental_includeRecentlyEditedRanges: boolean | number; + experimental_includeDiff: boolean | number; } export interface StdioOptions { @@ -1171,9 +1176,9 @@ export interface Config { embeddingsProvider?: EmbeddingsProviderDescription | ILLM; /** The model that Continue will use for tab autocompletions. */ tabAutocompleteModel?: - | CustomLLM - | ModelDescription - | (CustomLLM | ModelDescription)[]; + | CustomLLM + | ModelDescription + | (CustomLLM | ModelDescription)[]; /** Options for tab autocomplete */ tabAutocompleteOptions?: Partial; /** UI styles customization */ @@ -1264,9 +1269,9 @@ export type PackageDetailsSuccess = PackageDetails & { export type PackageDocsResult = { packageInfo: ParsedPackageInfo; } & ( - | { error: string; details?: never } - | { details: PackageDetailsSuccess; error?: never } -); + | { error: string; details?: never } + | { details: PackageDetailsSuccess; error?: never } + ); export interface TerminalOptions { reuseTerminal?: boolean; diff --git a/core/llm/llms/Ollama.ts b/core/llm/llms/Ollama.ts index b9734acb92..c770dbfe57 100644 --- a/core/llm/llms/Ollama.ts +++ b/core/llm/llms/Ollama.ts @@ -297,16 +297,22 @@ class Ollama extends BaseLLM { prompt: string, suffix?: string, ): OllamaRawOptions { - return { + const generateOptions: OllamaRawOptions = { model: this._getModel(), prompt, - suffix, raw: options.raw, options: this._getModelFileParams(options), keep_alive: options.keepAlive ?? 60 * 30, // 30 minutes stream: options.stream, // Not supported yet: context, images, system, template, format }; + + // Only add suffix if it's not empty. This allows composing the fim template in a way that uses prompt only + if (suffix && suffix !== "") { + generateOptions.suffix = suffix; + } + + return generateOptions; } private getEndpoint(endpoint: string): URL { diff --git a/core/util/parameters.ts b/core/util/parameters.ts index 648253712b..05ab61e06b 100644 --- a/core/util/parameters.ts +++ b/core/util/parameters.ts @@ -18,6 +18,10 @@ export const DEFAULT_AUTOCOMPLETE_OPTS: TabAutocompleteOptions = { useImports: true, transform: true, showWhateverWeHaveAtXMs: 300, + experimental_includeClipboard: true, + experimental_includeRecentlyVisitedRanges: true, + experimental_includeRecentlyEditedRanges: true, + experimental_includeDiff: true, }; export const COUNT_COMPLETION_REJECTED_AFTER = 10_000; From 3d08fc96bfd93ca315fde2c06d7ee1aa0e4a99f0 Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Sat, 1 Feb 2025 19:46:27 +0100 Subject: [PATCH 03/11] add promptOnly option to handle single prompt with suffix for Ollama provider --- core/autocomplete/templating/AutocompleteTemplate.ts | 7 ++++++- core/llm/llms/Ollama.ts | 11 +++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index afb2c61d12..bf24627c41 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -11,6 +11,10 @@ import { AutocompleteSnippetType, } from "../snippets/types.js"; +export interface AutocompleteCompletionOptions { + promptOnly?: boolean; +} + export interface AutocompleteTemplate { compilePrefixSuffix?: ( prefix: string, @@ -31,7 +35,7 @@ export interface AutocompleteTemplate { snippets: AutocompleteSnippet[], workspaceUris: string[], ) => string); - completionOptions?: Partial; + completionOptions?: Partial & Partial; } // https://huggingface.co/stabilityai/stable-code-3b @@ -105,6 +109,7 @@ const qwenCoderFimTemplate: AutocompleteTemplate = { "<|im_start|>", "<|im_end|>", ], + promptOnly: true // with ollama provider this makes sure a single prompt is sent (with suffix as part of the prompt, not as a separate parameter) }, }; diff --git a/core/llm/llms/Ollama.ts b/core/llm/llms/Ollama.ts index c770dbfe57..5c5df14885 100644 --- a/core/llm/llms/Ollama.ts +++ b/core/llm/llms/Ollama.ts @@ -4,6 +4,7 @@ import { ChatMessage, CompletionOptions, LLMOptions } from "../../index.js"; import { renderChatMessage } from "../../util/messageContent.js"; import { BaseLLM } from "../index.js"; import { streamResponse } from "../stream.js"; +import { AutocompleteCompletionOptions } from "../../autocomplete/templating/AutocompleteTemplate.js"; type OllamaChatMessage = { role: "tool" | "user" | "assistant" | "system"; @@ -293,7 +294,7 @@ class Ollama extends BaseLLM { } private _getGenerateOptions( - options: CompletionOptions, + options: CompletionOptions & Partial, prompt: string, suffix?: string, ): OllamaRawOptions { @@ -303,13 +304,15 @@ class Ollama extends BaseLLM { raw: options.raw, options: this._getModelFileParams(options), keep_alive: options.keepAlive ?? 60 * 30, // 30 minutes + suffix, stream: options.stream, // Not supported yet: context, images, system, template, format }; - // Only add suffix if it's not empty. This allows composing the fim template in a way that uses prompt only - if (suffix && suffix !== "") { - generateOptions.suffix = suffix; + if (options.promptOnly) { + console.log("fim: removing suffix"); + + delete generateOptions.suffix; } return generateOptions; From 5fb848d1631338368765b054f5e06d6f5d2d4549 Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Sun, 2 Feb 2025 06:17:50 +0100 Subject: [PATCH 04/11] Exclude Continue's own output from autocomplete snippets --- core/autocomplete/templating/filtering.ts | 7 ++++++- .../src/autocomplete/RecentlyVisitedRangesService.ts | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/autocomplete/templating/filtering.ts b/core/autocomplete/templating/filtering.ts index 12c3ac6ce3..02dbade362 100644 --- a/core/autocomplete/templating/filtering.ts +++ b/core/autocomplete/templating/filtering.ts @@ -119,13 +119,18 @@ export const getSnippets = ( ); */ // Convert configs to prioritized snippets - const prioritizedSnippets = snippetOrder + let prioritizedSnippets = snippetOrder .flatMap(({ key, priority }) => snippets[key].map(snippet => ({ snippet, priority })) ) .sort((a, b) => a.priority - b.priority) .map(({ snippet }) => snippet); + // Exclude Continue's own output as it makes it super-hard for users to test the autocomplete feature + // while looking at the prompts in the Continue's output + prioritizedSnippets = prioritizedSnippets.filter((snippet) => + !(snippet as AutocompleteCodeSnippet).filepath?.startsWith("output:extension-output-Continue.continue")); + const finalSnippets = []; let remainingTokenCount = getRemainingTokenCount(helper); diff --git a/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts b/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts index 6f850d0ce1..a20e88ac3d 100644 --- a/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts +++ b/extensions/vscode/src/autocomplete/RecentlyVisitedRangesService.ts @@ -113,7 +113,10 @@ export class RecentlyVisitedRangesService { } return allSnippets - .filter((s) => !currentFilepath || s.filepath !== currentFilepath) + .filter((s) => !currentFilepath || (s.filepath !== currentFilepath && + // Exclude Continue's own output as it makes it super-hard for users to test the autocomplete feature + // while looking at the prompts in the Continue's output + !s.filepath.startsWith("output:extension-output-Continue.continue"))) .sort((a, b) => b.timestamp - a.timestamp) .map(({ timestamp, ...snippet }) => snippet); } From 5aaab9c91e0377a0ba5e9111da380369be45682c Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Sun, 2 Feb 2025 07:29:35 +0100 Subject: [PATCH 05/11] update template logic codestral and qwen --- .../templating/AutocompleteTemplate.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index bf24627c41..ab336b89f3 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -96,7 +96,7 @@ const qwenCoderFimTemplate: AutocompleteTemplate = { // Empty suffix will make the prefix be used as a single prompt return [prompt, ""]; }, - template: "<|fim_prefix|>{{{prefix}}}<|fim_suffix|>{{{suffix}}}<|fim_middle|>", + template: "{{{prefix}}}", // output of compilePrefixSuffix already compiles everything into a single prompt completionOptions: { stop: [ "<|endoftext|>", @@ -172,7 +172,29 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { ]; }, template: (prefix: string, suffix: string): string => { - return "NOT USED!"; //`[SUFFIX]${suffix}[PREFIX]${prefix}`; + /* + This template is ignored with codestral provider, however theoretically it's possible that a provider + not supporting fim endpoint would have a model name matched with this template, + or the codestral implementation will be changed, so we provide a usable implementation. + */ + + const prefixMarkerIndex = prefix.lastIndexOf('[PREFIX]'); + + if (prefixMarkerIndex === -1) { + return suffix ? `[SUFFIX]${suffix}[PREFIX]${prefix}` : `[PREFIX]${prefix}`; + } + + if (!suffix) { + // [PREFIX] already in the prompt, but suffix is an empty string + return prefix; + } + + // Insert [SUFFIX]${suffix} just before [PREFIX] + return ( + prefix.substring(0, prefixMarkerIndex) + + '[SUFFIX]' + suffix + + prefix.substring(prefixMarkerIndex) + ); }, completionOptions: { stop: ["[PREFIX]", "[SUFFIX]", "\n+++++ "], From ddd0be1e6175d127cf5b45427a30d2087724606f Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Mon, 3 Feb 2025 09:37:28 +0100 Subject: [PATCH 06/11] Change mistral template to produce a single prompt --- .../templating/AutocompleteTemplate.ts | 29 +++---------------- core/llm/openaiTypeConverters.ts | 5 ++-- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index ab336b89f3..3480a55c93 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -167,37 +167,16 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { .join("\n\n"); return [ - `${otherFiles}\n\n+++++ ${getFileName(relativePaths[relativePaths.length - 1])}\n[PREFIX]${prefix}`, - `${suffix}`, + `${otherFiles}\n\n+++++ ${getFileName(relativePaths[relativePaths.length - 1])}\n[SUFFIX]${suffix}\n[PREFIX]${prefix}`, + "", ]; }, template: (prefix: string, suffix: string): string => { - /* - This template is ignored with codestral provider, however theoretically it's possible that a provider - not supporting fim endpoint would have a model name matched with this template, - or the codestral implementation will be changed, so we provide a usable implementation. - */ - - const prefixMarkerIndex = prefix.lastIndexOf('[PREFIX]'); - - if (prefixMarkerIndex === -1) { - return suffix ? `[SUFFIX]${suffix}[PREFIX]${prefix}` : `[PREFIX]${prefix}`; - } - - if (!suffix) { - // [PREFIX] already in the prompt, but suffix is an empty string - return prefix; - } - - // Insert [SUFFIX]${suffix} just before [PREFIX] - return ( - prefix.substring(0, prefixMarkerIndex) + - '[SUFFIX]' + suffix + - prefix.substring(prefixMarkerIndex) - ); + return prefix }, completionOptions: { stop: ["[PREFIX]", "[SUFFIX]", "\n+++++ "], + promptOnly: true }, }; diff --git a/core/llm/openaiTypeConverters.ts b/core/llm/openaiTypeConverters.ts index 4ed8be1c48..af21bf4708 100644 --- a/core/llm/openaiTypeConverters.ts +++ b/core/llm/openaiTypeConverters.ts @@ -16,6 +16,7 @@ import { MessageContent, TextMessagePart, } from ".."; +import { AutocompleteCompletionOptions } from "../autocomplete/templating/AutocompleteTemplate"; export function toChatMessage( message: ChatMessage, @@ -138,12 +139,12 @@ export function toCompleteBody( export function toFimBody( prefix: string, suffix: string, - options: CompletionOptions, + options: CompletionOptions & AutocompleteCompletionOptions, ): FimCreateParamsStreaming { return { model: options.model, prompt: prefix, - suffix, + suffix: options.promptOnly ? undefined : suffix, max_tokens: options.maxTokens, temperature: options.temperature, top_p: options.topP, From 95144dd2775bb98916df56e1760b9f09b77ec9a7 Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Tue, 4 Feb 2025 09:42:06 +0100 Subject: [PATCH 07/11] add comment linking to issue about the qwen fim template --- core/autocomplete/templating/AutocompleteTemplate.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index 3480a55c93..eb5a01cce0 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -55,6 +55,8 @@ const stableCodeFimTemplate: AutocompleteTemplate = { }; // https://github.com/QwenLM/Qwen2.5-Coder?tab=readme-ov-file#3-file-level-code-completion-fill-in-the-middle +// This issue asks about the use of <|repo_name|> and <|file_sep|> together with <|fim_prefix|>, <|fim_suffix|> and <|fim_middle|> +// https://github.com/QwenLM/Qwen2.5-Coder/issues/343 const qwenCoderFimTemplate: AutocompleteTemplate = { compilePrefixSuffix: ( prefix: string, From d5b51fe38e038e97948982984cec30878d1494b8 Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Wed, 5 Feb 2025 17:38:58 +0100 Subject: [PATCH 08/11] update completion provider to handle alternating diff patterns --- .../vscode/src/autocomplete/completionProvider.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/extensions/vscode/src/autocomplete/completionProvider.ts b/extensions/vscode/src/autocomplete/completionProvider.ts index c10d4d5f18..f7ddf82f59 100644 --- a/extensions/vscode/src/autocomplete/completionProvider.ts +++ b/extensions/vscode/src/autocomplete/completionProvider.ts @@ -280,8 +280,14 @@ export class ContinueCompletionProvider document.lineAt(startPos).range.end, ); } else if ( - diffPatternMatches(diffs, ["+", "-"]) || - diffPatternMatches(diffs, ["-", "+"]) + //diffPatternMatches(diffs, ["+", "-"]) || + //diffPatternMatches(diffs, ["-", "+"]) + diffs.every((diff, index) => + index % 2 === 0 ? diff.added : diff.removed + ) || + diffs.every((diff, index) => + index % 2 === 0 ? diff.removed : diff.added + ) ) { // We are midline and the model just inserted without repeating to the end of the line // We want to move the cursor to the end of the line From aaae90417b6344cc31b556e9f1231cab0f6b1fc0 Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Wed, 5 Feb 2025 18:11:38 +0100 Subject: [PATCH 09/11] update completion logic to handle midline insertions more accurately in completionProvider.ts --- .../vscode/src/autocomplete/completionProvider.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/extensions/vscode/src/autocomplete/completionProvider.ts b/extensions/vscode/src/autocomplete/completionProvider.ts index f7ddf82f59..aca4c54784 100644 --- a/extensions/vscode/src/autocomplete/completionProvider.ts +++ b/extensions/vscode/src/autocomplete/completionProvider.ts @@ -41,8 +41,7 @@ interface VsCodeCompletionInput { } export class ContinueCompletionProvider - implements vscode.InlineCompletionItemProvider -{ + implements vscode.InlineCompletionItemProvider { private onError(e: any) { const options = ["Documentation"]; if (e.message.includes("Ollama may not be installed")) { @@ -280,14 +279,8 @@ export class ContinueCompletionProvider document.lineAt(startPos).range.end, ); } else if ( - //diffPatternMatches(diffs, ["+", "-"]) || - //diffPatternMatches(diffs, ["-", "+"]) - diffs.every((diff, index) => - index % 2 === 0 ? diff.added : diff.removed - ) || - diffs.every((diff, index) => - index % 2 === 0 ? diff.removed : diff.added - ) + diffPatternMatches(diffs, ["+", "-"]) || + diffPatternMatches(diffs, ["-", "+"]) ) { // We are midline and the model just inserted without repeating to the end of the line // We want to move the cursor to the end of the line @@ -310,7 +303,7 @@ export class ContinueCompletionProvider } else { // If the first part of the diff isn't an insertion, then the model is // probably rewriting other parts of the line - return undefined; + // return undefined; - Let's assume it's simply an insertion } } } else { From d282974c34cbc9adc39c7d0c096b76d0920f2828 Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Thu, 6 Feb 2025 08:04:58 +0100 Subject: [PATCH 10/11] remove AutocompleteCompletionOptions and related logic --- .../templating/AutocompleteTemplate.ts | 90 +++++-------------- core/llm/llms/Ollama.ts | 15 +--- core/llm/openaiTypeConverters.ts | 5 +- 3 files changed, 29 insertions(+), 81 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index eb5a01cce0..f3fcc7eeaf 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -11,10 +11,6 @@ import { AutocompleteSnippetType, } from "../snippets/types.js"; -export interface AutocompleteCompletionOptions { - promptOnly?: boolean; -} - export interface AutocompleteTemplate { compilePrefixSuffix?: ( prefix: string, @@ -25,17 +21,17 @@ export interface AutocompleteTemplate { workspaceUris: string[], ) => [string, string]; template: - | string - | (( - prefix: string, - suffix: string, - filepath: string, - reponame: string, - language: string, - snippets: AutocompleteSnippet[], - workspaceUris: string[], - ) => string); - completionOptions?: Partial & Partial; + | string + | (( + prefix: string, + suffix: string, + filepath: string, + reponame: string, + language: string, + snippets: AutocompleteSnippet[], + workspaceUris: string[], + ) => string); + completionOptions?: Partial; } // https://huggingface.co/stabilityai/stable-code-3b @@ -58,47 +54,8 @@ const stableCodeFimTemplate: AutocompleteTemplate = { // This issue asks about the use of <|repo_name|> and <|file_sep|> together with <|fim_prefix|>, <|fim_suffix|> and <|fim_middle|> // https://github.com/QwenLM/Qwen2.5-Coder/issues/343 const qwenCoderFimTemplate: AutocompleteTemplate = { - compilePrefixSuffix: ( - prefix: string, - suffix: string, - filepath: string, - reponame: string, - snippets: AutocompleteSnippet[], - workspaceUris: string[] - ): [string, string] => { - // Helper function to get file name from snippet - function getFileName(snippet: { uri: string; uniquePath: string }) { - return snippet.uri.startsWith("file://") ? snippet.uniquePath : snippet.uri; - } - - // Start building the prompt with repo name - let prompt = `<|repo_name|>${reponame}`; - - const relativePaths = getShortestUniqueRelativeUriPaths( - [ - ...snippets.map((snippet) => - "filepath" in snippet ? snippet.filepath : "file:///Untitled.txt" - ), - filepath, - ], - workspaceUris - ); - - // Add each snippet with its file path - snippets.forEach((snippet, i) => { - const content = snippet.type === AutocompleteSnippetType.Diff - ? snippet.content - : snippet.content; - prompt += `\n<|file_sep|>${getFileName(relativePaths[i])}\n${content}`; - }); - - // Add the current file's prefix and suffix - prompt += `<|fim_prefix|>${prefix}<|fim_suffix|>${suffix}<|fim_middle|>`; - - // Empty suffix will make the prefix be used as a single prompt - return [prompt, ""]; - }, - template: "{{{prefix}}}", // output of compilePrefixSuffix already compiles everything into a single prompt + template: + "<|fim_prefix|>{{{prefix}}}<|fim_suffix|>{{{suffix}}}<|fim_middle|>", completionOptions: { stop: [ "<|endoftext|>", @@ -111,7 +68,6 @@ const qwenCoderFimTemplate: AutocompleteTemplate = { "<|im_start|>", "<|im_end|>", ], - promptOnly: true // with ollama provider this makes sure a single prompt is sent (with suffix as part of the prompt, not as a separate parameter) }, }; @@ -138,7 +94,10 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { if (snippets.length === 0) { if (suffix.trim().length === 0 && prefix.trim().length === 0) { - return [`+++++ ${getLastNUriRelativePathParts(workspaceUris, filepath, 2)}\n\n[PREFIX]\n${prefix}`, suffix]; + return [ + `+++++ ${getLastNUriRelativePathParts(workspaceUris, filepath, 2)}\n${prefix}`, + suffix, + ]; } return [prefix, suffix]; } @@ -169,16 +128,15 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { .join("\n\n"); return [ - `${otherFiles}\n\n+++++ ${getFileName(relativePaths[relativePaths.length - 1])}\n[SUFFIX]${suffix}\n[PREFIX]${prefix}`, - "", + `${otherFiles}\n\n+++++ ${getFileName(relativePaths[relativePaths.length - 1])}\n${prefix}`, + suffix, ]; }, template: (prefix: string, suffix: string): string => { - return prefix + return `[SUFFIX]${suffix}[PREFIX]${prefix}`; }, completionOptions: { stop: ["[PREFIX]", "[SUFFIX]", "\n+++++ "], - promptOnly: true }, }; @@ -212,10 +170,10 @@ const starcoder2FimTemplate: AutocompleteTemplate = { snippets.length === 0 ? "" : `${snippets - .map((snippet) => { - return snippet.content; - }) - .join("")}`; + .map((snippet) => { + return snippet.content; + }) + .join("")}`; const prompt = `${otherFiles}${prefix}${suffix}`; return prompt; diff --git a/core/llm/llms/Ollama.ts b/core/llm/llms/Ollama.ts index 5c5df14885..b9734acb92 100644 --- a/core/llm/llms/Ollama.ts +++ b/core/llm/llms/Ollama.ts @@ -4,7 +4,6 @@ import { ChatMessage, CompletionOptions, LLMOptions } from "../../index.js"; import { renderChatMessage } from "../../util/messageContent.js"; import { BaseLLM } from "../index.js"; import { streamResponse } from "../stream.js"; -import { AutocompleteCompletionOptions } from "../../autocomplete/templating/AutocompleteTemplate.js"; type OllamaChatMessage = { role: "tool" | "user" | "assistant" | "system"; @@ -294,28 +293,20 @@ class Ollama extends BaseLLM { } private _getGenerateOptions( - options: CompletionOptions & Partial, + options: CompletionOptions, prompt: string, suffix?: string, ): OllamaRawOptions { - const generateOptions: OllamaRawOptions = { + return { model: this._getModel(), prompt, + suffix, raw: options.raw, options: this._getModelFileParams(options), keep_alive: options.keepAlive ?? 60 * 30, // 30 minutes - suffix, stream: options.stream, // Not supported yet: context, images, system, template, format }; - - if (options.promptOnly) { - console.log("fim: removing suffix"); - - delete generateOptions.suffix; - } - - return generateOptions; } private getEndpoint(endpoint: string): URL { diff --git a/core/llm/openaiTypeConverters.ts b/core/llm/openaiTypeConverters.ts index af21bf4708..4ed8be1c48 100644 --- a/core/llm/openaiTypeConverters.ts +++ b/core/llm/openaiTypeConverters.ts @@ -16,7 +16,6 @@ import { MessageContent, TextMessagePart, } from ".."; -import { AutocompleteCompletionOptions } from "../autocomplete/templating/AutocompleteTemplate"; export function toChatMessage( message: ChatMessage, @@ -139,12 +138,12 @@ export function toCompleteBody( export function toFimBody( prefix: string, suffix: string, - options: CompletionOptions & AutocompleteCompletionOptions, + options: CompletionOptions, ): FimCreateParamsStreaming { return { model: options.model, prompt: prefix, - suffix: options.promptOnly ? undefined : suffix, + suffix, max_tokens: options.maxTokens, temperature: options.temperature, top_p: options.topP, From d80dbff7a7c2e3fea59d9a4e8ac20026ea1002fd Mon Sep 17 00:00:00 2001 From: ferenci84 Date: Thu, 6 Feb 2025 08:12:27 +0100 Subject: [PATCH 11/11] remove commented snippet filtering and reversing logic in AutocompleteTemplate.ts --- core/autocomplete/templating/AutocompleteTemplate.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/autocomplete/templating/AutocompleteTemplate.ts b/core/autocomplete/templating/AutocompleteTemplate.ts index f3fcc7eeaf..7f5366df07 100644 --- a/core/autocomplete/templating/AutocompleteTemplate.ts +++ b/core/autocomplete/templating/AutocompleteTemplate.ts @@ -102,11 +102,6 @@ const codestralMultifileFimTemplate: AutocompleteTemplate = { return [prefix, suffix]; } - //snippets = snippets.filter((snippet) => "filepath" in snippet); - - // reverse the snippets so that the most recent snippet is last - snippets = [...snippets].reverse(); - const relativePaths = getShortestUniqueRelativeUriPaths( [ ...snippets.map((snippet) =>