Skip to content

Commit

Permalink
Merge branch 'HoleFiller-Autocomplete' into 'main'
Browse files Browse the repository at this point in the history
Finish: Finish auto code completion

See merge request nsysu_mis_projects/misb114/course_mis324!28
  • Loading branch information
whats2000 committed Nov 17, 2024
2 parents 08a9473 + 244244b commit 87097f2
Show file tree
Hide file tree
Showing 25 changed files with 473 additions and 68 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Here’s an outline of the upcoming features and improvements for CodeBRT:
- [ ] Manual code completion with hotkeys
- [x] Trigger code completion
- [ ] Context retrieval
- [ ] Auto code completion
- [x] Auto code completion
- [ ] Code integrator to compose code snippets

### Version 0.4 (WIP)
Expand Down
2 changes: 1 addition & 1 deletion VSCodeExtension/code-brt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Here’s an outline of the upcoming features and improvements for CodeBRT:
- [ ] Manual code completion with hotkeys
- [x] Trigger code completion
- [ ] Context retrieval
- [ ] Auto code completion
- [x] Auto code completion
- [ ] Code integrator to compose code snippets

### Version 0.4 (WIP)
Expand Down
1 change: 1 addition & 0 deletions VSCodeExtension/code-brt/src/api/historyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export class HistoryManager implements IHistoryManager {
topK: undefined,
presencePenalty: undefined,
frequencyPenalty: undefined,
stop: undefined,
},
entries: {},
};
Expand Down
11 changes: 11 additions & 0 deletions VSCodeExtension/code-brt/src/constants/modelAdvanceSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,15 @@ export const MODEL_ADVANCE_SETTINGS: {
},
link: 'https://platform.openai.com/docs/guides/text-generation/parameter-details',
},
stop: {
description:
`A list of tokens where the model should stop generating further tokens. ` +
`Separate each token with a new line for multiple tokens. ` +
`If you want to stop the model from generating more tokens, you can add a stop token here.`,
range: {
min: undefined,
max: undefined,
},
link: 'https://platform.openai.com/docs/api-reference/chat/create#chat-create-stop',
},
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CompletionTemplate } from '../types';

