教學課程:使用 Chat API 建立程式碼教學聊天參與者

在本教學課程中,您將學習如何建立一個與 GitHub Copilot Chat 體驗整合的 Visual Studio Code 擴充功能。您將使用 Chat 擴充功能 API 來貢獻一個聊天參與者。您的參與者將是一位程式碼導師,可以針對程式設計概念提供解釋與練習範例。

先決條件

完成本教學課程,您需要準備下列工具與帳號

步驟 1:設定專案

首先,使用 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 Anatomy 文件了解更多資訊。簡單概述如下:

  • extension.ts 是您擴充功能的主要進入點,其中包含聊天參與者的邏輯。
  • package.json 包含擴充功能的後設資料(metadata),例如參與者的名稱與描述。

刪除 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
    }
    ]
}

此程式碼會註冊一個具有下列屬性的聊天參與者

  • 唯一識別碼 (Unique ID) chat-tutorial.code-tutor,將在程式碼中被參照
  • 全名 Code Tutor,將顯示在參與者回應的標題區域中
  • 名稱 tutor,將用於在聊天視窗中以 @tutor 參照此聊天參與者
  • 描述 "What can I teach you?",將顯示在聊天輸入欄位中作為預留位置文字

最後,設定 isSticky: true,這會在使用者開始與參與者互動後,自動在聊天輸入欄位中前置參與者的名稱。

步驟 3:製作提示詞 (Prompt)

現在參與者已註冊完成,您可以開始實作程式碼導師的邏輯。在 extension.ts 檔案中,您將為請求定義一個提示詞。

製作良好的提示詞是從參與者獲得最佳回應的關鍵。請查看 這篇文章 以取得提示詞工程 (prompt engineering) 的技巧。

您的程式碼導師應模擬現實生活中的導師,引導學生理解概念,而不是直接提供答案。此外,導師應保持專注於主題,並避免回答與程式設計無關的問題。

請思考下列兩個提示詞。哪一個比較能達到指定行為?

  1. You are a helpful code tutor. Your job is to teach the user with simple descriptions and sample code of the concept.

  2. 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.

第二個提示詞更具體,並為參與者提供了清晰的回應方向。請將此提示詞新增至 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:實作請求處理常式 (Request Handler)

現在提示詞已選定,您需要實作請求處理常式。這將負責處理使用者的聊天請求。您將定義請求處理常式、執行處理請求的邏輯,並將回應回傳給使用者。

首先,定義處理常式

// 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 送出請求,這會使用當前選定的模型送出請求。最後,將回應以串流 (stream) 方式傳送給使用者。

// 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 來呼叫您的參與者!

Participant in Chat pane

輸入您想要學習的內容來測試它。您應該會看到一個提供概念概述的回應!

如果您輸入相關訊息以繼續對話,您會發現參與者並未根據您的對話提供後續回應。這是因為我們目前的參與者僅送出了使用者當前的訊息,而非參與者的訊息紀錄。

在下方的螢幕截圖中,導師正確地回應了堆疊 (stacks) 的起始解釋。然而,在後續對話中,它並不理解使用者正在繼續對話以查看 Python 中堆疊的實作,因此它反而給出了一個關於 Python 的通用回應。

Participant with no message history

步驟 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 中堆疊的實作。

Participant with message history

步驟 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,這將啟動您的聊天參與者,並且您可以獲得互動式練習來練習程式設計!

Participant with a slash command

後續步驟

恭喜!您已成功建立了一個可以針對程式設計概念提供解釋與練習範例的聊天參與者。您可以透過微調提示詞、新增更多斜線指令 (slash commands),或利用 Language Model API 等其他 API 來進一步擴充您的參與者。準備好後,您也可以將您的擴充功能發佈到 Visual Studio Code Marketplace

您可以在 vscode-extensions-sample 儲存庫中找到本教學課程的完整原始程式碼。

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