教學 筆記 機器學習 程式設計 服務架設
用 GPT-3 開發 Discord 聊天機器人
更新於6個月前
本文將會教你如何用 GPT-3 的 API 製作一個 Discord 聊天機器人。
前言
首先,本文介紹的 GPT-3 API 並不是各位在 Open AI 網站的 ChatGPT 示範網頁,而是透過 OpenAI的API 存取 GPT-3 模型,效果會不會跟 ChatGPT 一樣好取決你設定的參數,也可以對模型進行 Fine-tuning (微調),如果想要直接使用 ChatGPT 製作,可以參考非官方 Node.js 套件 chatgpt 存取 ChatGPT API (非官方)。
GPT-3
GPT-3 是一個由人工智慧公司 OpenAI 所訓練的自然語言模型,其中的強大之處也不需要我多做介紹了,自己去 ChatGPT 試過就知道了。 另外要提一下,GPT-3 沒有提供模型檔案下載,只能透過 API 存取,這方面的話 OpenAI 一點也不 Open,不過也是情有可原啦,畢竟訓練這個模型的過程中花了好幾億,怎麼可能說給就給呢。API 的部分也有存取的次數限制,免費試用的情況下只有 18美元給你用,每 1000 tokens 是 0.02元,你可以把 tokens 想成是文字的數量,每 1000 tokens 大約是 750 字,詳細的資訊可以參考 OpenAI 的 Pricing 頁面。
備註:在 Transformers 模型,token 指的是被模型從完整文章或句子切割成的小單位,基本上可以是一個字元,也可以是一個詞組,取決於在進行 tokenizer 時模型的輸出。
前置準備
Node.js
本次要使用 Node.js 來進行開發,請先至 Node.js 官網下載並安裝 Node.js,安裝完後用 npm 建立一個新專案。 先創一個資料夾,我這裡命名為 gpt-3-chatbot-discord,然後打開終端機,輸入:
# cd /path/to/gpt-3-chatbot-discord
# npm init
然後他會要求你輸入一些設定,我這裡全部都讓他用預設的 (全部 enter)
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (gpt-3-chatbot-discord)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
之後會讓你確認 package.json 的內容,沒問題的話直接按 enter
About to write to G:\Documents\t-gpt-3-chatbot\gpt-3-chatbot-discord\package.json:

{
  "name": "gpt-3-chatbot-discord",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)
