用 OpenAI GPT-3 建立一個 Discord 聊天機器人

前言

這一篇將會來介紹如何使用 OpenAI GPT-3 來建立一個 Discord 聊天機器人,並且讓它可以回應我們的訊息,而且你會發現 Discord 非常適合拿來串 OpenAI。

本篇將不再說明如何申請 OpenAI Key,若有需要的話,可詳見這一篇文章

事前說明

這一篇將不會有過多的著墨說明 Node.js、Git、GitHub、npm 等等的基礎知識,因為如果包含這些的話這一篇內容可能會多。

但是如果你對於 npm 與 Git 指令不熟悉的話,這邊我倒是可以提供自己先前的一些文章給予參考

當然結尾我也會附上範例程式碼的 GitHub 連結,到時候我也會針對 README 進行一些說明,方便你可以快速運作一個 Discord 機器人。

Discord 準備動作

首先第一個步驟是我們要先去建立一個 Discord 機器人,首先請你先到 Discord Developer Portal 頁面點選 Application

Application

然後點選 「New Application」

New Application

接著會要求你輸入這個機器人的名稱,這邊我們就叫做「OpenAI GPT-3」吧

OpenAI GPT-3

請注意,這個應用程式的名稱不是機器人的名稱,除此之外到時候這個應用程式名稱也會變成 Discord 伺服器的權限名稱,所以請記得要取一個好的名稱。

進到「OpenAI GPT-3」應用程式頁面後,我們要先去建立一個機器人,點選左邊的「Bot」

Bot

然後再點右邊「Add Bot」

Add Bot

當你按下「Add Bot」後,Discord 會要求你確認是否要建立一個機器人,點選「Yes, do it!」

Yes, do it!

接著下方就會出現你的機器人,這邊請你找到「Token」的欄位,並且按一下「Reset Token」

Reset Token

這時候你會取得一串 Token,這串 Token 就是你的機器人的 Token,請記得要把它記下來,因為之後我們會用到它。

請不要隨意將你的機器人 Token 分享給別人,因為這樣會讓別人可以控制你的機器人。

除此之外,這邊 Username 將會是你的機器人的名稱,在最後的範例程式碼,我是把它叫做「妙麗·格蘭傑」就是了。

接著這邊有一件事情很重要,麻煩滾到下方一點的地方找到「Privileged Gateway Intents」,然後把「Message Content Intent」打開,這樣才能讓機器人可以接收到訊息唷!

接著呢?我們要去把這個機器人加入到我們的 Discord 伺服器中,點選左邊的「OAuth2」,然後再選「URL Generator」的方式(比較簡單)

URL Generator

進入後,請在左邊的「SCOPES」中勾選「bot」,然後在「BOT PERMISSIONS」中勾選「Send Messages」

Send Messages

由於我們只是要讓機器人可以回應我們的訊息,所以我們只勾選「Send Messages」,如果你想要讓機器人可以做更多事情,可以再斟酌一下要勾選哪些權限。

接著下方你應該就會看到一塊「Generated URL」的區塊,將那一串 URL 複製下來並貼到瀏覽器的網址列上,然後按下 Enter,基本上你應該就會看到選擇要將機器人加入到哪個伺服器的畫面,選擇好你的伺服器後就大膽的把它加入吧!

Add to Bot

Add to Bot

這樣子你的機器人就會進入到你的伺服器了,那麼到這邊為止,我們基本的 Discord 機器人算是建立完成了。

建立專案環境

首先請你先建立一個資料夾,例如…叫做「discord-bot」,然後當然就是初始化這個專案,所以我這邊就快速貼上相關的指令:

1
2
3
mkdir discord-bot
cd discord-bot
npm init -y

接著我們要來安裝一些我們會使用到的套件

  • dotenv
    • 用來讀取 .env 檔案
  • discord.js
    • 用來操作 Discord 的 套件
  • openai
    • 用來操作 OpenAI GPT-3 的 套件
1
npm install dotenv discord.js openai

接著在專案根目錄建立一個「index.js」的檔案,因為接下來我們會開始在這一個檔案內撰寫我們的機器人程式碼。

你也可以考慮加入 ESLint 管理你的程式碼品質唷

撰寫程式碼

終於到了撰寫程式碼的環節了,那麼這邊可能不會每一行都說明,但會盡可能的簡單一點,讓你可以盡可能做出一個自己的機器人。

Discord 機器人

首先,先讓我們撰寫 Discord 的機器人程式碼,那麼在官方 npm 上面,我們可以找到 Discord.js 的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });

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

client.on('interactionCreate', async interaction => {
if (!interaction.isChatInputCommand()) return;

if (interaction.commandName === 'ping') {
await interaction.reply('Pong!');
}
});

client.login(TOKEN);

這一段程式碼是官方提供的範例程式碼,這邊我們來看一下這些程式碼的意思

  • Client:這是我們要使用的 Discord 的 Client,我們可以透過這個 Client 來操作 Discord 的 API
  • GatewayIntentBits:這是我們要使用的 Discord 的 GatewayIntent,我們可以透過這個 GatewayIntent 來決定我們的機器人要做什麼事情
  • client.on('ready', () => { ... }):這是我們要在機器人啟動後要做的事情,例如:在 console 上面印出「Logged in as ${client.user.tag}!」,告知我們機器人已經啟動了。
  • client.login(TOKEN):這是我們要登入我們的機器人,這邊的 TOKEN 就是我們在 Discord Developer Portal 上面產生的機器人的 Token

