教程:使用聊天API构建一个代码教程聊天参与者

在本教程中,您将学习如何创建一个与GitHub Copilot Chat体验集成的Visual Studio Code扩展。您将使用Chat扩展API来贡献一个聊天参与者。您的参与者将是一个代码导师,可以为编程概念提供解释和示例练习。

先决条件

完成本教程,您需要以下工具和账户:

第一步:设置你的项目

首先,使用Yeoman和VS Code扩展生成器生成扩展项目。

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`

一旦你的扩展项目生成,你将主要处理两个文件:extension.tspackage.json,你可以在扩展结构文档中了解更多信息。以下是一个快速概述:

  • extension.ts 是您扩展的主要入口点,并包含您的聊天参与者的逻辑。
  • package.json 包含您的扩展的元数据,例如参与者的名称和描述。

删除extension.tsactivate()方法中自动生成的代码。这是你将放置我们聊天参与者逻辑的地方。

步骤2:注册聊天参与者

package.json文件中,将自动生成的contributes部分替换为以下内容:

"contributes":{
    "chatParticipants": [
    {
        "id": "chat-tutorial.code-tutor",
        "fullName": "Code Tutor",
        "name": "tutor",
        "description": "What can I teach you?",
        "isSticky": true
    }
    ]
}

此代码注册了一个聊天参与者,具有以下属性:

  • 唯一ID chat-tutorial.code-tutor,将在代码中引用
  • 全名 Code Tutor,将会显示在您的参与者的响应标题区域
  • 名称 tutor,将在聊天视图中用作 @tutor 来引用聊天参与者
  • 描述 "我可以教你什么?",这将作为占位符文本显示在聊天输入字段中

最后,设置isSticky: true将在用户开始与参与者互动后,自动在聊天输入字段前添加参与者名称。

步骤3:设计提示

现在参与者已经注册,你可以开始实现代码导师的逻辑了。在extension.ts文件中,你将定义一个请求的提示。

制作一个好的提示是从参与者那里获得最佳响应的关键。查看这篇文章以获取关于提示工程的技巧。

您的代码导师应该模仿现实世界中的导师,通过引导学生理解概念而不是直接提供答案。此外,导师应保持专注于主题,避免回答非编程问题。

考虑以下两个提示。哪一个更有可能产生指定的行为?

  1. 你是一个有用的代码导师。你的工作是用简单的描述和示例代码来教授用户这个概念。

  2. 你是一个有用的代码导师。你的工作是通过简单的描述和示例代码来教授用户概念。通过一系列消息提供概念的引导性概述。不要直接给用户答案,而是引导他们自己找到答案。如果用户提出非编程问题,礼貌地拒绝回答。

第二个提示更为具体,并为参与者提供了明确的回应方向。将此提示添加到extension.ts文件中。

const BASE_PROMPT =
  'You are a helpful code tutor. Your job is to teach the user with simple descriptions and sample code of the concept. Respond with a guided overview of the concept in a series of messages. Do not give the user the answer directly, but guide them to find the answer themselves. If the user asks a non-programming question, politely decline to respond.';

步骤4:实现请求处理器

现在已选择提示,您需要实现请求处理程序。这将处理用户的聊天请求。您将定义请求处理程序,执行处理请求的逻辑,并向用户返回响应。

首先,定义处理程序:

// define a chat handler
const handler: vscode.ChatRequestHandler = async (
  request: vscode.ChatRequest,
  context: vscode.ChatContext,
  stream: vscode.ChatResponseStream,
  token: vscode.CancellationToken
) => {
  return;
};

在这个处理程序的主体中,初始化提示和一个带有提示的messages数组。然后,发送用户在聊天框中输入的内容。你可以通过request.prompt访问这个内容。

使用request.model.sendRequest发送请求,这将使用当前选择的模型发送请求。最后,将响应流式传输给用户。

// define a chat handler
const handler: vscode.ChatRequestHandler = async (
  request: vscode.ChatRequest,
  context: vscode.ChatContext,
  stream: vscode.ChatResponseStream,
  token: vscode.CancellationToken
) => {
  // initialize the prompt
  let prompt = BASE_PROMPT;

  // initialize the messages array with the prompt
  const messages = [vscode.LanguageModelChatMessage.User(prompt)];

  // add in the user's message
  messages.push(vscode.LanguageModelChatMessage.User(request.prompt));

  // send the request
  const chatResponse = await request.model.sendRequest(messages, {}, token);

  // stream the response
  for await (const fragment of chatResponse.text) {
    stream.markdown(fragment);
  }

  return;
};

步骤5:创建聊天参与者

一旦处理程序被实现,最后一步是通过使用Chat扩展API中的createChatParticipant方法来创建聊天参与者。确保使用与package.json中相同的ID。

您应通过为其添加图标来进一步自定义您的参与者。这将在与参与者互动时显示在聊天视图中。

// define a chat handler
const handler: vscode.ChatRequestHandler = async (
  request: vscode.ChatRequest,
  context: vscode.ChatContext,
  stream: vscode.ChatResponseStream,
  token: vscode.CancellationToken
) => {
  // initialize the prompt
  let prompt = BASE_PROMPT;

  // initialize the messages array with the prompt
  const messages = [vscode.LanguageModelChatMessage.User(prompt)];

  // add in the user's message
  messages.push(vscode.LanguageModelChatMessage.User(request.prompt));

  // send the request
  const chatResponse = await request.model.sendRequest(messages, {}, token);

  // stream the response
  for await (const fragment of chatResponse.text) {
    stream.markdown(fragment);
  }

  return;
};

// create participant
const tutor = vscode.chat.createChatParticipant('chat-tutorial.code-tutor', handler);

// add icon to participant
tutor.iconPath = vscode.Uri.joinPath(context.extensionUri, 'tutor.jpeg');

步骤6:运行代码

你现在可以尝试你的聊天参与者了! 按下 F5 来运行代码。一个新的 VS Code 窗口将会打开,里面是你的聊天参与者。

在Copilot Chat面板中,您现在可以通过输入@tutor来调用您的参与者!

聊天窗格中的参与者

通过输入你想了解的内容来测试它。你应该会看到一个为你提供概念概览的响应!

如果您输入相关消息以继续对话,您会注意到参与者不会根据您的对话给出后续响应。这是因为我们当前的参与者仅发送用户的当前消息,而不发送参与者的消息历史记录。

在下面的截图中,导师正确地回应了关于栈的初步解释。然而,在后续的对话中,它没有理解用户正在继续对话以查看Python中栈的实现,因此它给出了一个关于Python的通用回应。

没有消息历史的参与者

步骤7:添加消息历史以获取更多上下文

Copilot Chat 的最大价值之一在于能够通过多次消息迭代来获得最佳响应。为此,您需要将参与者的消息历史记录发送到聊天请求中。您可以通过 context.history 访问此历史记录。

你需要检索该历史记录并将其添加到messages数组中。你需要在request.prompt被添加之前完成此操作。

// define a chat handler
const handler: vscode.ChatRequestHandler = async (
  request: vscode.ChatRequest,
  context: vscode.ChatContext,
  stream: vscode.ChatResponseStream,
  token: vscode.CancellationToken
) => {
  // initialize the prompt
  let prompt = BASE_PROMPT;

  // initialize the messages array with the prompt
  const messages = [vscode.LanguageModelChatMessage.User(prompt)];

  // get all the previous participant messages
  const previousMessages = context.history.filter(
    h => h instanceof vscode.ChatResponseTurn
  );

  // add the previous messages to the messages array
  previousMessages.forEach(m => {
    let fullMessage = '';
    m.response.forEach(r => {
      const mdPart = r as vscode.ChatResponseMarkdownPart;
      fullMessage += mdPart.value.value;
    });
    messages.push(vscode.LanguageModelChatMessage.Assistant(fullMessage));
  });

  // add in the user's message
  messages.push(vscode.LanguageModelChatMessage.User(request.prompt));

  // send the request
  const chatResponse = await request.model.sendRequest(messages, {}, token);

  // stream the response
  for await (const fragment of chatResponse.text) {
    stream.markdown(fragment);
  }

  return;
};

现在当你运行代码时,你可以与参与者进行对话,并且包含之前消息的所有上下文!在下面的截图中,参与者正确地理解了用户请求查看Python中栈的实现。

带有消息历史的参与者

步骤8:添加命令

现在基本参与者已经实现,您可以通过添加命令来扩展它。命令是常见用户意图的简写表示,由/符号表示。扩展程序可以使用该命令来相应地提示语言模型。

添加一个命令来提示你的导师为某个概念提供练习题目会非常好。你需要在package.json文件中注册该命令,并在extension.ts中实现逻辑。你可以将该命令命名为exercise,这样可以通过输入/exercise来调用它。

package.json中,将commands属性添加到chatParticipants属性中。在这里,您将指定命令的名称和简要描述:

"contributes": {
    "chatParticipants": [
      {
        "id": "chat-tutorial.code-tutor",
        "fullName": "Code Tutor",
        "name": "tutor",
        "description": "What can I teach you?",
        "isSticky": true,
        "commands": [
          {
            "name": "exercise",
            "description": "Provide exercises to practice a concept."
          }
        ]
      }
    ]
  },

要实现从导师那里获取示例练习的逻辑,最简单的方法是更改发送到请求中的提示。创建一个新的提示,EXERCISES_PROMPT,要求参与者返回示例练习。以下是一个可能的示例:

const EXERCISES_PROMPT =
  'You are a helpful tutor. Your job is to teach the user with fun, simple exercises that they can complete in the editor. Your exercises should start simple and get more complex as the user progresses. Move one concept at a time, and do not move on to the next concept until the user provides the correct answer. Give hints in your exercises to help the user learn. If the user is stuck, you can provide the answer and explain why it is the answer. If the user asks a non-programming question, politely decline to respond.';

在请求处理程序中,您需要添加逻辑来检测用户是否引用了命令。您可以通过request.command属性来实现这一点。

如果命令被引用,将提示更新为新创建的 EXERCISES_PROMPT

// define a chat handler
const handler: vscode.ChatRequestHandler = async (
  request: vscode.ChatRequest,
  context: vscode.ChatContext,
  stream: vscode.ChatResponseStream,
  token: vscode.CancellationToken
) => {
  // initialize the prompt
  let prompt = BASE_PROMPT;

  if (request.command === 'exercise') {
    prompt = EXERCISES_PROMPT;
  }

  // initialize the messages array with the prompt
  const messages = [vscode.LanguageModelChatMessage.User(prompt)];

  // get all the previous participant messages
  const previousMessages = context.history.filter(
    h => h instanceof vscode.ChatResponseTurn
  );

  // add the previous messages to the messages array
  previousMessages.forEach(m => {
    let fullMessage = '';
    m.response.forEach(r => {
      const mdPart = r as vscode.ChatResponseMarkdownPart;
      fullMessage += mdPart.value.value;
    });
    messages.push(vscode.LanguageModelChatMessage.Assistant(fullMessage));
  });

  // add in the user's message
  messages.push(vscode.LanguageModelChatMessage.User(request.prompt));

  // send the request
  const chatResponse = await request.model.sendRequest(messages, {}, token);

  // stream the response
  for await (const fragment of chatResponse.text) {
    stream.markdown(fragment);
  }

  return;
};

这就是需要添加的全部内容!获取消息历史记录、发送请求和流式传输请求的其余逻辑都保持不变。

现在你可以输入/exercise,这将调出你的聊天参与者,你可以获得互动练习来练习编码!

带有斜杠命令的参与者

下一步

恭喜!您已成功创建了一个可以为编程概念提供解释和示例练习的聊天参与者。您可以通过微调提示、添加更多斜杠命令或利用其他API(如语言模型API)来进一步扩展您的参与者。一旦准备就绪,您还可以将您的扩展发布到Visual Studio Code Marketplace

您可以在vscode-extensions-sample 仓库中找到本教程的完整源代码。