你會發現資料夾裡多了一個 package.json,就代表專案初始化成功。 然後安裝一些要用到的套件,輸入:
# npm i discord.js
# npm i openai
另外我個人習慣用 .env 把一些敏感的資料 (如 API 的金鑰等) 跟程式碼隔離開來,一方面方便修改這些資料,另一方面就是如果要把檔案傳給別人使用時可以避免把敏感資料傳送給別人,如果對方沒有程式設計基礎的話,也可以請他直接修改 .env 檔案裡面的設定,改成他自己的金鑰之類的。 因此再多安裝一個 dotenv 套件:
# npm i dotenv
Discord API
接下來要創建一個 Discord 的 APP,前往 Discord Developer Portal 的 Applications 頁面,點擊 New Application 創建一個新的 APP。
輸入好名稱後再把底下同意事項打勾,點擊 Create。 進到 Applications 的畫面後,點進左邊 Bot 的標籤,然後按 Add Bot,之後再按下 Yes, do it!
然後再按名字底下的 Reset Token,Yes, do it! 後,你就會得到一串 Token。
在專案的資料夾下創建一個 .env 檔案,然後新增一行:
DISCORD_TOKEN=<你的Discord Token>
把 <你的 Discord Token> 替換成剛剛產生的 Token。 然後再到 Bot 頁面,有一個選項是 MESSAGE CONTENT INTENT,把它打開來,不然會讀不到訊息。
設定完記得 Save Changes。
將機器人加入 Discord 伺服器
創好 APP 後,再進入 Developer Portal 左邊 OAuth2,URL Generator 的地方,SCOPES 的地方勾選 bot,BOT PERMISSIONS 勾選 GENERAL PERMISSIONS 的 Read Messages/View Channels、TEXT PERMISSIONS 的 Send Messages,詳細的權限說明可以參考文件的 Permissions 頁面。
然後底下會有一串網址,直接貼在瀏覽器打開。
選擇要新增進去的伺服器,要注意的是,你只能把機器人新增到你有管理權限的伺服器中,如果沒有的話要請伺服器管理員幫你加入。這邊會建議各位自己創一個伺服器來測試。 選好後點選繼續,會讓你確認要給機器人哪些權限,這邊都不要管,直接按授權。
看到已授權之後就代表加入成功了,可以到伺服器中看有沒有出現你的機器人。
OpenAI API
接下來換 OpenAI 的部分,進入 OpenAI 的 API Keys 頁面,點擊 +Create new secret key 新增一個 API Key。 建立完成後會跳出一個視窗給你複製 API Key,一樣把它加入 .env 中。
OPENAI_API_KEY=<你的OpenAI API Key>
開始開發
先做一些設定
由於我們才都使用預設的設定建立專案,所以 Node.js 的 main 檔案是 index.js,當然你也可以自己取名字,不過不要忘了進去 package.json 把 main 的選想改成你設的檔名。
"main": "<你設定的檔名>.js",
接下來新建 index.js 檔案,先引入要用的套件:
const dotenv = require('dotenv');
const { Configuration, OpenAIApi } = require('openai');
const { Client, GatewayIntentBits } = require('discord.js');
然後引入 .env 設定
dotenv.config();
Discord Client
我們可以用以下程式碼新建一個 Discord Client:
const DiscordClient = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});
其中 intents 的部分是要使用到什麼權限,這邊原本是要用數字表示,但是 Discord 有提供 GatewayIntentBits 可以幫你產生,只要呼叫相對應的變數即可,詳細的資訊可以到文件的 Gateway Intents 查看。 然後可以用 DiscordClient.on 來確認是否登入成功
DiscordClient.on('ready', () => {
    console.log(`Logged in as ${DiscordClient.user.tag}!`);
});
我們可以建立一個簡單的 Ping-Pong 功能
DiscordClient.on('messageCreate', (message) => {
    if (message.content === 'ping') {
        message.reply('Pong!');
    }
});
最後再登入 Bot
DiscordClient.login(process.env.DISCORD_TOKEN);
process.env.DISCORD_TOKEN 是呼叫前面我們放在 .env 的設定,如果你不想要用 dotenv 的話,可以直接在這邊輸入你的 token
DiscordClient.login('<你的Discord Token>');
這邊是這個段落的完整程式碼:
const dotenv = require('dotenv');
const { Configuration, OpenAIApi } = require('openai');
const { Client, GatewayIntentBits } = require('discord.js');

dotenv.config();

const DiscordClient = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});

DiscordClient.on('ready', () => {
    console.log(`Logged in as ${DiscordClient.user.tag}!`);
});

DiscordClient.on('messageCreate', (message) => {
    if (message.content === 'ping') {
        message.reply('Pong!');
    }
});