/**
* The follow code is modified from the original source code.
* https://github.com/continuedev/continue/blob/main/core/autocomplete/templates.ts
Expand Down Expand Up @@ -113,3 +115,83 @@ export const MAIN_PROMPT_TEMPLATE = `<LANGUAGE>{codeLanguage}</LANGUAGE>
TASK: Fill the {{FILL_HERE}} hole. Answer only with the CORRECT completion inside the <COMPLETION> tag. Do NOT include any explanations, markdown formatting, or extra content. Do it now.
<COMPLETION>`;

// ==================== Add Ollama Model Template ====================
export const HOLE_FILLER_TEMPLATE: {
[key: string]: CompletionTemplate;
} = {
// StableCode FIM Template (Default)
stableCodeFimTemplate: {
template: '<fim_prefix>{{{prefix}}}<fim_suffix>{{{suffix}}}<fim_middle>',
completionOptions: {
stop: [
'<fim_prefix>',
'<fim_suffix>',
'<fim_middle>',
'<file_sep>',
'<|endoftext|>',
'</fim_middle>',
'</code>',
],
},
},
// Qwen2.5-Coder Template
qwenCoderFimTemplate: {
template:
'<|fim_prefix|>{{{prefix}}}<|fim_suffix|>{{{suffix}}}<|fim_middle|>',
completionOptions: {
stop: [
'<|endoftext|>',
'<|fim_prefix|>',
'<|fim_middle|>',
'<|fim_suffix|>',
'<|fim_pad|>',
'<|repo_name|>',
'<|file_sep|>',
'<|im_start|>',
'<|im_end|>',
],
},
},
// Codestral Template
codestralFimTemplate: {
template: '[SUFFIX]{{{suffix}}}[PREFIX]{{{prefix}}}',
completionOptions: {
stop: ['[PREFIX]', '[SUFFIX]'],
},
},
// CodeLlama Template
codeLlamaFimTemplate: {
template: '<PRE> {{{prefix}}} <SUF>{{{suffix}}} <MID>',
completionOptions: {
stop: ['<PRE>', '<SUF>', '<MID>', '<EOT>'],
},
},
// DeepSeek-Coder Template
deepseekFimTemplate: {
template:
'<|fim▁begin|>{{{prefix}}}<|fim▁hole|>{{{suffix}}}<|fim▁end|>',
completionOptions: {
stop: [
'<|fim▁begin|>',
'<|fim▁hole|>',
'<|fim▁end|>',
'//',
'<|end▁of▁sentence|>',
],
},
},
// StarCoder Template
starCoderFimTemplate: {
template: '<fim_prefix>{{{prefix}}}<fim_suffix>{{{suffix}}}<fim_middle>',
completionOptions: {
stop: [
'<fim_prefix>',
'<fim_suffix>',
'<fim_middle>',
'<file_sep>',
'<|endoftext|>',
],
},
},
};
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
import vscode from 'vscode';
import * as vscode from 'vscode';

import type { LoadedModelServices } from 'src/types';
import { AbstractCompletionProvider } from '../base';
import { SettingsManager } from '../../../api';
import { AutoCodeCompletionStrategy } from '../strategies';
import { StatusBarManager } from '../ui/statusBarManager';
import type { LoadedModelServices } from '../../../types';

// TODO: Implement the AutoCodeCompletionProvider class
export class AutoCodeCompletionProvider implements AbstractCompletionProvider {
private readonly completionStrategy: AutoCodeCompletionStrategy;

constructor(
_ctx: vscode.ExtensionContext,
_settingsManager: SettingsManager,
_loadedModelServices: LoadedModelServices,
_statusBarManager: StatusBarManager,
) {}
ctx: vscode.ExtensionContext,
settingsManager: SettingsManager,
loadedModelServices: LoadedModelServices,
statusBarManager: StatusBarManager,
) {
this.completionStrategy = new AutoCodeCompletionStrategy(
ctx,
settingsManager,
loadedModelServices,
statusBarManager,
);
}

provideCompletionItems(
_document: vscode.TextDocument,
_position: vscode.Position,
_token: vscode.CancellationToken,
async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
): Promise<
vscode.InlineCompletionItem[] | vscode.InlineCompletionList | null
> {
return Promise.resolve(null);
const completions = await this.completionStrategy.provideCompletion(
document,
position,
token,
);

if (!completions || completions.length === 0) {
return null;
}

return new vscode.InlineCompletionList(completions);
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,185 @@
// TODO: Implement the auto code completion strategy by Code Model
import * as vscode from 'vscode';

import type {
CodeLanguageId,
CompletionStrategy,
CompletionTemplate,
LoadedModelServices,
ModelServiceType,
} from '../../../types';
import { FILE_TO_LANGUAGE, LANGUAGE_NAME_MAPPING } from '../constants';
import { SettingsManager, HistoryManager } from '../../../api';
import { StatusBarManager } from '../ui/statusBarManager';
import { getTemplateForModel, postProcessCompletion } from '../utils';

export class AutoCodeCompletionStrategy implements CompletionStrategy {
private readonly settingsManager: SettingsManager;
private readonly historyManager: HistoryManager;
private readonly loadedModelServices: LoadedModelServices;
private readonly statusBarManager: StatusBarManager;

constructor(
ctx: vscode.ExtensionContext,
settingsManager: SettingsManager,
loadedModelServices: LoadedModelServices,
statusBarManager: StatusBarManager,
) {
this.settingsManager = settingsManager;
this.historyManager = new HistoryManager(
ctx,
'autoCodeCompletionIndex.json',
'autoCodeCompletionHistories',
);
this.loadedModelServices = loadedModelServices;
this.statusBarManager = statusBarManager;
}

private detectLanguageId(
document: vscode.TextDocument,
): CodeLanguageId | 'unknown' {
const fileExtension =
document.uri.fsPath.split('.').pop()?.toLowerCase() || '';
const languageIdFromExtension = FILE_TO_LANGUAGE[fileExtension];
const languageId =
languageIdFromExtension || document.languageId.toLowerCase();

if (LANGUAGE_NAME_MAPPING[languageId as CodeLanguageId]) {
return languageId as CodeLanguageId;
} else {
return 'unknown';
}
}

private cleanCompletionResponse(response: string): string {
return response.trim();
}

private async getResponse(
prompt: string,
modelService: ModelServiceType,
modelName: string,
template: CompletionTemplate,
): Promise<string> {
const history = this.historyManager.getCurrentHistory();
await this.historyManager.updateHistoryModelAdvanceSettings(history.root, {
...history.advanceSettings,
systemPrompt: '',
temperature: template.completionOptions?.temperature || 0.7,
maxTokens: template.completionOptions?.maxTokens || 150,
stop: template.completionOptions?.stop || undefined,
});

const response = await this.loadedModelServices[
modelService
].service.getResponse({
query: prompt,
historyManager: this.historyManager,
selectedModelName: modelName,
disableTools: true,
});

return this.cleanCompletionResponse(response.textResponse);
}

public async provideCompletion(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
): Promise<vscode.InlineCompletionItem[] | null> {
if (!this.settingsManager.get('autoTriggerCodeCompletion')) {
return null;
}

this.statusBarManager.showProcessing();

try {
const modelService = this.settingsManager.get(
'lastUsedAutoCodeCompletionModelService',
) as ModelServiceType;
const modelName = this.settingsManager.get(
'lastSelectedAutoCodeCompletionModel',
)[modelService];

if (!this.loadedModelServices[modelService]) {
console.warn(`Model service ${modelService} is not loaded.`);
return null;
}

const languageId = this.detectLanguageId(document);
if (languageId === 'unknown') {
console.warn('Unsupported language for code completion.');
return null;
}

const language = LANGUAGE_NAME_MAPPING[languageId];

const template = getTemplateForModel(modelName);

if (!template) {
console.warn(`No template found for model ${modelName}`);
return null;
}

const maxLines = 20;
const startLine = Math.max(0, position.line - maxLines);
const endLine = Math.min(
document.lineCount - 1,
position.line + maxLines,
);

const prefix = document.getText(
new vscode.Range(new vscode.Position(startLine, 0), position),
);

const suffix = document.getText(
new vscode.Range(
position,
new vscode.Position(endLine, Number.MAX_VALUE),
),
);

const filepath = document.uri.fsPath;

const prompt =
typeof template.template === 'function'
? template.template(prefix, suffix, filepath, language)
: template.template
.replace('{{{prefix}}}', prefix)
.replace('{{{suffix}}}', suffix);

if (token.isCancellationRequested) {
return null;
}

const rawResponse = await this.getResponse(
prompt,
modelService,
modelName,
template,
);

const postProcessedResult = postProcessCompletion(
rawResponse,
prefix,
suffix,
modelName,
);

if (!postProcessedResult) {
return null;
}

const completionItem = new vscode.InlineCompletionItem(
postProcessedResult,
new vscode.Range(position, position),
);

return [completionItem];
} catch (error) {
console.error('Autocomplete error:', error);
return null;
} finally {
this.statusBarManager.showIdle();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,2 @@
import * as vscode from 'vscode';

export interface CompletionStrategy {
provideCompletion(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
): Promise<vscode.InlineCompletionItem[] | null>;
}

export * from './manuallyCodeCompletionStrategy';
export * from './autoCodeCompletionStrategy';
Loading

0 comments on commit 87097f2

Please sign in to comment.