用 Node.js 建立你的第一個 LINE Bot 聊天機器人以 OpenAI GPT-3 與 GPT-3.5 為例

Robot

前言

Danger
雖然主軸介紹是以 GPT-3 為主,但是後面有補充如何轉換以及提供 GPT-3.5 的範例程式碼,如果你想要使用 GPT-3.5 的話,可以參考後面的範例程式碼。

最近 OpenAI 的 ChatGPT 非常的火紅,剛好這個 OpenAI 是有提供 API 可以串接的,雖然是以 GPT-3 為主軸,但是後面我也有分享如何轉換到 ChatGPT API,所以這一篇我就來介紹如何使用 Node.js 基於 OpenAI 的 API 來建立一個 Line 聊天機器人吧!

Note
請注意,該篇文章不會特別針對每一行程式碼去說明,因為光是申請流程其實就滿繁瑣的。
除此之外,如果你不想看到最後才拿到範例程式碼的話,這邊也提前提供給你參考
本篇範例程式碼

前置動作

首先這邊有幾個前置準備動作要先完成,否則後面的步驟都是免談的唷!

Note
這一篇是在 2022 年 12 月 10 日寫的,所以後續未來如果畫面有變動的話,會建議請以當下的畫面為準。

申請 OpenAI API

首先,請你先進入 OpenAI 的網站註冊會員,這邊你可以選擇第三方服務帳號(如:Google、Microsoft)或是 Email 帳號註冊

註冊會員

註冊完後,你就可以開始申請 API 了~

如果你本身有帳號的話,那就直接登入 OpenAI 吧。

登入後,你應該會看到這個畫面

Welcome to OpenAI

接著找到你的頭像點它,找到「View API keys」

View API keys

找到後你會看到「API keys」的申請畫面,點一下下方的「Create New Secret key」,這樣你就拿到串接 OpenAI 的 key,請先把它記下來,後面會用到。

OpenAI Key

Danger
API key generated 是非常重要的,因此請不要隨意提供給他人,以免造成不必要的損失。

那麼我們申請這個是 API Key 要幹嘛呢?主要是稍後後面會串接 OpenAI 所提供的 API 來使用 GPT,如果你還不知道什麼是 GPT 的話,你可以試著玩一下 ChatGPT,簡單來講 ChatGPT 比 GPT-3 更厲害更聰明的聊天機器人,它可以做到的事情非常多,例如像是…

  • 請它規劃日本旅遊行程
  • 寫一篇行銷文案
  • 幫忙找出程式碼錯誤
  • 程式碼解題

…等等,非常多

雖然目前 ChatGPT 還沒有開放,但是我們可以改串它的 GPT-3 來玩玩,因此我們申請這個 API Key 就是用來串接 GPT-3 的~

Note
OpenAI 本身有提供免費的額度,三個月 18 美金,除此之外,OpenAI 的計算方式會因你使用的 model 不同而有不同的計算方式,例如像是「Davinci」的計算方式是 $0.0200(美金) / 1K tokens,如果你想要了解更多的話,可以參考這邊

額外補充

什麼是 tokens?
tokens 是指你輸入的字數,例如:「我想要去日本旅遊」,這句話有 8 個字,因此 tokens 就是 8。

申請 Line Developer

接下來我們要來申請 Line Developer 帳號,你可以透過這個連結快速進入 Line Developer 網站,登入方式,基本上就是使用 LINE 帳號登入

LINE 帳號登入

登入後,你可以透過這個連結,進入 LINE 的開發者後台

開發者後台

接著,你會看到畫面上什麼都沒有,所以這邊你要點一下中間的「Create a new provider」,這個類似於一個公司帳號的概念,底下會有許多的專案,所以你可以把它想像成一個公司,而這個公司底下有許多的專案,例如:Line Bot、Line MUSIC 等等之類你的服務。

這邊的話,範例我們就叫做「Example Bot」

Example Bot

建立的過程是非常快的,因此當你點下「Create」 之後,你就可以馬上看到 Example Bot 的畫面

Example Bot

接著,點一下畫面上的 「Create a Messaging API channel」,因為我們要來申請一個聊天機器人,所以我們要使用「Messaging API」

Create a Messaging API channel