DiscordClient.login(process.env.DISCORD_TOKEN);
然後到 package.json,在 scripts 中新增一段
"start": "node index.js",
之後打開終端機,輸入
# npm start
如果看到 Logged in as <bot的名字> 的話,就代表登入成功,接下來可以到 Discord 中發送 ping。
如果機器人有回應你 Pong! 的話,就代表你到這邊都沒有問題了。
OpenAI API
然後我們要開始把 GPT-3 整合進 Bot 中。 先建立 OpenAIApi 的相關設定。
const configuration = new Configuration({
    apiKey: process.env.OPENAI_API_KEY,
});
這裡的 process.env.OPENAI_API_KEY 與前面的 DISCORD_TOKEN 同理 然後再建立 OpenAIApi 的 Client:
const openai = new OpenAIApi(configuration);
可以建立一個變數來儲存之前的聊天內容,因為 GPT-3 是透過前後文來推論下一句話的
let prompt = `你是一個聊天機器人Chatbot,你必須陪同一個人聊天\n
Chatbot: 你好,我是一個聊天機器人\n`;
這裡先預置一些內容,讓 GPT-3 了解自己的身分,當然,這裡算是蠻重要的部分,因為這會決定 GPT-3 要成為一個什麼樣的角色。 你也可以讓他帶入角色的性格,比如說:你可以寫「你是一個彪悍大叔,名叫大壯,你現在在跟你的小姪子們聊天」,這樣 GPT-3 就會帶入性格來產生對話內容,當然這部分就看你的 prompt 和參數下得好不好,就跟你用 Stable Diffusion 來產生圖畫是差不多的道理了。 第二行的話是先預置一段由機器人 Chatbot 發出的訊息,寫了名字會讓 GPT-3 更好的認識自己。 接下來修改回覆訊息的部分
DiscordClient.on('messageCreate', async (message) => {
    // 接下來有關回覆訊息的程式都要寫在這裡
});
當有人傳送訊息時,都會進來這段 Listener,所以要把相關的程式碼寫在裡面,另外這裡也把回應函數變成 async (非同步),因為待會呼叫 OpenAIApi 的時候,由於它是非同步的函數,為了在回傳訊息時可以先等待 API 回傳結果,因此會加上 await (同步),讓程式等 API 回傳資料後再繼續執行。 先把傳訊人是 bot 的訊息給排除掉
if (message.author.bot) return;
這樣如果訊息的發送者身分為 bot 的話,會直接離開這個函數,這樣會避免機器人讀到自己的訊息後開始秀逗。 之後,把用戶傳入的訊息加入 prompt 中:
prompt += `${message.author.username}: ${message.content}\n`;
這邊是以「傳訊者名字: 訊息」的格式來新增訊息的,這是為了讓 GPT 能夠辨識誰是誰,以及知道你的名字。 另外,這邊的訊息是直接加進 prompt 中,與前面的資料結合在一起,就如同前面所介紹的,GPT-3 是透過前後文來推測下一句話。 接下來用 OpenAIApi 發送請求獲得推測的訊息。
const gptResponse = await openai.createCompletion({
    model: 'text-davinci-002',
    prompt: prompt,
    max_tokens: 100,
    temperature: 0.9,
});
這邊要注意的是,要在呼叫 openai.createCompletion() 前加上 await,不然程式就不會等到資料回傳就繼續進行下去,這樣會導致後面的程式出錯,當然,你也可以用 promise 的寫法來寫,不過這裡為了方便展示就以這樣為主。 其中的 model 是 text-davinci-002,這是 GPT-3 的模型代號之一,是 GPT-3 系列中最完整的版本,也有其他版本的模型可以選擇,詳細資訊可以看官方文件的 GPT-3 頁面。 prompt 是要用於預測的資料,就是前面程式中的 prompt 變數。 max_tokens 是限制 GPT-3 最多能回覆多少字,如果你有試玩過 ChatGPT 的話,你應該會經常遇到它講話講一半的狀況,就是這個原因,如果出現斷句的話可以嘗試將 max_tokens 設高;但也不是越高越好,因為請求有最大的 Tokens 上限,如果使用 text-davinci-002 的話上限是 4000 tokens,超過上限的話程式會出錯,待會會提到。 temperature 是關於採樣的相關參數,詳細的說明不是本文的重點,我也沒有信心可以講得比網路上其他大佬好,關於這個參數你可以直接拿「sampling temperature」去餵狗。另外這個參數在做 bot 的情況下官方文件中有提到「Try 0.9 for more creative applications, and 0 (argmax sampling) for ones with a well-defined answer.」,也就是說,bot 的話可以直接使用 0.9 作為採樣溫度,因為 bot 需要更多的創造力,而 0 的話就是更精準的答案。不過還是要經過實驗才能得到最佳的結果,所以盡量嘗試不同的值,找到最好的。 另外,temperature 有一個替代品叫做 top_p,官方建議這兩種要設定其中一種,但是不建議都設。 其他關於參數的相關資料可以參考官方文件的 Completion 頁面,請盡量嘗試不同參數,尋找最好的設定值。 得到回傳結果之後,回傳得到的結果
message.reply(gptResponse.data.choices[0].text.split(':')[1]);
gptResponse.data.choices 是得到的預測結果,因為它是一個陣列,所以我們直接取它第一個 [0],然後陣列中的元素是 dict,text 欄位即是它的預測內容。 另外,這裡用 split(‘:’) 將句子用「:」分割成名字和訊息內容的部分,[1] 則是他的訊息部分。 之後再往 prompt 中加入預測結果作為 prompt 的一部分,這樣才會讓 GPT 了解前面的對話經過。
prompt += `Chatbot: ${gptResponse.data.choices[0].text.split(':')[1]}\n`;
以下是到目前為止的完整程式碼
const dotenv = require('dotenv');
const { Configuration, OpenAIApi } = require('openai');
const { Client, GatewayIntentBits } = require('discord.js');

