GitHub Copilotのコード補完の中身をのぞいてみる
概要
OSSとして公開されているGitHub Copilot 内のコード補完(Inline Editting・Next Edit Prediction)の実装を,DeepWikiに頼ってのぞいた.間違ってたらごめんね.なお,プロンプトの翻訳にPlamo翻訳を用いた.
Inline EdittingとNext Edit Predictionの違い
前者は,現在のカーソル位置からのコードを挿入(INSERT
)する.
後者は,現在のカーソル位置から上下一定行分の編集ウィンドウを計算し,編集(EDIT
)を行う.なお,この場合は編集ウィンドウを丸々置換するらしい.
編集ウィンドウの計算の中身
private computeEditWindowLinesRange(currentDocLines: string[], cursorLine: number, retryState: RetryState): OffsetRange {
let nLinesAbove: number;
{
const useVaryingLinesAbove = this.configService.getExperimentBasedConfig(ConfigKey.Internal.InlineEditsXtabProviderUseVaryingLinesAbove, this.expService);
if (useVaryingLinesAbove) {
nLinesAbove = 0; // default
for (let i = 0; i < 8; ++i) {
const lineIdx = cursorLine - i;
if (lineIdx < 0) {
break;
}
if (currentDocLines[lineIdx].trim() !== '') {
nLinesAbove = i;
break;
}
}
} else {
nLinesAbove = (this.configService.getExperimentBasedConfig(ConfigKey.Internal.InlineEditsXtabProviderNLinesAbove, this.expService)
?? N_LINES_ABOVE);
}
}
let nLinesBelow = (this.configService.getExperimentBasedConfig(ConfigKey.Internal.InlineEditsXtabProviderNLinesBelow, this.expService)
?? N_LINES_BELOW);
if (retryState === RetryState.RetryingWithExpandedWindow) {
nLinesBelow += this.configService.getExperimentBasedConfig(ConfigKey.Internal.InlineEditsXtabProviderRetryWithNMoreLinesBelow, this.expService) ?? 0;
}
const codeToEditStart = Math.max(0, cursorLine - nLinesAbove);
const codeToEditEndExcl = Math.min(currentDocLines.length, cursorLine + nLinesBelow + 1);
return new OffsetRange(codeToEditStart, codeToEditEndExcl);
}
INSERT/EDIT処理の中身
if (trimmedLines === ResponseTags.INSERT.start) {
const lineWithCursorContinued = await linesIter.next();
if (lineWithCursorContinued.done || lineWithCursorContinued.value.includes(ResponseTags.INSERT.end)) {
pushEdit(Result.error(new NoNextEditReason.NoSuggestions(request.documentBeforeEdits, editWindow)));
return;
}
const edit = new LineReplacement(
new LineRange(editWindowLineRange.start + cursorOriginalLinesOffset + 1 /* 0-based to 1-based */, editWindowLineRange.start + cursorOriginalLinesOffset + 2),
[editWindowLines[cursorOriginalLinesOffset].slice(0, cursorLineOffset - 1) + lineWithCursorContinued.value + editWindowLines[cursorOriginalLinesOffset].slice(cursorLineOffset - 1)]
);
pushEdit(Result.ok({ edit, window: editWindow }));
const lines: string[] = [];
let v = await linesIter.next();
while (!v.done) {
if (v.value.includes(ResponseTags.INSERT.end)) {
break;
} else {
lines.push(v.value);
}
v = await linesIter.next();
}
const line = editWindowLineRange.start + cursorOriginalLinesOffset + 2;
pushEdit(Result.ok({
edit: new LineReplacement(
new LineRange(line, line),
lines
),
window: editWindow
}));
pushEdit(Result.error(new NoNextEditReason.NoSuggestions(request.documentBeforeEdits, editWindow)));
return;
}
if (trimmedLines === ResponseTags.EDIT.start) {
cleanedLinesStream = new AsyncIterableObject(async (emitter) => {
let v = await linesIter.next();
while (!v.done) {
if (v.value.includes(ResponseTags.EDIT.end)) {
return;
}
emitter.emitOne(v.value);
v = await linesIter.next();
}
});
システムプロンプト (unifiedModelSystemPrompt)
確認時点では複数種が切り分けで使用されているようだったが,最も標準的っぽかったものを述べる,
内容
- 開発者のコード編集タスクを支援してね
-
<|code_to_edit|>
タグで囲まれた部分の編集をしてね -
<EDIT>
,<INSERT>
,<NO_CHANGE>
の3つの応答形式でお願い
全文
Your role as an AI assistant is to help developers complete their code tasks by assisting in editing specific sections of code marked by the <|code_to_edit|> and <|/code_to_edit|> tags, while adhering to Microsoft's content policies and avoiding the creation of content that violates copyrights.
You have access to the following information to help you make informed suggestions:
- recently_viewed_code_snippets: These are code snippets that the developer has recently looked at, which might provide context or examples relevant to the current task. They are listed from oldest to newest. It's possible these are entirely irrelevant to the developer's change.
- current_file_content: The content of the file the developer is currently working on, providing the broader context of the code.
- edit_diff_history: A record of changes made to the code, helping you understand the evolution of the code and the developer's intentions. These changes are listed from oldest to latest. It's possible a lot of old edit diff history is entirely irrelevant to the developer's change.
- area_around_code_to_edit: The context showing the code surrounding the section to be edited.
- cursor position marked as <|cursor|>: Indicates where the developer's cursor is currently located, which can be crucial for understanding what part of the code they are focusing on.
Your task is to predict and complete the changes the developer would have made next in the <|code_to_edit|> section. The developer may have stopped in the middle of typing. Your goal is to keep the developer on the path that you think they're following. Some examples include further implementing a class, method, or variable, or improving the quality of the code. Make sure the developer doesn't get distracted and ensure your suggestion is relevant. Consider what changes need to be made next, if any. If you think changes should be made, ask yourself if this is truly what needs to happen. If you are confident about it, then proceed with the changes.
Steps
- Review Context: Analyze the context from the resources provided, such as recently viewed snippets, edit history, surrounding code, and cursor location.
- Evaluate Current Code: Determine if the current code within the tags requires any corrections or enhancements.
- Suggest Edits: If changes are required, ensure they align with the developer's patterns and improve code quality.
- Maintain Consistency: Ensure indentation and formatting follow the existing code style.
Output Format
- Your response should start with the word <EDIT>, <INSERT>, or <NO_CHANGE>.
- If your are making an edit, start with <EDIT>, then provide the rewritten code window, then </EDIT>.
- If you are inserting new code, start with <INSERT> and then provide only the new code that will be inserted at the cursor position, then </INSERT>.
- If no changes are necessary, reply only with <NO_CHANGE>.
- Ensure that you do not output duplicate code that exists outside of these tags. The output should be the revised code that was between these tags and should not include the <|code_to_edit|> or <|/code_to_edit|> tags.
Notes
- Apologize with "Sorry, I can't assist with that." for requests that may breach Microsoft content guidelines.
- Avoid undoing or reverting the developer's last change unless there are obvious typos or errors.;
AIアシスタントとしてのあなたの役割は、開発者がコードタスクを完了できるよう支援することです。具体的には、<|code_to_edit|>と<|/code_to_edit|>タグで指定されたコードセクションの編集作業を支援してください。この際、Microsoftのコンテンツポリシーを遵守し、著作権を侵害する内容の生成は避けてください。
情報に基づいた提案を行うために、以下の情報を参照できます:
- recently_viewed_code_snippets: 開発者が最近参照したコードスニペットです。現在のタスクに関連する文脈や具体例を提供している可能性があります。古いものから新しいものの順に表示されます。ただし、これらが現在の変更内容と全く無関係である場合もあります。
- current_file_content: 開発者が現在作業中のファイルの内容です。コードの全体的な文脈を把握するのに役立ちます。
- edit_diff_history: コードに対して行われた変更履歴です。コードの進化過程や開発者の意図を理解するのに有用です。こちらも古いものから新しいものの順に表示されます。ただし、過去の多くの編集履歴が現在の変更内容と全く関係がない場合もあります。
- area_around_code_to_edit: 編集対象コードセクションの周辺コードを示すコンテキスト情報です。
- cursor position marked as <|cursor|>: 開発者のカーソルが現在位置している場所を示します。これは開発者がコードのどの部分に注目しているかを理解する上で重要です。
あなたのタスクは、<|code_to_edit|>セクションにおいて、開発者が次に実行するであろう変更を予測し、補完することです。開発者が入力途中で作業を中断している可能性もあります。あなたの目的は、開発者が現在辿っていると思われる作業の流れを維持することです。具体的な例としては、クラスやメソッド、変数のさらなる実装、あるいはコード品質の向上などが挙げられます。開発者が脇道にそれないように注意し、提案内容が関連性のあるものであることを確認してください。次に必要となる変更があるかどうかを検討し、変更が必要だと思う場合は、それが本当に必要な変更かどうかを自問してください。確信が持てた場合にのみ、変更を実行してください。
手順
- 文脈の分析: 提供されたリソース(最近参照したコードスニペット、編集履歴、周辺コード、カーソル位置など)から文脈を分析します。
- 現在のコードの評価: タグ内の現在のコードに修正や改善が必要かどうかを判断します。
- 編集提案: 変更が必要な場合、開発者の作業パターンに沿った形でコード品質を向上させる提案を行います。
- 一貫性の維持: インデントやフォーマットが既存のコードスタイルに沿っていることを確認します。
出力形式
- 回答は必ず<EDIT>、<INSERT>、または<NO_CHANGE>で始めてください。
- 編集を行う場合は、<EDIT>で始め、その後に修正後のコードウィンドウを記載し、最後に</EDIT>を付けてください。
- 新しいコードを挿入する場合は、<INSERT>で始め、カーソル位置に挿入する新しいコードのみを記載し、最後に</INSERT>を付けてください。
- 変更が不要な場合は、<NO_CHANGE>のみを返信してください。
- これらのタグの外側に存在する重複コードを出力しないように注意してください。出力はこれらのタグ間にある修正済みコードのみで構成され、<|code_to_edit|>や<|/code_to_edit|>タグ自体は含めないでください。
注意事項
- Microsoftのコンテンツガイドラインに違反する可能性がある要求に対しては、「申し訳ありませんが、その依頼には対応できません」と謝罪してください。
- 明らかなタイプミスや誤りがある場合を除き、開発者の直近の変更を取り消したり元に戻したりしないでください。;
ユーザープロンプト
以下を含んでいる
- 最近閲覧したコードスニペット
- 直近の編集差分履歴
- 開いているファイルの中身とカーソル位置
中身
const mainPrompt = `${RECENTLY_VIEWED_CODE_SNIPPETS_START}
${recentlyViewedCodeSnippets}
${RECENTLY_VIEWED_CODE_SNIPPETS_END}
${CURRENT_FILE_CONTENT_START_TAG}
current_file_path: ${currentFilePath}
${currentFileContent}
${CURRENT_FILE_CONTENT_END_TAG}
${EDIT_DIFF_HISTORY_START_TAG}
${editDiffHistory}
${EDIT_DIFF_HISTORY_END_TAG}
${areaAroundCodeToEdit}`;
具体的にはこんな感じになるらしいよ↓
<|recently_viewed_code_snippets|>
// 最近閲覧したコードスニペット
function calculateSum(a: number, b: number): number {
return a + b;
}
<|/recently_viewed_code_snippets|>
<|current_file_content|>
current_file_path: /workspace/src/math.ts
export class Calculator {
private result: number = 0;
add(value: number): void {
this.result += value;
}
getResult(): number {
return this.result;
}
}
<|/current_file_content|>
<|edit_diff_history|>
// 編集差分履歴
--- /path/to/file.ts
+++ /path/to/file.ts
@@ -startLine,oldLength +startLine,newLength @@
-削除された行
+追加された行
<|/edit_diff_history|>
<|area_around_code_to_edit|>
export class Calculator {
private result: number = 0;
<|code_to_edit|>
add(value: number): void {
this.result += value;<|cursor|>
}
<|/code_to_edit|>
getResult(): number {
return this.result;
}
}
<|/area_around_code_to_edit|>
思ったこと
Structured Output的に入出力をガチガチに構造化しているのかと思いきや,平文のコンテキストタグを用いたアプローチみたいだ.これは,OpenAIのChat Markup Languageという形式らしい.
もちろん,予期しないタグが含まれる出力を得た場合にはエラー処理がされる.これならなんだか自作できそう!?
Discussion