進入「Create a Messaging API channel」後,會有一些欄位要填寫

  • Channel type
    • 保持預設 Messages API
  • Provider
    • 保持預設 「Example Bot」 即可
  • Company or owner’s country or region
    • 依據你的公司所在地選擇,這邊我選擇台灣
  • Channel icon
    • 你可以之後再上傳,所以這邊先不用管
  • Channel name
    • 這邊我們就叫「溜腳學院」吧(老闆不要扁我)
  • Channel description
    • 你可以簡單描述一下,這個聊天機器人的功能,例如:「大家好,我是溜腳學院的小客服」
  • Category
    • 就選擇…「其他媒體」
  • Subcategory
    • 接著一樣選「媒體(其他)」
  • Email address
    • 這是當你的聊天機器人有問題時,信箱聯絡你的方式
  • Privacy policy URL 與 Terms of use URL
    • 這兩個就不用理它,因為我們目前用不到。

最後再打勾兩個條管,你就可以點下「Create」建立你的聊天機器人了。

(由於這個頁面圖片太大張了,所以就不額外截圖。)

當你按下「Create」之後,它會出現一個視窗,主要是告知你一些資訊,所以你可以按下 ok 關閉它。

Create

到目前為止,我們只是申請好了一個 Line Bot,接著要來取得一些資訊,首先在「Base settings」中找到以下資訊並複製起來

  • Channel ID
  • Channel secret

接著切換到「Messaging API」,找到「Channel access token」

Channel access token

接著按下 「issue」,你會取得一串很長的 Token,請複製起來,後面我們會使用到。

到目前為止,我們已經準備好了 Line Bot 的申請以及 Open API 的申請,接下來就是準備進入撰寫程式碼的部分囉。

建立一個專案

建立專案方式我就不多述了,相信大家應該都很熟悉怎麼建立專案以及安裝 Node.js,而這邊我稍微會有一點特別,我會使用 pnpm 來建立,如果你真的完全沒想法,你可以參考我以下指令來建立一個專案,當然你也可以使用 npm 或是 yarn 來建立。

Note
以下指令適用於 Mac 系統,若為 Mac 建議額外 Google 一下指令。

1
2
3
4
5
6
# 建立一個資料夾
mkdir example-line-bot
# 進入資料夾
cd example-line-bot
# 初始化專案
pnpm init

基本上到這邊就算是初始化好一個專案了,接著就是準備要來安裝 Line Bot 的套件了。

安裝相關套件

接下來我們要安裝 @line/bot-sdk 這個套件,請在專案終端機下輸入以下

1
pnpm i @line/bot-sdk express dotenv

安裝好之後,你可以先在專案底下建立一個 app.js 的檔案,稍後我們就會開始撰寫 LINE Bot 的程式碼囉。

撰寫 LINE Bot

前面我們已經準備好了 app.js 檔案,接著就是要撰寫程式碼,不外乎前面就是引入 @line/bot-sdkexpressdotenv 這三個套件

1
2
3
4
require('dotenv').config();

const express = require('express');
const line = require('@line/bot-sdk');

接著呢?其實 Line 官方有提供範例程式碼,因此我們是可以直接嘗試挪過來測試看看

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
require('dotenv').config();

const line = require('@line/bot-sdk');
const express = require('express');

// create LINE SDK config from env variables
const config = {
channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
channelSecret: process.env.CHANNEL_SECRET,
};

// create LINE SDK client
const client = new line.Client(config);

// create Express app
// about Express itself: https://expressjs.com/
const app = express();

// register a webhook handler with middleware
// about the middleware, please refer to doc
app.post('/callback', line.middleware(config), (req, res) => {
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result))
.catch((err) => {
console.error(err);
res.status(500).end();
});
});

// event handler
function handleEvent(event) {
if (event.type !== 'message' || event.message.type !== 'text') {
// ignore non-text-message event
return Promise.resolve(null);
}

// create a echoing text message
const echo = { type: 'text', text: event.message.text };

// use reply API
return client.replyMessage(event.replyToken, echo);
}

// listen on port
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`listening on ${port}`);
});

記得在專案底下建立一個 .env 並填寫以下資訊跟 Key

1
2
CHANNEL_ACCESS_TOKEN=
CHANNEL_SECRET=

理論上來講,你目前專案應該是已經可以輸入 node app.js 啟動了,所以你可以嘗試啟動看看,如果無法啟動的話,會建議你往前面的步驟再檢查一下。

部署到 Render