dotenv.config();

const DiscordClient = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
    ],
});

DiscordClient.on('ready', () => {
    console.log(`Logged in as ${DiscordClient.user.tag}!`);
});

const configuration = new Configuration({
    apiKey: process.env.OPENAI_API_KEY,
});

const openai = new OpenAIApi(configuration);

let prompt = `你是一個聊天機器人Chatbot,你必須陪同一個人聊天\n
Chatbot: 你好,我是一個聊天機器人\n`;

DiscordClient.on('messageCreate', async (message) => {
    if (message.author.bot) return;
    prompt += `${message.author.username}: ${message.content}\n`;
    const gptResponse = await openai.createCompletion({
        model: 'text-davinci-002',
        prompt: prompt,
        max_tokens: 100,
        temperature: 0.9,
    });
    message.reply(gptResponse.data.choices[0].text.split(':')[1]);
    prompt += `Chatbot: ${gptResponse.data.choices[0].text.split(':')[1]}\n`;
});

DiscordClient.login(process.env.DISCORD_TOKEN);
接下來重啟程式,就可以到 Discord 試玩它了。
這是我經過調整參數得到的結果,雖然對話上沒什麼問題,但是由於它對時事的了解不是即時的,所以不要想它會給你什麼很正確的解答。 不過其實這也是取決於參數的設定,我嘗試改用 top_p: 0.3 之後,它終於知道現在 (2022 年 12 月 23 日)世界盃還沒結束了。
關於 Prompt
前面有提到,每次送出的請求中,Prompt 的上限是 4000 tokens,如果超過的話會請求失敗,最簡單的解法就是新增一個指令,比如說 clear,讓使用者手動清除,不過聊得太開心的話也會忘記這件事情,如果要真正上線運作的話,這方法肯定行不通。也可以套一層 try,讓它出錯時先把 prompt 重設 (又或是取最後幾行),然後再重送,這也是其中一種解法,也能保證對話的持續性。 我這邊還有一個我自己玩的機器人的作法提供給大家:就是讓使用者想要機器人回答時要標註它才會回應,然後幫每個使用者設一個專屬的 prompt 變數,這樣一方面機器人就不會每句話都回,一方面也不會混雜一堆人的訊息導致文本預測的不準確。不過就它的能力其實群聊也不是不行啦。
結論
GPT-3 無疑是目前最好的文本預測模型了,當時出來時我們系上的教授還很興奮的跟我們分享,網路上也就 ChatGPT 的出現造成了很大的騷動,這個場面絕對不比 Stable Diffusion 還不盛大。 除了對參數進行調整之外,還能對模型進行 Fine-tuning (微調),如果想了解這部分可以到官方文件中的 Fine-tuning 頁面查看。通過 Fine-tuning 可以讓模型更貼近使用情境。 當然,我更希望 OpenAI 能夠將 GPT-3 的模型開源釋出,這樣就有更多花樣可以玩了。 最後,希望這篇文章能夠幫助你完整你理想中的 Discord 機器人囉,也希望能夠讓你更了解 GPT-3 這套模型。
秉持著我都會遇事別人怎麼不會的精神分享解決事情的經過。
sakkyoi © 2024
Roses are Red, Violets are Blue Unexpected '{' on line 32.
本站使用 Tocas-UI 進行設計
問題回報
如果您在本站的使用過程中遇到任何困難或是錯誤,歡迎透過 email 向我們回報。如果是關於文章內容的相關討論,敬請使用留言板直接進行討論,這將幫助其他使用者了解您遇到的問題。
使用者分析
我們使用自己的分析伺服器收集並分析您在本站的行為,這樣能確保您的相關資料不受其他第三方伺服器濫用。您可以選擇將本功能關閉以保護您的隱私。但這會阻止我們透過您的行為來改善您和其他使用者的體驗。