本文章實作以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"]; = new OpenAIAPI(key); OpenAIAPI api <ChatMessage> messages = new List<ChatMessage>(); Listfor (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") { .Add(new ChatMessage(ChatMessageRole.System, content.ToString())); messages} if (role.ToString() == "user") { .Add(new ChatMessage(ChatMessageRole.User, content.ToString())); messages} if (role.ToString() == "assistant") { .Add(new ChatMessage(ChatMessageRole.Assistant, content.ToString())); messages} } } var result = await api.Chat.CreateChatCompletionAsync(new ChatRequest() { = Model.ChatGPTTurbo_1106, Model = 0.1, Temperature = 200, MaxTokens = 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"]; = new OpenAIAPI(key); OpenAIAPI api
用
JsonElement
的TryGetProperty
方法來透過key取得json中的內容,在進一步將對話的前後文List<JsonElement>,轉換成 chat completion 可以處理的格式 List<ChatMessage><ChatMessage> messages = new List<ChatMessage>(); Listfor (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") { .Add(new ChatMessage(ChatMessageRole.System, content.ToString())); messages} if (role.ToString() == "user") { .Add(new ChatMessage(ChatMessageRole.User, content.ToString())); messages} if (role.ToString() == "assistant") { .Add(new ChatMessage(ChatMessageRole.Assistant, content.ToString())); messages} } }
問題與前文的格式準備好了,只需要在呼叫
OpenAIAPI
中的 Completion相關的非同步方法,來呼叫OpenAI即可var result = await api.Chat.CreateChatCompletionAsync(new ChatRequest() { = Model.ChatGPTTurbo_1106, Model = 0.1, Temperature = 200, MaxTokens = 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 .UseCors(c => c.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod()); app.... .Run(); app
在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.js
const { defineConfig } = require('@vue/cli-service') .exports = defineConfig({ moduletranspileDependencies: 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 = [] .push(this.system_prompt) //Fristly, insert the system prompt submitted_messages//only remember recently 7 content let remember_num = 7 if (this.messages.length > remember_num) { for (let i = 0; i < remember_num; ++i) { + 1] = this.messages[this.messages.length - remember_num + i]; submitted_messages[i }else { } for (let i = 0; i < this.messages.length; ++i) { + 1] = this.messages[i] submitted_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個對話。
.push(this.system_prompt) //Fristly, insert the system prompt submitted_messages//only remember recently 7 content let remember_num = 7 if (this.messages.length > remember_num) { for (let i = 0; i < remember_num; ++i) { + 1] = this.messages[this.messages.length - remember_num + i]; submitted_messages[i }else { } for (let i = 0; i < this.messages.length; ++i) { + 1] = this.messages[i] submitted_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一致最後如果想要完整程式碼專案,可以到這裡參考