到目前為止我們要先嘗試將專案部署到 Render 上面,否則我們並不清楚專案是否可以正常運作,因為在使用 Render 之前,我們需要先建立一個 Render 的帳號,如果你還沒有的話,可以先到這邊註冊一個帳號。

建立好後,會建議你將專案程式碼上傳到 GitHub 上,因為 Render 有提供 GitHub 的整合,這樣可以讓我們更方便的部署專案,而上傳到 GitHub 這流程就不再贅述了,因此我這邊假使你已經完成了將程式碼上傳到 GitHub 這個動作後,接著就是直接進入到 Render 後台

Render 後台

這邊請選擇 「Web Services」

New Web Services

接著你可以在這個畫面上選擇專案,如果你發現你的畫面上一個專案都沒有的話,可以點一下旁邊的「Configure account」

Configure account

接著你可以在這邊選擇專案,懶一點的話你可以乾脆給它全部的權限,因為我們只是要部署一個簡單的專案,所以我就選擇我要的單一專案即可

單一專案

接下來畫面上應該就可以看到你的專案了,接著點選你要部署的專案

專案

在這邊點一下「Connect」

Connect

這邊會要求你填一些欄位,我就不多說明了,我直接附上我的設定

設定

接著往下滾,就選擇 「Free」專案即可,然後再點一下下方的「Advanced」

Advanced

因為我們要在「Advanced」區塊填入環境變數

Environment Variable

接著點一下「Add Environment Variable」,依照欄位填入

Add Environment Variable

最後其他設定可以不用管,直接點一下最後的「Create Web Service」

預設 Render 會去偵測 Commit 紀錄,所以如果有新的 Commit,Render 會自動重新部署,如果你不想要這個功能的話,可以在「Advanced」區塊裡面,將「Auto Deploy」關閉

Auto Deploy

那麼由於為了方便示範,因此我這邊預設會採用「Yes」

接著你就可以看到你的專案正在部署了

部署

這個過程稍微會比較久一點,因為 Render 會將它打包成一個 Docker Image,然後再部署到它的伺服器上,所以這個過程會稍微比較久一點,但是不用擔心,因為它會自動幫你做這些事情,你只要等待它完成即可。

部署完畢的話,你可以在畫面看到他提示你專案已經運行起來並且開始部署

運行開始

而你的專案的網址,就會在這邊

專案網址

不出意外的話,應該是無法開啟的,因為我們只有做一個 post 的 API,那麼到這邊我們專案就正式部署出去了,基本上都不用理會它了。

這邊請記住網址,因為後面我們會在使用到。

加入 Line Bot 好友

接下來我們就要來實際測試一下,我們的 Line Bot 是否可以正常運作,而取得這個 LINE Bot 的 ID 方式就在 Developer Console 的「Messaging API」裡面

Line Bot

加入好友後,你應該就會立刻看到好友歡迎的訊息通知

歡迎的訊息通知

接著你可以試著發送訊息給它,例如「Test」,但是你會發現它沒有辦法正常回應你

Test

因此接下來,我們要做一點調整,讓它可以正常回應你。

調整 Line Bot

這邊請你回到 Line Bot 的後台,點一下「Messaging API」,然後找到「Webhook settings」

Webhook settings

接著點一下「Edit」,然後將「Webhook URL」填入你的專案網址,例如 https://example.com/callback

Webhook URL

callback 是因為我們 Express 專案的路由是 /callback,如果你的路由不是這個的話,請自行修改

1
2
3
4
5
6
7
8
9
app.post('/callback', line.middleware(config), (req, res) => {
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result))
.catch((err) => {
console.error(err);
res.status(500).end();
});
});

按下儲存後,你可以點一下 「Verify」,來驗證你的網址是否正確以及專案是否正常,只要能看到「Success」就代表你的網址以及專案是正確的

Success

接著將下方的 「Use webhook」打開

Use webhook

打開後,你再去試著發送訊息給你的 Line Bot,你就會發現它可以正常回應你了,而且是不論你打什麼,他都會回應你剛剛打的字

成功回應

恭喜你,截至為止,我們終於把 Line Bot 正式處理好了

關掉預設回饋

接下來你可能會想要把那段該死的提示回饋

1
2
3
4
感謝您的訊息!

很抱歉,本帳號無法個別回覆用戶的訊息。
敬請期待我們下次發送的內容喔(moon smile)

以及加入好友通知

1
2
3
4
5
6
Ray您好!
我是溜腳學院。
感謝您加入好友

