本文章實作以OpenAI 的chat GPT模型為基礎,以VueJS做為前端的聊天機器人,會詳細的介紹,包括申請Open AI API,還有後端、前端等等,最後會做出可根據上下文進行聊天對話的聊天機器人
- 環境
- 後端
- NET Core 6.0
- 前端
- Node.js
- VueJS前端
- 後端
壹、預先準備
首先最重要的是,要取得Open AI的API Key,注意這部分需要綁定信用卡,需要先預付一筆金額
到OpenAI網頁來申請 https://openai.com/,點選右上角註冊登入

登入之後,選擇右邊API,接著選的
API keys

選擇
Create new secret key
輸入Key的名稱,這裡取作
MyTestKey,在選擇Project 名稱,這裡用預設的Default Project,最後選擇Create secret key
最後在這頁,把API KEY了複製下來,在這個頁面之後,將無法再看到的API KEY,所以如果忘記了,就要再新增一個API KEY了

貳、後端
- 這裡我們用ASP .NET Core WebAPI 做為後端,首先要安裝Visual Studio ,而且必須安裝網頁開發相關的套件
建立ASP .NET Core WebAPI專案與secret key設定
建立新的專案

選擇
ASP .NET Core Web API,注意: 若沒安裝ASP .NET網頁開發套件,則不會顯示ASP .NET Core Web API專案的選項
名稱取作
chatbot_backend_api,建立

先安裝OpenAI相關套件,打開
套件管理器主控台
在下面的
套件管理器主控台輸入指令安裝 OpenAI 的相關套件NuGet\Install-Package OpenAI -Version 1.11.0
這裡我們要設定專案中的
appsettings.json檔案,先將openai 的 secret key填到<your_openai_apikey>的位置appsettings.json{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "openai_key": "<your_openai_apikey>", "AllowedHosts": "*" }
建立API Controller
在右邊的Controller資料夾上面右鍵,新增控制器

選擇左側
API,然後選API控制器-空白
控制器取名為
OpenAIchatController.cs
空的API控制器,如下圖所示,現在開始一步一步撰寫這個API controller

