Tutorial: KI-gestützte Code-Annotationen mit der Language Model API generieren
In diesem Tutorial lernst du, wie du eine VS Code-Erweiterung erstellst, um einen KI-gestützten Code-Tutor zu bauen. Du verwendest die Language Model (LM) API, um Vorschläge zur Verbesserung deines Codes zu generieren und die VS Code Extension APIs zu nutzen, um diese nahtlos im Editor als Inline-Annotationen zu integrieren, über die der Benutzer mit der Maus fahren kann, um weitere Informationen zu erhalten. Nach Abschluss dieses Tutorials weißt du, wie du benutzerdefinierte KI-Funktionen in VS Code implementierst.

Voraussetzungen
Für dieses Tutorial benötigst du folgende Werkzeuge und Konten
Gerüst für die Erweiterung erstellen
Verwende zuerst Yeoman und den VS Code Extension Generator, um ein TypeScript- oder JavaScript-Projekt zu erstellen, das für die Entwicklung bereit ist.
npx --package yo --package generator-code -- yo code
Wähle die folgenden Optionen aus, um den neuen Erweiterungs-Assistenten abzuschließen...
# ? 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`
Ändere die Datei package.json, um die richtigen Befehle einzufügen
Das erstellte Projekt enthält einen einzelnen "helloWorld"-Befehl in der Datei package.json. Dieser Befehl wird in der Befehlspalette angezeigt, wenn deine Erweiterung installiert ist.
"contributes": {
"commands": [
{
"command": "code-tutor.helloWorld",
"title": "Hello World"
}
]
}
Da wir eine Code-Tutor-Erweiterung entwickeln, die Zeilen mit Annotationen versieht, benötigen wir einen Befehl, mit dem der Benutzer diese Annotationen ein- und ausschalten kann. Aktualisiere die Eigenschaften command und title
"contributes": {
"commands": [
{
"command": "code-tutor.annotate",
"title": "Toggle Tutor Annotations"
}
]
}
Während die package.json die Befehle und UI-Elemente für eine Erweiterung definiert, ist die Datei src/extension.ts der Ort, an dem du den Code platzierst, der für diese Befehle ausgeführt werden soll.
Öffne die Datei src/extension.ts und ändere die Methode registerCommand, damit sie mit der Eigenschaft command in der Datei package.json übereinstimmt.
const disposable = vscode.commands.registerCommand('code-tutor.annotate', () => {
Führe die Erweiterung aus, indem du auf F5 drückst. Dies öffnet eine neue VS Code-Instanz mit der installierten Erweiterung. Öffne die Befehlspalette, indem du ⇧⌘P (Windows, Linux Ctrl+Shift+P) drückst und nach "tutor" suchst. Du solltest den Befehl "Tutor Annotations" sehen.

Wenn du den Befehl "Tutor Annotations" auswählst, siehst du eine "Hello World"-Benachrichtigung.

Implementiere den "annotate"-Befehl
Damit unsere Code-Tutor-Annotationen funktionieren, müssen wir ihm Code senden und ihn bitten, Annotationen bereitzustellen. Dies werden wir in drei Schritten tun:
- Hole den Code mit Zeilennummern aus dem aktuell geöffneten Tab des Benutzers.
- Sende diesen Code an die Language Model API zusammen mit einem benutzerdefinierten Prompt, der das Modell anweist, wie Annotationen bereitzustellen sind.
- Parse die Annotationen und zeige sie im Editor an.
Schritt 1: Code mit Zeilennummern abrufen
Um den Code aus dem aktuellen Tab zu erhalten, benötigen wir eine Referenz auf den vom Benutzer geöffneten Tab. Dies können wir erreichen, indem wir die Methode registerCommand zu einer registerTextEditorCommand ändern. Der Unterschied zwischen diesen beiden Befehlen besteht darin, dass letztere uns eine Referenz auf den vom Benutzer geöffneten Tab gibt, die TextEditor genannt wird.
const disposable = vscode.commands.registerTextEditorCommand('code-tutor.annotate', async (textEditor: vscode.TextEditor) => {
Nun können wir die textEditor-Referenz verwenden, um den gesamten Code im "sichtbaren Editorbereich" zu erhalten. Das ist der Code, der auf dem Bildschirm sichtbar ist – er enthält keinen Code, der oberhalb oder unterhalb des sichtbaren Editorbereichs liegt.
Füge die folgende Methode direkt über der Zeile export function deactivate() { } am Ende der Datei extension.ts hinzu.
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;
}
Dieser Code verwendet die Eigenschaft visibleRanges des TextEditors, um die Position der Zeilen zu ermitteln, die derzeit im Editor sichtbar sind. Er beginnt dann mit der ersten Zeilenposition und bewegt sich zur letzten Zeilenposition, wobei jede Codezeile zusammen mit der Zeilennummer zu einem String hinzugefügt wird. Schließlich gibt er den String zurück, der den gesamten sichtbaren Code mit Zeilennummern enthält.
Nun können wir diese Methode vom Befehl code-tutor.annotate aufrufen. Ändere die Implementierung des Befehls so, dass sie wie folgt aussieht:
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);
}
);
Schritt 2: Code und Prompt an die Language Model API senden
Der nächste Schritt ist der Aufruf des GitHub Copilot Language Models und das Senden des Benutzercodes zusammen mit Anweisungen zur Erstellung der Annotationen.
Dazu müssen wir zunächst festlegen, welches Chat-Modell wir verwenden möchten. Wir wählen hier 4o, da es ein schnelles und leistungsfähiges Modell für die Art der Interaktion ist, die wir aufbauen.
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'
});
}
);
Wir benötigen Anweisungen – oder einen "Prompt" –, die dem Modell mitteilen, Annotationen zu erstellen und in welchem Format wir die Antwort wünschen. Füge den folgenden Code am Anfang der Datei direkt unter den Importen hinzu.
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." }
`;
Dies ist ein spezieller Prompt, der das Language Model anweist, Annotationen zu generieren. Er enthält auch Beispiele dafür, wie das Modell seine Antwort formatieren soll. Diese Beispiele (auch "Multi-Shot" genannt) ermöglichen es uns, das Format der Antwort zu definieren, damit wir sie parsen und als Annotationen anzeigen können.
Wir übergeben Nachrichten in einem Array an das Modell. Dieses Array kann beliebig viele Nachrichten enthalten. In unserem Fall enthält es den Prompt gefolgt vom Benutzercode mit Zeilennummern.
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)
];
}
);
Um die Nachrichten an das Modell zu senden, müssen wir zuerst sicherstellen, dass das ausgewählte Modell verfügbar ist. Dies behandelt Fälle, in denen die Erweiterung nicht bereit ist oder der Benutzer nicht bei GitHub Copilot angemeldet ist. Dann senden wir die Nachrichten an das Modell.
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);
}
}
);
Chat-Antworten kommen als Fragmente an. Diese Fragmente enthalten normalerweise einzelne Wörter, manchmal aber auch nur Satzzeichen. Um Annotationen anzuzeigen, während die Antwort gestreamt wird, möchten wir warten, bis wir eine vollständige Annotation haben, bevor wir sie anzeigen. Aufgrund der Art und Weise, wie wir unser Modell angewiesen haben, seine Antwort zu geben, wissen wir, dass wir eine vollständige Annotation haben, wenn wir ein schließendes } sehen. Wir können dann die Annotation parsen und sie im Editor anzeigen.
Füge die fehlende Funktion parseChatResponse oberhalb der Methode getVisibleCodeWithLineNumbers in der Datei extension.ts hinzu.
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
}
}
}
}
Wir benötigen eine letzte Methode, um die Annotationen tatsächlich anzuzeigen. VS Code nennt diese "Dekorationen". Füge die folgende Methode oberhalb der Methode parseChatResponse in der Datei extension.ts hinzu.
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]);
}
Diese Methode nimmt unsere geparste Annotation vom Modell und verwendet sie, um eine Dekoration zu erstellen. Dies geschieht, indem zuerst ein TextEditorDecorationType erstellt wird, der das Aussehen der Dekoration festlegt. In diesem Fall fügen wir nur eine graue Annotation hinzu und kürzen sie auf 25 Zeichen. Die vollständige Nachricht wird angezeigt, wenn der Benutzer mit der Maus über die Nachricht fährt.
Wir legen dann fest, wo die Dekoration erscheinen soll. Wir benötigen sie auf der Zeilennummer, die in der Annotation angegeben wurde, und am Ende der Zeile.
Schließlich legen wir die Dekoration auf dem aktiven Texteditor fest, was dazu führt, dass die Annotation im Editor erscheint.
Wenn deine Erweiterung noch läuft, starte sie neu, indem du den grünen Pfeil in der Debug-Leiste auswählst. Wenn du die Debug-Sitzung geschlossen hast, drücke F5, um die Erweiterung auszuführen. Öffne eine Codedatei im neuen VS Code-Fenster, das sich öffnet. Wenn du "Toggle Tutor Annotations" aus der Befehlspalette auswählst, solltest du die Code-Annotationen im Editor sehen.

Füge einen Button zur Editor-Titelleiste hinzu
Du kannst deine Befehle auch von anderen Orten als der Befehlspalette aus aufrufen. In unserem Fall können wir einen Button oben im aktuellen Tab hinzufügen, mit dem der Benutzer die Annotationen einfach ein- und ausschalten kann.
Ändere dazu den "contributes"-Teil der package.json wie folgt:
"contributes": {
"commands": [
{
"command": "code-tutor.annotate",
"title": "Toggle Tutor Annotations",
"icon": "$(comment)"
}
],
"menus": {
"editor/title": [
{
"command": "code-tutor.annotate",
"group": "navigation"
}
]
}
}
Dies bewirkt, dass ein Button im Navigationsbereich (rechte Seite) der Editor-Titelleiste erscheint. Das "icon" stammt aus der Produkt-Icon-Referenz.
Starte deine Erweiterung mit dem grünen Pfeil neu oder drücke F5, wenn die Erweiterung noch nicht läuft. Du solltest nun ein Kommentar-Icon sehen, das den Befehl "Toggle Tutor Annotations" auslöst.

Nächste Schritte
In diesem Tutorial hast du gelernt, wie du eine VS Code-Erweiterung erstellst, die KI mit der Language Model API in den Editor integriert. Du hast die VS Code Extension API verwendet, um den Code aus dem aktuellen Tab abzurufen, ihn mit einem benutzerdefinierten Prompt an das Modell gesendet und dann das Modellergebnis geparst und direkt im Editor mithilfe von Dekorationen angezeigt.
Als Nächstes kannst du deine Code Tutor-Erweiterung erweitern, um einen Chat-Teilnehmer einzuschließen, damit Benutzer direkt über die GitHub Copilot Chat-Oberfläche mit deiner Erweiterung interagieren können. Du kannst auch die volle Bandbreite der VS Code APIs erkunden, um neue Wege für die Erstellung benutzerdefinierter KI-Erlebnisse in deinem Editor zu entdecken.
Den vollständigen Quellcode für dieses Tutorial findest du im Repository mit VS Code-Erweiterungsbeispielen.