教程:使用语言模型 API 生成由 AI 支持的代码注释
在这个教程中,您将学习如何创建一个 VS Code 扩展,以构建一个基于 AI 的代码导师。您将使用语言模型 (LM) API 生成代码改进建议,并利用 VS Code 扩展 API 将其无缝集成到编辑器中,作为用户可以悬停以获取更多信息的内联注释。完成此教程后,您将了解如何在 VS Code 中实现自定义的 AI 功能。

先决条件
完成本教程需要以下工具和账户:
搭脚手架扩展
首先,使用 Yeoman 和 VS Code 扩展生成器来构建一个准备好开发的 TypeScript 或 JavaScript 项目。
npx --package yo --package generator-code -- yo code
选择以下选项以完成新的扩展向导...
# ? 您想创建什么类型的扩展? 新扩展 (TypeScript)
# ? 您的扩展名称是什么? Code Tutor
### 按
# ? 你的扩展的标识符是什么? code-tutor
# ? 你的扩展的描述是什么? 留空
# ? 初始化一个git仓库? 是
# ? 用webpack打包源代码? 否
# ? 使用哪个包管理器? npm
# ? 您是否想用Visual Studio Code打开新文件夹?用`code`打开
修改 package.json 文件以包含正确的命令
构建的项目包含一个单独的 "helloWorld" 命令在package.json文件。这个命令是当你安装扩展时在命令面板中显示的内容。
"贡献": {
"命令": [
{
"命令": "code-tutor.helloWorld",
"标题": "Hello World"
}
]
}
由于我们正在开发一个代码导师扩展,该扩展将为代码行添加注释,因此我们需要一个命令来允许用户切换这些注释的显示和隐藏。更新命令和标题属性:
"贡献": {
"命令": [
{
"命令": "code-tutor.annotate",
"标题": "切换导师注释"
}
]
}
虽然package.json定义了扩展的命令和用户界面元素,src/extension.ts文件是存放那些命令需要执行的代码的地方。
打开src/extension.ts文件并更改注册命令方法使其与命令物业在package.json文件。
常量 一次性 = vscode.命令.注册命令('code-tutor.annotate', () => {
按F5运行扩展。这将打开一个带有扩展安装的新的 VS Code 实例。按⇧⌘P(Windows, Linux Ctrl+Shift+P)打开命令面板,搜索“tutor”。你应该会看到“Tutor Annotations”命令。

如果你选择“导师注释”命令,你将看到一个“Hello World”通知消息。

实现"annotate"命令
为了使我们的Code Tutor注释功能正常工作,我们需要向它发送一些代码并要求它提供注释。我们将分三步进行:
- 获取当前用户打开标签页中的带行号的代码。
- 将该代码发送到语言模型API,并附上一个自定义提示,指示模型如何提供注释。
- 解析注释并在编辑器中显示。
步骤 1: 获取带行号的代码
要从当前标签页获取代码,我们需要一个用户打开的标签页的引用。我们可以通过修改来获取注册命令方法成为注册文本编辑器命令这两个命令之间的区别是,后者给我们一个用户打开的标签的引用,称为文本编辑器输入:.
常量 一次性 = vscode.命令.注册文本编辑器命令('code-tutor.annotate', 异步 (文本编辑器: vscode.文本编辑器) => {
现在我们可以使用文本编辑器获取“可编辑查看空间”的所有代码的参考。这是屏幕上可以看到的代码——它不包括在可编辑查看空间中显示的代码上方或下方的代码。
在以下方法直接上方添加以下方法导出函数 deactivation() { }底部的行扩展.ts文件。
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 = '';
// 从当前位置的行获取文本。
// 行号是基于0的,所以我们将它加1以使其基于1。
while (currentLine < endLine) {
code += `${currentLine + 1}: ${textEditor.document.lineAt(currentLine).text} \n`;
// 移动到下一行位置
currentLine++;
}
return code;
}
这段代码使用了可见范围TextEditor的属性获取当前在编辑器中可见的行的位置。然后从第一行位置开始,移动到最后一行位置,将每行代码和行号添加到字符串中。最后,返回包含所有可见代码和行号的字符串。
现在我们可以从这个方法调用代码导师.注释命令。修改命令的实现,使其看起来像这样:
常量 一次性 = vscode.命令.注册文本编辑器命令(
'code-tutor.annotate',
异步 (文本编辑器: vscode.文本编辑器) => {
// 从当前编辑器获取带行号的代码
const 带行号的代码 = 获取可见的带行号的代码(文本编辑器);
}
);
步骤 2:将代码和提示发送到语言模型 API
下一步是调用 GitHub Copilot 语言模型,并将用户的代码以及创建注释的说明一起发送给它。
要实现这一点,我们首先需要指定要使用的聊天模型。我们在此选择4o,因为它是我们正在构建的交互类型的一个快速且强大的模型。
常量 一次性 = vscode.命令.注册文本编辑器命令(
'code-tutor.annotate',
异步 (文本编辑器: vscode.文本编辑器) => {
// 从当前编辑器获取带行号的代码
常量 带行号的代码 = 获取可见的带行号的代码(文本编辑器);
// 选择40聊天模型
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": "我认为你应该使用 for 循环而不是 while 循环。for 循环更简洁且更容易阅读。" }{ "line": 12, "suggestion": "我认为你应该使用 for 循环而不是 while 循环。for 循环更简洁且更容易阅读。" }
`;
这是一个特殊的提示,用于指示语言模型如何生成注释。它还包括模型应该如何格式化其响应的示例。这些示例(也称为“多示例”)是我们定义响应格式的方式,以便我们可以解析并将其显示为注释。
我们将消息以数组的形式传递给模型。这个数组可以包含你喜欢的任何数量的消息。在我们的例子中,它包含提示以及带有行号的用户代码。
常量 一次性 = vscode.命令.注册文本编辑器命令(
'code-tutor.annotate',
异步 (文本编辑器: vscode.文本编辑器) => {
// 从当前编辑器获取带行号的代码
常量 带行号的代码 = 获取可见的带行号的代码(文本编辑器);
// 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 的情况。然后我们将消息发送到模型。
常量 一次性 = vscode.命令.注册文本编辑器命令(
'code-tutor.annotate',
异步 (文本编辑器: vscode.文本编辑器) => {
// 从当前编辑器获取带行号的代码
常量 带行号的代码 = 获取可见的带行号的代码(文本编辑器);
// 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);
}
}
);
聊天回复以片段形式到来。这些片段通常包含单个单词,但有时只包含标点符号。为了在回复流进来时显示注释,我们希望在显示之前等到我们有一个完整的注释。由于我们已经指示模型返回其响应的方式,我们知道当我们看到闭合}我们有一个完整的注释。然后我们可以解析注释并在编辑器中显示。
添加缺失的解析聊天响应函数在上方获取带行号的可见代码方法在扩展.ts文件。
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) {
// 不做任何事
}
}
}
}
我们需要一种最后的方法来实际显示注释。VS Code 称这些为“装饰”。在以下方法上方添加以下方法解析聊天响应方法在扩展.ts文件。
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 装饰 = { 范围: 范围, 悬停信息: 建议 };
```plaintext
vscode.Windows.活动文本编辑器?.设置装饰(装饰类型, [装饰]);
}
```
该方法接受我们从模型中解析的注释,并使用它来创建一个装饰。这是通过首先创建一个文本编辑器装饰类型指定装饰的外观。在这种情况下,我们只是添加了一个灰色的注释,并将其截断为25个字符。当用户悬停在消息上时,我们将显示完整的消息。
然后我们设置装饰应该出现的位置。我们需要它出现在注释中指定的行号上,并且在行尾。
最后,我们在活动文本编辑器上设置装饰,这会使得注释出现在编辑器中。
如果你的扩展仍在运行,请通过选择调试栏中的绿色箭头重新启动它。如果你关闭了调试会话,请按F5来运行扩展。在新打开的 VS Code Windows实例中打开一个代码文件。当你从命令面板中选择“切换导师注释”时,你应该在编辑器中看到代码注释出现。

在编辑器标题栏添加一个按钮
您可以启用命令在命令面板以外的地方调用。在我们的例子中,我们可以在当前标签页的顶部添加一个按钮,允许用户轻松切换注释。
要这样做,修改“contributes”部分的package.json如下:
"contributes": {
"commands": [
{
"command": "code-tutor.annotate",
"title": "Toggle Tutor Annotations",
"icon": "$(comment)"
}
],
"menus": {
"editor/title": [
{
"command": "code-tutor.annotate",
"group": "navigation"
}
]
}
}
这会在编辑器标题栏的导航区域(右侧)显示一个按钮。图标来自产品图标参考。
用绿色箭头重新启动你的扩展或按F5,如果扩展尚未运行。你应该现在看到一个评论图标,这将触发“切换导师注释”命令。

下一步
在这个教程中,你学习了如何创建一个 VS Code 扩展,将 AI 集成到编辑器中,使用语言模型 API。你使用 VS Code 扩展 API 从当前标签页获取代码,用自定义提示将其发送到模型,然后使用装饰器在编辑器中解析和显示模型结果。
接下来,您可以将您的 Code Tutor 扩展扩展到包括一个聊天参与者,这将允许用户通过 GitHub Copilot 聊天界面直接与您的扩展进行交互。您还可以探索 VS Code 中的全部 API 功能,以探索在您的编辑器中构建自定义 AI 体验的新方法。
你可以在这个教程的完整源代码在vscode-extensions-sample 仓库中找到。