此官方帳號將定期發放最新資訊給您
敬請期待

這兩個關掉,關掉方式很簡單,一樣回到 Line Bot 的後台,點一下「Messaging API」,然後找到「Auto-reply messages」

Auto-reply messages

你會跳到 LINE@ 管理介面,將「加入好友歡迎訊息」以及「自動回覆訊息」關掉就搞定囉~

關掉預設通知

串接 OpenAI API

終於到了我們的重頭戲了,前面你可以發現 LINE Bot 的流程與申請非常的多且繁瑣,因此前面花很多時間在介紹,主要是希望你可以順利建立好你的 LINE Bot,確保你接下來的步驟不會有問題。

安裝 OpenAI 套件

首先不外乎我們要先安裝 OpenAI 的套件,請在專案底下輸入以下指令

1
pnpm i openai

接著 openai 本身有提供範例程式碼

1
2
3
4
5
6
7
8
9
10
11
12
const { Configuration, OpenAIApi } = require("openai");

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

const completion = await openai.createCompletion({
model: "text-davinci-002",
prompt: "Hello world",
});
console.log(completion.data.choices[0].text);

因此基於上方這一段,我們稍候可以來修改一下,讓它可以跟我們的 LINE Bot 串接。

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

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

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

調整 app.js 的程式碼

首先我們要先在 app.js 中引入 OpenAI 的套件

1
const { Configuration, OpenAIApi } = require("openai");

接下來我們要來實例化 OpenAI

1
2
3
4
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

apiKey 就是我們最早前面所申請的 OpenAI API Key,而這一段一樣是放在 .env 的。

如果你沒申請的話你會無法往下繼續的,請務必申請 OpenAI API Key 唷。

目前來講,你的 .env 應該長這樣

1
2
3
4
CHANNEL_ACCESS_TOKEN=
CHANNEL_SECRET=

OPENAI_API_KEY=

接下來呢?我們就要將原本 Line 官方所提供的範例程式碼稍微調整一下,主要調整的範圍是 handleEvent 的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function handleEvent(event) {
if (event.type !== 'message' || event.message.type !== 'text') {
// ignore non-text-message event
return Promise.resolve(null);
}

const completion = await openai.createCompletion({
model: "text-davinci-003",
prompt: event.message.text ,
});

// create a echoing text message
const echo = { type: 'text', text: completion.data.choices[0].text };

// use reply API
return client.replyMessage(event.replyToken, echo);
}

這邊我們將原本的 prompt 改成 event.message.text,也就是使用者輸入的訊息,而 completion.data.choices[0].text 就是 OpenAI 所回傳的訊息。

沒問題後就儲存檔案,並 commit push 出去到 GitHub 上,屆時 Render 就會自動幫你重新部署了。

Note
記得要去 Render 上面增加 OPENAI_API_KEY 的環境變數,否則會出現錯誤唷。

當部署完畢後,你再去試著跟你的 LINE Bot 聊天,你會發現它已經會講話囉~

開始聊天

只是你會發現它好像沒辦法回你很多話,這原因是我們沒有告知它要使用多少 Tokens,因此這邊我們調整一下

1
2
3
4
5
const completion = await openai.createCompletion({
model: "text-davinci-003",
prompt: event.message.text ,
max_tokens: 200,
});

這個 max_tokens 會影響到 OpenAI 所回傳的訊息長度,因此我們這邊設定為 200,這樣就可以讓它回很多話了。

調整 OpenAI 的回傳訊息

那麼這邊也有一件很有趣的事情,也就是 OpenAI 的訊息都有一大推空白,這是因為 OpenAI 會將你的訊息分成一個個的段落,而每個段落之間都會有一個空白,因此我們也要來調整一下。

1
2
3
4
5
6
7
const completion = await openai.createCompletion({
model: "text-davinci-003",
prompt: event.message.text ,
max_tokens: 200,
});

const echo = { type: 'text', text: completion.data.choices[0].text.trim() };

再重新上傳程式碼後,你就可以看到機器人正常的回你了

正常回覆

恭喜你,完成了一個可以跟你聊天的機器人!而且是串接了 OpenAI 的機器人!

GPT-3.5 的使用

由於後來 OpenAI 開放了 ChatGPT 的 API,所以這一篇我算是補充一下該怎麼接,而不會整個重新介紹一次,因為其實整個的程式碼轉換成本上算低了。

首先原本的程式碼是這樣的