其中比較需要留意的是 client.on('Event', () => { ... }),這邊的 Event 就是我們要監聽的事件,例如前面的 ready 就代表著我們要監聽機器人啟動的事件。

當然 Discord 的事件非常的多,比較常見的事件有:

  • ready:當機器人啟動時觸發
  • interactionCreate:當使用者呼叫機器人的指令時觸發
  • messageCreate:當使用者發送訊息時觸發

這三個算是最常見的事件,只是我們這邊只會使用到 readymessageCreate,然後還有一個地方必須調整,也就是 intents,這邊的 intents 就是我們要決定我們的機器人要做什麼事情,而我們這邊會需要 GuildsMessagesMessageContent 這兩個權限,所以我們就先把官方的程式碼修改成以下並貼入到 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const { Client, GatewayIntentBits } = require('discord.js');

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

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

client.on('messageCreate', async message => {
console.log('使用者發送的訊息:', message.content);
});

client.login(process.env.DISCORD_TOKEN);

接著你就可以在終端機輸入 node index.js 來啟動你的機器人了,當你看到 Logged in as ${client.user.tag}! 這樣的訊息時,代表你的機器人已經啟動了,接著你的 Discord 上面發送訊息時,同時終端機也會印出你發送的訊息。

Discord

到目前為止,其實我們已經將一個 Discord 給完成了,接下來,當然就是準備來接 OpenAI 的 API 啦!

串接 OpenAI 的 API

接著,請你在根目錄下面建立一個資料夾叫做 services,然後在 services 資料夾下面建立一個檔案叫做 openai.js,接著我們將會在 openai.js 中貼入以下的程式碼,下方這一段程式碼我有點稍微封裝一下,方便可以直接匯出使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const { Configuration, OpenAIApi } = require('openai');

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

const openAI = new OpenAIApi(configuration);

async function openAiMessage(prompt) {
try {
const { data } = await openAI.createCompletion({
model: 'text-davinci-003',
prompt,
max_tokens: 100,
});
const [choices] = data.choices;

return choices.text.trim(); // OpenAI 回傳時都會有許多空白,因此要使用 trim 去除前後空白
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
return `對不起,我發生 **${error.response.status} - ${error.response.statusText}** 錯誤,所以不知道該怎麼回你 QQ`;
}
}

module.exports = {
openAiMessage,
};

openAI 有提供多個 model 可以選擇,分別是

  • text-davinci-003
  • text-curie-001
  • text-babbage-001
  • text-ada-001

其中目前比較厲害的就是 text-davinci-003,因此我們這邊也會使用這個 model,每一個 model 都有不同的特性,你可以在這裡找到它的介紹。

接著回到 index.js,我們將會在 messageCreate 事件中,將訊息傳入到 openai.js 中,並且將回傳的訊息回傳給使用者,所以我們將會修改成以下的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const { Client, GatewayIntentBits } = require('discord.js');
const { openAiMessage } = require('./services/openai');

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

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

client.on('messageCreate', async message => {
console.log('使用者發送的訊息:', message.content);
if (message.author.bot) return; // 避免機器人互相回覆
const response = await openAiMessage(message.content);
message.reply(response);
});

client.login(process.env.DISCORD_TOKEN);

那麼這邊比較需要這邊多了一個新語法,也就是 message.reply(response);,這個語法簡單來講就是針對使用者的訊息去「回覆」,接著你就可以試著在 Discord 中輸入一些訊息,然後你就會發現你的 Discord Bot 已經會回覆你一些訊息,而這些訊息就是 OpenAI 所回傳的訊息唷!

JOJO

JOJO

讓你的 Discord Bot 更聰明吧

到目前為止,其實我們已經完成了一個串接 OpenAI 的 Discord 聊天機器人,但是這個聊天機器人還是有一些缺點…

  • 好像有點笨
  • 無法前後文理解

那麼要解決這個問題的核心重點在於,要將先前的訊息(包含機器人回你的部分)在每次使用者發送訊息後疊加發送給 OpenAI,這樣 OpenAI 才能夠理解使用者的上下文。

但是這一段其實有一個很大的問題存在,也就是 OpenAI 的計算方式是一個字代表一個 Token,因此疊加上去是非常可怕的,詳情可以參考這位大大的分享,我就不多述了。

所以如果你還是期望你的 Discord Bot 能夠更聰明的話,這邊我提供幾個思路方向,第一種方式就是利用 Discord 的 Forum 論壇就可以解決這個問題

Forum

因為論壇就是以一個貼文作為一個主題貼文來集中討論,因此就可以針對該主題的內容撈全部上下文,接著就可以疊加送給 OpenAI 分析,這樣就可以解決上下文的問題,而且我們也可以去限制一則貼文內的總字數,進而限制 OpenAI 的計算量,這樣就可以避免 OpenAI 的計算量太大的問題發生。

但是 Discord 的 Forum 論壇我印象是必須為「社群」模式才能夠擁有的,因此另一種做法就是只撈特定數量的訊息,例如只撈前 10 筆訊息,只要超過就直接重新計算,這樣也可以解決上下文的問題。

只是我自己認為還是沒有 Forum 論壇模式的好就是了 xD

範例程式碼

最後這邊,我也提供一下我自己做的範例程式碼,你可以參考這一篇文章建立 Discord,然後搭配我提供的程式碼去建立屬於你自己的 Discord 聊天機器人唷。

GitHub:https://github.com/hsiangfeng/hermione

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