チュートリアル: Language Model API を使用して AI 駆動のコードアノテーションを生成する

このチュートリアルでは、AI 駆動の「コードチューター (Code Tutor)」を構築するための VS Code 拡張機能の作成方法を学びます。Language Model (LM) API を使用してコードを改善するための提案を生成し、VS Code 拡張機能 API を活用して、ユーザーがホバーして詳細を確認できるインラインアノテーションとしてエディターにシームレスに統合します。このチュートリアルを完了すると、VS Code でカスタム AI 機能を実装する方法が習得できます。

VS Code displaying custom annotations from GitHub Copilot as annotations

前提条件

このチュートリアルを完了するには、以下のツールとアカウントが必要です。

拡張機能の雛形を作成する

まず、Yeoman と VS Code Extension Generator を使用して、開発可能な 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」コマンドが 1 つ含まれています。このコマンドが、拡張機能をインストールしたときにコマンドパレットに表示されます。

"contributes": {
  "commands": [
      {
      "command": "code-tutor.helloWorld",
      "title": "Hello World"
      }
  ]
}

コードの行にアノテーションを追加するコードチューター拡張機能を作成するため、ユーザーがこれらのアノテーションのオン/オフを切り替えられるコマンドが必要です。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」コマンドが表示されるはずです。

The "Toggle Tutor Annotations" command in the VS Code Command Palette

「Tutor Annotations」コマンドを選択すると、「Hello World」という通知メッセージが表示されます。

The message 'Hello World from Code Tutor' displayed in a notification

"annotate" コマンドを実装する

コードチューターのアノテーションを機能させるには、コードをモデルに送信し、アノテーションを提供するよう依頼する必要があります。これは 3 つのステップで行います。

  1. ユーザーが開いている現在のタブから、行番号付きのコードを取得する。
  2. そのコードを、アノテーションの提供方法をモデルに指示するカスタムプロンプトと共に Language Model API に送信する。
  3. アノテーションを解析し、エディターに表示する。

ステップ 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: コードとプロンプトを Language Model 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'
    });
  }
);

アノテーションを作成するようモデルに指示し、どのような形式で応答を希望するかを伝える「プロンプト」が必要です。imports のすぐ下に、以下のコードを追加します。

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
      }
    }
  }
}

最後に、実際にアノテーションを表示するためのメソッドが 1 つ必要です。VS Code ではこれらを「デコレーション (decorations)」と呼びます。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」を選択すると、エディターにコードアノテーションが表示されるはずです。

A code file with annotations from GitHub Copilot

エディターのタイトルバーにボタンを追加する

コマンドパレット以外の場所からもコマンドが呼び出せるようにできます。今回は、現在のタブの上部にボタンを追加し、ユーザーが簡単にアノテーションを切り替えられるようにします。

これを行うには、package.json の「contributes」部分を次のように変更します。

"contributes": {
  "commands": [
    {
      "command": "code-tutor.annotate",
      "title": "Toggle Tutor Annotations",
      "icon": "$(comment)"
    }
  ],
  "menus": {
    "editor/title": [
      {
        "command": "code-tutor.annotate",
        "group": "navigation"
      }
    ]
  }
}

これにより、エディタータイトルバーのナビゲーション領域(右側)にボタンが表示されます。「icon」は Product Icon Reference から指定します。

緑色の矢印で拡張機能を再起動するか、既に実行されていない場合は F5 を押します。これで、「Toggle Tutor Annotations」コマンドをトリガーするコメントアイコンが表示されるはずです。

A comment icon appears in the title bar of the active tab in VS Code

次のステップ

このチュートリアルでは、Language Model API を使用して AI をエディターに統合する VS Code 拡張機能の作成方法を学びました。VS Code 拡張機能 API を使用して現在のタブからコードを取得し、カスタムプロンプトと共にモデルに送信し、デコレーターを使用して結果を解析しエディター上に直接表示しました。

次に、あなたのコードチューター拡張機能を拡張して、チャット参加者 (Chat Participant) を含めることもできます。これにより、ユーザーは GitHub Copilot のチャットインターフェースを介して拡張機能と直接対話できるようになります。また、VS Code の API 一覧を参照して、エディター内にカスタム AI 体験を構築する新しい方法を探索してみてください。

このチュートリアルの完全なソースコードは、vscode-extensions-sample リポジトリで確認できます。

© . This site is unofficial and not affiliated with Microsoft.