1
2
3
4
5
const completion = await openai.createCompletion({
model: "text-davinci-003",
prompt: event.message.text,
max_tokens: 200,
});

這邊我們要將 createCompletion 改成 createChatCompletion 方法(官方文件竟然沒提到),接著我們要將 prompt 改成 messages 以及 model 也要調整一下

1
2
3
4
5
6
7
8
const completion = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{
role: 'user',
content: event.message.text,
}],
max_tokens: 200,
});

接著 const echo = { type: 'text', text: completion.data.choices[0].text.trim() }; 這一段也要調整,需要調整成以下

1
const echo = { type: 'text', text: choices.message.content.trim() || '抱歉,我沒有話可說了。' };

這樣子你就可以從 GPT-3 轉換成 GPT-3.5 了囉!

完整程式碼:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
require('dotenv').config();

const express = require('express');
const line = require('@line/bot-sdk');
const { Configuration, OpenAIApi } = require("openai");

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

// create LINE SDK config from env variables
const config = {
channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
channelSecret: process.env.CHANNEL_SECRET,
};

// create LINE SDK client
const client = new line.Client(config);

// create Express app
// about Express itself: https://expressjs.com/
const app = express();

// register a webhook handler with middleware
// about the middleware, please refer to doc
app.post('/callback', line.middleware(config), (req, res) => {
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result))
.catch((err) => {
console.error(err);
res.status(500).end();
});
});

// event handler
async function handleEvent(event) {
if (event.type !== 'message' || event.message.type !== 'text') {
// ignore non-text-message event
return Promise.resolve(null);
}

const { data } = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [
{
role: 'user',
content: event.message.text,
}
],
max_tokens: 500,
});

// create a echoing text message
const [choices] = data.choices;
const echo = { type: 'text', text: choices.message.content.trim() || '抱歉,我沒有話可說了。' };

// use reply API
return client.replyMessage(event.replyToken, echo);
}

// listen on port
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`listening on ${port}`);
});

最後也稍微講一下 messages,我們目前可以看到傳送了一個陣列物件

1
2
3
4
[{
role: 'user',
content: event.message.text,
}]

這個的代表著我們發送出去的訊息是來自「使用者」,如果你後續想要做到跟 ChatGPT 一樣可以貫穿前後文的話,role 還有一個屬性是 system,你只要將機器人回覆你的訊息也放進去就可以了,例如:

1
2
3
4
5
6
7
8
9
10
[
{
role: 'user',
content: event.message.text,
},
{
role: 'system',
content: '你好,我是機器人',
}
],

這樣子 ChatGPT 就知道哪一段訊息是它發的,哪一段是你發的囉~

結語

相信你應該發現這個 Line Bot 的建立流程超級無敵的長,而且非常的多流程跟細節,但是你也可以因此認識到如何串接第三方服務,以及如何將你的程式碼部署到 Render 上面,所以我覺得是一個相當不錯的小練習,所以如果你想嘗試做一個聊天機器人,是可以考慮使用這個方法的。

但是要注意一件事情 OpenAI 的 GPT 有一個重點使用,如果你要讓你聰明一點的回覆你,那麼你就要將它的上下文(包含你的回覆)一起撈出來丟給它,否則你會發現它有點笨,可是 LINE 的聊天機器人不太適合這種方式,因為過程你可能會問很多事情,這樣子訊息量會非常龐大且沒辦法刪除,因此這邊我們就不做這件事情了,而是讓它針對你每個問題去回覆即可,而不包含前一個問題的回覆。

最後這邊我也附上範例程式碼,如果你想直接拉回去用也是可以的,因為這一份程式碼基本上都是以官方所提供的為主,很少有大幅度調整的部分,所以你可以直接拿來用。

希望這一篇可以讓你快快樂樂做出一個 LINE Bot。

Info
如果你覺得部署到 Render 很難去除錯,其實是可以考慮使用 ngrok 的,這是一個可以讓你將本機的服務對外開放的服務,你可以參考這篇文章

補充

Render 本身選擇免費方案時,若有一段時間(印象是 15 分鐘)沒有使用則會進入睡眠狀態,如同先前 Heroku 免費版一樣,一但進入睡眠狀態後,重新啟動也要大約 30 秒~1 分鐘左右,因此如果你想避免這種狀況的話,你可以考慮使用 UpTimeRoBot 服務強迫它不准睡覺(壓榨勞工)。