チュートリアル: 言語モデルAPIを使用してAI搭載のコード注釈を生成する
このチュートリアルでは、AI搭載のCode Tutorを構築するためのVS Code拡張機能を作成する方法を学びます。言語モデル(LM)APIを使用してコードを改善するための提案を生成し、VS Code拡張機能APIを活用して、ユーザーが詳細情報を確認するためにホバーできるインライン注釈としてエディターにシームレスに統合します。このチュートリアルを完了すると、VS CodeでカスタムAI機能を実装する方法がわかります。
前提条件
このチュートリアルを完了するには、次のツールとアカウントが必要です
拡張機能の足場を構築する
まず、YeomanとVS Code拡張機能ジェネレーターを使用して、開発準備が整ったTypeScriptまたはJavaScriptプロジェクトの足場を構築します。
npx --package yo --package generator-code -- yo code
新しい拡張機能ウィザードを完了するために、次のオプションを選択します...
# ? What type of extension do you want to create? New Extension (TypeScript)
# ? What's the name of your extension? Code Tutor
### Press <Enter> to choose default for all options below ###
# ? What's the identifier of your extension? code-tutor
# ? What's the description of your extension? LEAVE BLANK
# ? Initialize a git repository? Yes
# ? Bundle the source code with webpack? No
# ? Which package manager to use? npm
# ? Do you want to open the new folder with Visual Studio Code? Open with `code`
正しいコマンドを含めるように package.json ファイルを修正する
スキャフォールドされたプロジェクトには、package.json
ファイルに単一の "helloWorld" コマンドが含まれています。このコマンドは、拡張機能がインストールされたときにコマンドパレットに表示されます。
"contributes": {
"commands": [
{
"command": "code-tutor.helloWorld",
"title": "Hello World"
}
]
}
行に注釈を追加するCode Tutor拡張機能を構築するため、ユーザーがこれらの注釈のオン/オフを切り替えられるコマンドが必要になります。command
および title
プロパティを更新してください
"contributes": {
"commands": [
{
"command": "code-tutor.annotate",
"title": "Toggle Tutor Annotations"
}
]
}
package.json
が拡張機能のコマンドとUI要素を定義する一方で、src/extension.ts
ファイルはそれらのコマンドに対して実行されるべきコードを配置する場所です。
src/extension.ts
ファイルを開き、registerCommand
メソッドを package.json
ファイルの command
プロパティと一致するように変更します。
const disposable = vscode.commands.registerCommand('code-tutor.annotate', () => {
F5キーを押して拡張機能を実行します。これにより、拡張機能がインストールされた新しいVS Codeインスタンスが開きます。⇧⌘P (Windows, Linux Ctrl+Shift+P)を押してコマンドパレットを開き、「tutor」と検索します。「Tutor Annotations」コマンドが表示されるはずです。
「Tutor Annotations」コマンドを選択すると、「Hello World」通知メッセージが表示されます。
"annotate" コマンドを実装する
Code Tutorの注釈を機能させるには、コードを送信して注釈を提供するように依頼する必要があります。これは3つのステップで行います
- ユーザーが開いている現在のタブから行番号付きのコードを取得します。
- そのコードを、モデルに注釈の提供方法を指示するカスタムプロンプトとともに、言語モデルAPIに送信します。
- 注釈を解析し、エディターに表示します。
ステップ1: 行番号付きのコードを取得する
現在のタブからコードを取得するには、ユーザーが開いているタブへの参照が必要です。これは、registerCommand
メソッドを registerTextEditorCommand
に変更することで取得できます。これら2つのコマンドの違いは、後者がユーザーが開いているタブ(TextEditor
と呼ばれる)への参照を提供することです。
const disposable = vscode.commands.registerTextEditorCommand('code-tutor.annotate', async (textEditor: vscode.TextEditor) => {
これで、textEditor
参照を使用して「表示可能なエディタースペース」内のすべてのコードを取得できます。これは画面上に表示されるコードであり、表示可能なエディタースペースの上または下にあるコードは含まれません。
extension.ts
ファイルの最下部にある export function deactivate() { }
行の直上に次のメソッドを追加します。
function getVisibleCodeWithLineNumbers(textEditor: vscode.TextEditor) {
// get the position of the first and last visible lines
let currentLine = textEditor.visibleRanges[0].start.line;
const endLine = textEditor.visibleRanges[0].end.line;
let code = '';
// get the text from the line at the current position.
// The line number is 0-based, so we add 1 to it to make it 1-based.
while (currentLine < endLine) {
code += `${currentLine + 1}: ${textEditor.document.lineAt(currentLine).text} \n`;
// move to the next line position
currentLine++;
}
return code;
}
このコードは、TextEditorの visibleRanges
プロパティを使用して、エディターで現在表示されている行の位置を取得します。次に、最初の行の位置から最後の行の位置まで移動し、各コード行を行番号とともに文字列に追加します。最後に、表示可能なすべてのコードを行番号付きで含む文字列を返します。
これで、code-tutor.annotate
コマンドからこのメソッドを呼び出すことができます。コマンドの実装を次のように変更してください
const disposable = vscode.commands.registerTextEditorCommand(
'code-tutor.annotate',
async (textEditor: vscode.TextEditor) => {
// Get the code with line numbers from the current editor
const codeWithLineNumbers = getVisibleCodeWithLineNumbers(textEditor);
}
);
ステップ2: コードとプロンプトを言語モデルAPIに送信する
次のステップは、GitHub Copilot言語モデルを呼び出し、ユーザーのコードと注釈を作成するための指示を送信することです。
これを行うには、まず使用したいチャットモデルを指定する必要があります。ここで4oを選択するのは、私たちが構築している種類のインタラクションにとって高速で高性能なモデルだからです。
const disposable = vscode.commands.registerTextEditorCommand(
'code-tutor.annotate',
async (textEditor: vscode.TextEditor) => {
// Get the code with line numbers from the current editor
const codeWithLineNumbers = getVisibleCodeWithLineNumbers(textEditor);
// select the 4o chat model
let [model] = await vscode.lm.selectChatModels({
vendor: 'copilot',
family: 'gpt-4o'
});
}
);
モデルに注釈を作成させ、レスポンスの形式を指定するための指示、つまり「プロンプト」が必要です。ファイルの先頭、インポートの直下に次のコードを追加してください。
const ANNOTATION_PROMPT = `You are a code tutor who helps students learn how to write better code. Your job is to evaluate a block of code that the user gives you and then annotate any lines that could be improved with a brief suggestion and the reason why you are making that suggestion. Only make suggestions when you feel the severity is enough that it will impact the readability and maintainability of the code. Be friendly with your suggestions and remember that these are students so they need gentle guidance. Format each suggestion as a single JSON object. It is not necessary to wrap your response in triple backticks. Here is an example of what your response should look like:
{ "line": 1, "suggestion": "I think you should use a for loop instead of a while loop. A for loop is more concise and easier to read." }{ "line": 12, "suggestion": "I think you should use a for loop instead of a while loop. A for loop is more concise and easier to read." }
`;
これは、言語モデルに注釈の生成方法を指示する特別なプロンプトです。また、モデルが応答をどのようにフォーマットすべきかの例も含まれています。これらの例(「マルチショット」とも呼ばれる)は、応答の形式を定義し、それを解析して注釈として表示できるようにするものです。
モデルにはメッセージを配列で渡します。この配列には、好きなだけメッセージを含めることができます。私たちの場合、プロンプトの後にユーザーの行番号付きコードが含まれます。
const disposable = vscode.commands.registerTextEditorCommand(
'code-tutor.annotate',
async (textEditor: vscode.TextEditor) => {
// Get the code with line numbers from the current editor
const codeWithLineNumbers = getVisibleCodeWithLineNumbers(textEditor);
// select the 4o chat model
let [model] = await vscode.lm.selectChatModels({
vendor: 'copilot',
family: 'gpt-4o'
});
// init the chat message
const messages = [
vscode.LanguageModelChatMessage.User(ANNOTATION_PROMPT),
vscode.LanguageModelChatMessage.User(codeWithLineNumbers)
];
}
);
モデルにメッセージを送信するには、まず選択したモデルが利用可能であることを確認する必要があります。これは、拡張機能が準備できていない場合や、ユーザーがGitHub Copilotにサインインしていない場合を処理します。その後、メッセージをモデルに送信します。
const disposable = vscode.commands.registerTextEditorCommand(
'code-tutor.annotate',
async (textEditor: vscode.TextEditor) => {
// Get the code with line numbers from the current editor
const codeWithLineNumbers = getVisibleCodeWithLineNumbers(textEditor);
// select the 4o chat model
let [model] = await vscode.lm.selectChatModels({
vendor: 'copilot',
family: 'gpt-4o'
});
// init the chat message
const messages = [
vscode.LanguageModelChatMessage.User(ANNOTATION_PROMPT),
vscode.LanguageModelChatMessage.User(codeWithLineNumbers)
];
// make sure the model is available
if (model) {
// send the messages array to the model and get the response
let chatResponse = await model.sendRequest(
messages,
{},
new vscode.CancellationTokenSource().token
);
// handle chat response
await parseChatResponse(chatResponse, textEditor);
}
}
);
チャットの応答はフラグメントとして届きます。これらのフラグメントには通常、単一の単語が含まれますが、句読点のみの場合もあります。応答がストリーミングされるにつれて注釈を表示するためには、完全な注釈が得られるまで待ってから表示したいと考えます。モデルに応答を返すように指示した方法のため、閉じ}
が表示されたときに完全な注釈が得られたことがわかります。その後、注釈を解析してエディターに表示できます。
extension.ts
ファイルの getVisibleCodeWithLineNumbers
メソッドの上に、不足している parseChatResponse
関数を追加します。
async function parseChatResponse(
chatResponse: vscode.LanguageModelChatResponse,
textEditor: vscode.TextEditor
) {
let accumulatedResponse = '';
for await (const fragment of chatResponse.text) {
accumulatedResponse += fragment;
// if the fragment is a }, we can try to parse the whole line
if (fragment.includes('}')) {
try {
const annotation = JSON.parse(accumulatedResponse);
applyDecoration(textEditor, annotation.line, annotation.suggestion);
// reset the accumulator for the next line
accumulatedResponse = '';
} catch (e) {
// do nothing
}
}
}
}
最後に、実際に注釈を表示するためのメソッドが必要です。VS Codeではこれらを「デコレーション」と呼びます。extension.ts
ファイルの parseChatResponse
メソッドの上に次のメソッドを追加します。
function applyDecoration(editor: vscode.TextEditor, line: number, suggestion: string) {
const decorationType = vscode.window.createTextEditorDecorationType({
after: {
contentText: ` ${suggestion.substring(0, 25) + '...'}`,
color: 'grey'
}
});
// get the end of the line with the specified line number
const lineLength = editor.document.lineAt(line - 1).text.length;
const range = new vscode.Range(
new vscode.Position(line - 1, lineLength),
new vscode.Position(line - 1, lineLength)
);
const decoration = { range: range, hoverMessage: suggestion };
vscode.window.activeTextEditor?.setDecorations(decorationType, [decoration]);
}
このメソッドは、モデルから解析された注釈を受け取り、それを使用してデコレーションを作成します。これはまず、デコレーションの外観を指定する TextEditorDecorationType
を作成することによって行われます。この場合、単に灰色の注釈を追加し、25文字に切り詰めます。ユーザーがメッセージにホバーしたときに、完全なメッセージを表示します。
次に、デコレーションが表示される場所を設定します。注釈で指定された行番号の行末に表示されるようにする必要があります。
最後に、アクティブなテキストエディターにデコレーションを設定します。これにより、エディターに注釈が表示されます。
拡張機能がまだ実行されている場合は、デバッグバーから緑色の矢印を選択して再起動してください。デバッグセッションを閉じた場合は、F5キーを押して拡張機能を実行します。開いた新しいVS Codeウィンドウインスタンスでコードファイルを開きます。コマンドパレットから「Toggle Tutor Annotations」を選択すると、エディターにコード注釈が表示されるはずです。
エディターのタイトルバーにボタンを追加する
コマンドパレット以外の場所からコマンドを呼び出すことができます。今回のケースでは、ユーザーが簡単に注釈を切り替えられるように、現在のタブの最上部にボタンを追加できます。
これを行うには、package.json
の "contributes" 部分を次のように変更します
"contributes": {
"commands": [
{
"command": "code-tutor.annotate",
"title": "Toggle Tutor Annotations",
"icon": "$(comment)"
}
],
"menus": {
"editor/title": [
{
"command": "code-tutor.annotate",
"group": "navigation"
}
]
}
}
これにより、エディターのタイトルバーのナビゲーション領域(右側)にボタンが表示されます。「アイコン」はプロダクトアイコンリファレンスから取得されます。
拡張機能がまだ実行されていない場合は、緑色の矢印で再起動するか、F5キーを押してください。これで、「Toggle Tutor Annotations」コマンドをトリガーするコメントアイコンが表示されるはずです。
次のステップ
このチュートリアルでは、言語モデルAPIを使用してAIをエディターに統合するVS Code拡張機能を作成する方法を学びました。VS Code拡張機能APIを使用して現在のタブからコードを取得し、カスタムプロンプトと共にモデルに送信し、その後モデルの結果を解析してデコレーターを使用してエディターに直接表示しました。
次に、Code Tutor拡張機能を拡張してチャット参加者を含めることもできます。これにより、ユーザーはGitHub Copilotチャットインターフェースを介して拡張機能と直接対話できるようになります。また、VS Codeの全範囲のAPIを探索して、エディターでカスタムAIエクスペリエンスを構築する新しい方法を見つけることもできます。
このチュートリアルの完全なソースコードは、vscode-extensions-sampleリポジトリにあります。