OpenAIchatController.cs的程式碼如下using Microsoft.AspNetCore.Mvc; using System.Text.Json; using OpenAI_API; using OpenAI_API.Models; using OpenAI_API.Chat; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 namespace chatbot_backend_api.Controllers { [Route("api/[controller]")] [ApiController] public class OpenAIchatController : ControllerBase { private readonly IConfiguration Configuration; public OpenAIchatController(IConfiguration configuration) { Configuration = configuration; } // POST api/<OpenAIchatController> [HttpPost] public async Task<string> Post([FromBody] List<JsonElement> message_json) { string key = Configuration["openai_key"]; OpenAIAPI api = new OpenAIAPI(key); List<ChatMessage> messages = new List<ChatMessage>(); for (int i = 0; i < message_json.Count; ++i) { if (message_json[i].TryGetProperty("role", out var role) && message_json[i].TryGetProperty("content", out var content)) { // Process role and content if (role.ToString() == "system") { messages.Add(new ChatMessage(ChatMessageRole.System, content.ToString())); } if (role.ToString() == "user") { messages.Add(new ChatMessage(ChatMessageRole.User, content.ToString())); } if (role.ToString() == "assistant") { messages.Add(new ChatMessage(ChatMessageRole.Assistant, content.ToString())); } } } var result = await api.Chat.CreateChatCompletionAsync(new ChatRequest() { Model = Model.ChatGPTTurbo_1106, Temperature = 0.1, MaxTokens = 200, Messages = messages }); var reply = result.Choices[0].Message; return reply.TextContent.Trim(); } } }以下是
OpenAIchatController.cs的詳細說明:再controller中新增 readonly 的 field
Configuration與建構子,當產生OpenAIchatController的時候,自動將組態設定檔Load到Configuration中private readonly IConfiguration Configuration; public OpenAIchatController(IConfiguration configuration) { Configuration = configuration; }新增對應POST方法,處理使用者從網頁前端POST過來的資料。以下是controller函數的簽名,注意這個函數的簽名有
async,是一個非同步的函數[HttpPost] public async Task<string> Post([FromBody] List<JsonElement> message_json) { return ""; }我們預期網頁前端POST過來的資料,是一個JSON的 list,與OpenAI的 Chat Completions API的格式相同,格式如下:
[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Who won the world series in 2020?"}, {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."}, {"role": "user", "content": "Where was it played?"} ]角色 "role" 包含
system: 通常讓GPT進行角色扮演設定user: 使用者的提問assistant: GPT的回應
內容 "content"就是該對話的字串內容
一開始傳入的json list命名為
message_json,透過專案中的appsettings.json檔案來提取 secret key,注意這裡的"openai_key"與appsettings.json中設定的變數名稱對應。接著在利用string變數key來產生一個可以進行對話的OpenAIAPI物件
string key = Configuration["openai_key"]; OpenAIAPI api = new OpenAIAPI(key);用
JsonElement的TryGetProperty方法來透過key取得json中的內容,在進一步將對話的前後文List<JsonElement>,轉換成 chat completion 可以處理的格式 List<ChatMessage>List<ChatMessage> messages = new List<ChatMessage>(); for (int i = 0; i < message_json.Count; ++i) { if (message_json[i].TryGetProperty("role", out var role) && message_json[i].TryGetProperty("content", out var content)) { // Process role and content if (role.ToString() == "system") { messages.Add(new ChatMessage(ChatMessageRole.System, content.ToString())); } if (role.ToString() == "user") { messages.Add(new ChatMessage(ChatMessageRole.User, content.ToString())); } if (role.ToString() == "assistant") { messages.Add(new ChatMessage(ChatMessageRole.Assistant, content.ToString())); } } }問題與前文的格式準備好了,只需要在呼叫
OpenAIAPI中的 Completion相關的非同步方法,來呼叫OpenAI即可var result = await api.Chat.CreateChatCompletionAsync(new ChatRequest() { Model = Model.ChatGPTTurbo_1106, Temperature = 0.1, MaxTokens = 200, Messages = messages }); var reply = result.Choices[0].Message;一些 completion API相關的參數如下:
Model.ChatGPTTurbo_1106: 模型的編號Temperature = 0.1: 模型的溫度,溫度越低產生的對話越死板MaxTokens = 200,: 這裡我們限制最大回應的Tokens為200,希望API不要回應太長的文本,控制token的使用量
最後再將回應整理好,去除空白之後回傳
return reply.TextContent.Trim();
另外還要設定與許跨域存取API,這樣才能在VueJS中呼叫在不同網域的後端(ASP .NET Core WebAPI)
在
Program.cs中新增跨來源資源共用 Cross-Origin Resource Sharing (CORS)var builder = WebApplication.CreateBuilder(args); .... var app = builder.Build(); // Enable CORS app.UseCors(c => c.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod()); .... app.Run();
在Swagger上面測試後端 ASP .NET Core WebAPI
按下
F5執行,會跳出瀏覽器導向Swagger api的頁面
在Swagger上測試API,並且貼上測試問對話的json的list格式
[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "Who won the world series in 2020?"}, {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."}, {"role": "user", "content": "Where was it played?"} ]

收到API的回應,成功

參、前端
這裡我們VueJS做為前端,首先要安裝Visual Studio Code與Node ,並且從網路上的vue chat bot專案抓下來修改
首先下載 Node.js https://nodejs.org/en
下載之後無腦下一步安裝即可
將AIChatbot 的 VUE專案跑起來
下載AIChatbot專案,然後解壓縮到一個你記得的路徑

用Visual Studio Code 開啟 aichat 資料夾(去你剛剛解壓縮的路徑),注意我們只用專案的前端部分來修改

這裡要選Yes, I trust the authors

接著打開Terminal panel

在Visual Studio Code的Terminal中,在
aichat路徑下使用npm install指令,安裝必要的vue開發相關的套件
需要花一些時間來安裝,安裝成功畫面如下

用
npm run serve來執行專案,第一次編譯執行會花一些時間
執行成功後,可以用瀏覽器訪問頁面
http://localhost:8080/

注意這邊輸入問題送出後會報錯,因為我們並沒有把後端API對接起來,這個AI Chatbot只有前端而已

修改AIChatbot專案,符合我們設計的ASP .NET Core WebAPI專案
首先先設定proxy
在
vue.config.js中設定proxy使VueJS前端,可以訪問不同源的 ASP .NET WebAPI注意這裡的7136port 需要試你ASP .NET WebAPI 執行時候的port來修改,會因為不同電腦執行而不同
vue.config.jsconst { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, devServer: { proxy: { '/api': { target: 'https://localhost:7136 ', changeOrigin: true, pathRewrite: { '^/api': "https://localhost:7136/api/OpenAIchat" } } } } })
首先安裝 icon相關的套件,這裡要先用ctrl+c把正在執行的vue前端關掉,再安裝套件
npm install primeicons
修改
src/components/ChatBox.vue先在
<script>tag中引入primeicons套件<script> import axios from 'axios'; import 'primeicons/primeicons.css' ... </script>再
<script>中重新設計vue中 data的資料 lien45 附近currentMessage: 目前送出的問題processing: 表示是否正在等待API回應system_prompt: 表示用來讓Chat GPT 做角色扮演的系統訊息messages: 表示目前要顯示在chat box中的對話紀錄,格式就是json的list格式
export default { name: 'ChatBox', data() { return { currentMessage: '', system_prompt: { "role": "system", "content": "you are a helpful assiatant." }, processing: false, messages: [], }; },修改sumbit 按鈕,更換成icon顯示,大約在Line 15附近
<div class="inputContainer"> <input v-model="currentMessage" type="text" class="messageInput" :disabled="processing" placeholder="Please enter your question" @keyup.enter="sendMessage(currentMessage)" /> <button :disabled="processing" @click="sendMessage(currentMessage)" class="askButton"> <i class="pi pi-send" style="font-size: 1rem"></i> </button> </div>修改完成後,網頁的送出按鈕會變化

重新設計
sendMessage的非同步方法:async sendMessage(currentMessage) { if (currentMessage.trim().length === 0) { return } this.processing = true //update messages this.messages.push({ role: 'user', content: currentMessage, }); var submitted_messages = [] submitted_messages.push(this.system_prompt) //Fristly, insert the system prompt //only remember recently 7 content let remember_num = 7 if (this.messages.length > remember_num) { for (let i = 0; i < remember_num; ++i) { submitted_messages[i + 1] = this.messages[this.messages.length - remember_num + i]; } } else { for (let i = 0; i < this.messages.length; ++i) { submitted_messages[i + 1] = this.messages[i] } } this.messages.push({ role: 'assistant', content: '', }); await axios .post('https://localhost:7136/api/OpenAIchat', submitted_messages ) .then((response) => { this.messages[this.messages.length - 1].content = response.data // Access the 'data' property of the response object }).catch(function (error) { console.log(error.toJSON()); }); this.currentMessage = '' this.processing = false }以下是
sendMessage函數的詳細說明:當目前的詢問問題
currentMessage是空白的時候,就直接retrun不浪費API的使用if (currentMessage.trim().length === 0) { return }設定成正在呼叫API的狀態,不讓使用者在繼續送出問題 disable button 與 input text
this.processing = true然後在messages插入使用者目前詢問的問題
//update messages this.messages.push({ role: 'user', content: currentMessage, });submitted_messages這個list中存放,要post到後端ASP .NET Core WebAPI的所有對話紀錄。這裡用一個迴圈做copy的動作,只取最近的7筆問答,也就是3組QA還有一個Q,不要忘記了最前面還要加上system prompt,也就是角色扮演的指令 ,總計有8個對話。
submitted_messages.push(this.system_prompt) //Fristly, insert the system prompt //only remember recently 7 content let remember_num = 7 if (this.messages.length > remember_num) { for (let i = 0; i < remember_num; ++i) { submitted_messages[i + 1] = this.messages[this.messages.length - remember_num + i]; } } else { for (let i = 0; i < this.messages.length; ++i) { submitted_messages[i + 1] = this.messages[i] } }最後在真正POST上去之前,先在要顯示在對話窗的
messages中插入一個空白的回應this.messages.push({ role: 'assistant', content: '', });這裡用axios做非同步的api呼叫,等待完成API呼叫後,將真正的Chat GPT的回應,寫入
messages的最後一的回應,也就是chat GPT的回應訊息,如果API呼叫失敗,我們的例外處理也會catch到錯誤的情況,在log中打印錯誤另外再提醒一下
https://localhost:7136/api/OpenAIchat這個 port 7136 每個人自己執行的時候號碼會不同,需將 port 7136 改成ASP .NET Core WebAPI執行時的urlawait axios .post('https://localhost:7136/api/OpenAIchat', submitted_messages ) .then((response) => { this.messages[this.messages.length - 1].content = response.data // Access the 'data' property of the response object }).catch(function (error) { console.log(error.toJSON()); }); this.currentMessage = '' this.processing = false
最後我們再來修改,顯示對話的部分,大約在Line 7 附近,把
<template v-for="(message, index) in messages" :key="index">與</template>中間都全部覆蓋掉<div :class="message.role == 'user'? 'messageFromUser' : 'messageFromChatGpt'"> <div :class="(message.role == 'user' || message.content.trim().length !== 0)? 'userMessageWrapper' : 'chatGptMessageWrapper'"> <div :class="message.role == 'user' ? 'userMessageContent' : 'chatGptMessageContent'"> {{ message.content }} </div> </div> </div>修改完成後,你可以重新用
npm run serve執行Vue.js專案,然後在網頁上面與Ai Chat Bot聊天測試
到這裡你已經做出一個最簡單的聊天機器人了
如果有錯誤,可以檢查一下
sendMessage方法 與vue.config.js中的URL是否正確,是否與跑起來的ASP .NET WebAPI 的URL一致最後如果想要完整程式碼專案,可以到這裡參考
