ChatGPT x LINE Bot指南:打造智能對話助手
從基礎設置到Prompt策略,一步步整合ChatGPT與LINE Bot
久違的開新坑,緣起是想幫公司客服弄個小幫手之類的東西,最後就變成了標題的LINE Bot + chatGPT的組合了,順勢被要求應該被寫成文章記錄一下,於是有了這篇文章XD
這篇適合誰?
這篇預期是給已有基本Node.js開發環境的開發者參考用,並有GitHub、render、ngrok帳號供後續的開發與部署,若有錯誤還煩請各位大大們指教(合掌
大綱
- 註冊LINE開發者帳號
- 建立LINE Bot用Channel
- 初始化express專案
- LINE Bot基本的應答功能實作
- 使用ngrok進行本地端LINE Bot開發
- 串接ChatCompletion api
- 優化prompt
- 其他prompt範例
- 部署於render
▍註冊LINE開發者帳號
前往LINE Developers官方網站並點選右上角的「Log In」。
登入後,點選「建立LINE商用ID」,填寫必要的個人資訊,並同意相關條款。點選「Create my account」,就正式成為LINE開發者啦!
▍建立LINE Bot用Channel
成功以LINE開發者帳號進入後台後,就先點選「Create」建立一個新的Provider,用於管理接下來要開發的Bot。
輸入Provider名稱(例如:測試初號機),點選「Create」。接著就會進入Provider頁面
接著我們要建立新的Channel作為line bot與LINE平台的通道,這次由於是要用來製作LINE bot,需使用Messaging API,所以點選中間的「 Create a Messaging API channel」。
接著把所有欄位的必填填寫完成,按下Create,就會提醒你接下去就會創建同名的官方帳號了,務必確認名稱不要撞到別人或是取錯啊XD
同意資訊也給他按下去…
到此就完成基本的Channel設定了,之後度開發過程中,會再回到這裡取得Channel secret和Channel Access Token來進行串接,就先擺著囉!
▍初始化express專案
萬事起頭難,先來啟個express專案:
mkdir line-bot-with-gpt
cd line-bot-with-gpt
pnpm init
git init
code .
先修改一下`package.json`,滿足下列幾點:
- 讓本專案可以使用ES modules
- 讓進入點改為`app.js`
- 新增dev script,使用nodemon來啟動`app.js`
{
"name": "line-bot-with-gpt",
"version": "1.0.0",
"description": "",
- "main": "index.js",
+ "main": "app.js",
+ "type": "module",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "dev": "nodemon app.js"
},
"keywords": [],
"author": "",
"license": "ISC"
}
安裝express套件:
pnpm i express
新建`app.js`,並參考express範例引入套件,做基礎伺服器設置:
import express from 'express'
const app = express()
app.get('/', (req, res) => {
res.send('Hello World!')
})
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Example app listening on http://localhost:${port}/`)
})
啟動伺服器看看有沒有問題:
pnpm run dev
▍LINE Bot基本的應答功能實作
先按照官方文件範例新增.env檔案並增加兩個環境變數:
CHANNEL_ACCESS_TOKEN=你的CHANNEL_ACCESS_TOKEN
CHANNEL_SECRET=你的CHANNEL_SECRET
回頭前往LINE developer尋找兩個需要引入的環境變數:
CHANNEL_ACCESS_TOKEN:Basic settings > Channel secret
CHANNEL_SECRET:Channel access token > Channel access token > 按下issue生成
安裝dotenv來讀取環境變數:
pnpm i dotenv
引入dotenv及啟動:
import express from 'express'
+ import dotenv from 'dotenv'
+ dotenv.config()
const app = express()
pnpm i @line/bot-sdk
參考官方文件範例引入line sdk並撰寫基本路由:
import express from 'express'
import dotenv from 'dotenv'
+ import line from '@line/bot-sdk'
dotenv.config()
const app = express()
+ const lineConfig = {
+ channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
+ channelSecret: process.env.CHANNEL_SECRET,
+ }
+ app.post('/webhook', line.middleware(lineConfig), (req, res) => {
+ Promise
+ .all(req.body.events.map(handleEvent))
+ .then((result) => res.json(result))
+ })
- app.get('/', (req, res) => {
- res.send('Hello World!')
- })
+ const client = new line.Client(lineConfig)
+ function handleEvent(event) {
+ if (event.type !== 'message' || event.message.type !== 'text') {
+ return Promise.resolve(null)
+ }
+
+ return client.replyMessage(event.replyToken, {
+ type: 'text',
+ text: event.message.text
+ })
+ }
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Example app listening on http://localhost:${port}/`)
})
可以看到其中進行了幾項操作
- 宣告了lineConfig引入兩個環境變數。
- 設置了一個POST路由`/webhook`,並會經過line middleware進行驗證,最後將所有的events交由handleEvent處理。
- handleEvent會驗證event.type,若非message且message.type非text就回傳null,否則就拿著event.replyToken去回傳一樣的event.message.text給使用者。
到此就是基本的LINE Bot內容了,接著就是要實際串接看看了!
▍使用ngrok進行本地端LINE Bot開發
因為還需要在本機進行開發,就先不部署到render上,但因為串接需要https協定,所以要使用ngrok,讓http://localhost:3000/可以被外網找到。
啟動ngrok,將3000 port曝露出來:
ngrok http 3000
複製Forwarding的網址(https://xxxxxxx.jp.ngrok.io),並貼到開發者後台的Webhook settings > Webhook URL上,記得要添加POST的路徑!
儲存後按下Verify測試設置是否有效:
沒意外的話會出現Success,若是404則可能是後面的POST路徑有誤,而500則有可能是環境變數設置錯誤,讓middleware驗證失敗。
接著記得把下面的use webhook開啟,才會真的生效:
另外為了不讓測試受到干擾,也順道把下面的幾個官方帳號功能停用,點擊Edit後會另開分頁供設定:
接著就掃描後台提供的QR code,把LINE Bot加入自己的LINE吧!
對話起來和我們預期的一樣,就是個鸚鵡學舌的狀態,不過至少路打通了!
接著就是重點的串接GPT啦!
▍串接ChatCompletion api
前往openai官網後台生成自己的api key
把api key加到`.env`中:
CHANNEL_ACCESS_TOKEN=xxxxx
CHANNEL_SECRET=xxxxx
+ OPENAI_API_KEY=xxxxx
pnpm install openai
接著串接的對象是Create chat completion api,Model則是選用基於GPT-3.5的gpt-3.5-turbo,畢竟就官方說法其費用更加低廉,且對chat功能有最佳化,之前實際測試起來也確實如此。
參考官方文件範例來串接Create chat completion:
import express from 'express'
import dotenv from 'dotenv'
import line from '@line/bot-sdk'
+ import { Configuration, OpenAIApi } from 'openai'
dotenv.config()
const app = express()
const lineConfig = {
channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
channelSecret: process.env.CHANNEL_SECRET,
}
+ const openaiConfig = new Configuration({
+ apiKey: process.env.OPENAI_API_KEY,
+ })
+ const openai = new OpenAIApi(openaiConfig);
app.post('/webhook', line.middleware(lineConfig), (req, res) => {
Promise
.all(req.body.events.map(handleEvent))
.then((result) => res.json(result))
})
const client = new line.Client(lineConfig)
- function handleEvent(event) {
+ async function handleEvent(event) {
if (event.type !== 'message' || event.message.type !== 'text') {
return Promise.resolve(null)
}
+ try {
+ const completion = await openai.createChatCompletion({
+ model: "gpt-3.5-turbo",
+ messages: [{ role: "user", content: "Hello world" }],
+ });
+
+ console.log(completion.data.choices[0].message)
+ } catch (error) {
+ console.error(error)
+ }
+
return client.replyMessage(event.replyToken, {
type: 'text',
text: event.message.text
})
}
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Example app listening on http://localhost:${port}/`)
})
一開始引入兩個function:Configuration和OpenAIApi,主要就是將api key放入後new一個openai實體出來。
接著改寫一下原有的handleEvent,因為範例中有使用await語法,所以將其加上async,並用try catch包裹官方文件呼叫createChatCompletion的範例。
範例中的payload是:
{
model: "gpt-3.5-turbo",
messages: [{role: "user", content: "Hello world"}],
}
其中‘‘gpt-3.5-turbo’’就是前面提到要使用的模型,而messages是個Array,其中的role為‘‘user’’,並有content:‘‘Hello world’’。最後則是把回傳內容直接在伺服器端印出來。
就向機器人發個訊息,戳看看會得到什麼吧!
按照上面的撰寫方式,只要機器人鸚鵡學舌回來時,就表示GPT的回應也來了!
串上啦!有成功讓AI回應 “Hello world!”的內容了!
接著修改一下handleEvent的內容,讓使用者的訊息真的有傳入GPT中,並且將GPT的回應也使用client.replyMessage回應給使用者。
async function handleEvent(event) {
if (event.type !== 'message' || event.message.type !== 'text') {
return Promise.resolve(null)
}
try {
const completion = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
- messages: [{ role: "user", content: "Hello world" }],
+ messages: [{ role: "user", content: event.message.text }]
})
- console.log(completion.data.choices[0].message)
+ if (completion.data.choices[0]?.message?.content) {
+ return client.replyMessage(event.replyToken, {
+ type: 'text',
+ text: completion.data.choices[0].message.content
+ })
+ }
} catch (error) {
console.error(error)
}
- return client.replyMessage(event.replyToken, {
- type: 'text',
- text: event.message.text
- })
}
整體很簡單,就是把event.message.text塞進user content中,並且把原來鸚鵡學舌的內容直接改成AI的回應內容。
接下來就是修改prompt的內容,讓AI符合指定情境並上線囉!
▍優化prompt
觀察先前的回應內容中可以發現,AI回覆的role為‘assistant’,而在官方文件中也有提到,message role是用來標記對話中不同參與者身份的關鍵參數,該身份共有三種:
- system:可以視為系統層級的prompt,不會因為後續的內容而變更的指示(當然部分模型有較不注重system的傾向),通常類似於chatGPT中的第一句話,用來為整個對話設定語境和指導 AI 的行為。
- user:可以視為真實用戶的對話內容,AI會將user的內容視作實際的提問和對話,若事先給予定義好的用戶對話內容,也可以視為給AI參考用的上下文。
- assistant:可以視為AI的回應內容,這裡的內容會作為接下來model回應的參考指標,讓AI了解之後的回應是否有特定格式或語氣等。
知道這些規則後,就可以嘗試發想要請AI擔任的角色和情境了。
以星座專家為例
若想要AI作為星座專家,我們可以將message寫成:
{
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: "你是一位星座專家,請你按照使用者告知的星座來進行本日星座運勢分析。若使用者提供的內容不是十二星座,請拒絕回答。分析內容包含:事業、感情、財運、健康,請描述分析內容並以星數表示好壞,滿分五顆星,最好五顆,最壞一顆星。"
},
{
role: "assistant",
content: "你好,讓我來看看你的星座運勢,請告訴我你的星座為何?"
},
{ role: "user", content: event.message.text }
]
}
用system身份將AI設定為星座專家,並要求AI回覆事業、感情、財運、健康等面向的運勢,並且加上不回答的條件。
用assistant身份讓AI知道上文已經詢問過使用者其星座為何。
下文便是user在LINE中實際傳入的星座了!
馬上試看看…
接著我們可以更加擴充system的回答範本,讓AI了解我們預期的回答格式為何:
{
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: "你是一位星座專家,請你按照使用者告知的星座來進行本日星座運勢分析。若使用者提供的內容不是十二星座,請拒絕回答。分析內容包含:事業、感情、財運、健康,請條列各項分析內容並以星數表示好壞,滿分五顆星(★★★★★),最低一顆星(★☆☆☆☆)。"
},
{
role: "system",
content: "回答範本為:以下是<星座>的今日運勢:\n 事業運勢為 <星數> \n <解析> \n 感情運勢為 <星數> \n <解析> 財運運勢為 <星數> \n <解析> 健康運勢為 <星數> \n <解析> \n 整體運勢為<星數> \n <解析>。"
},
{
role: "assistant",
content: "你好,讓我來看看你的星座運勢,請告訴我你的星座為何?"
},
{ role: "user", content: event.message.text }
]
}
這裡追加了一段system級的回答範本,並用<變數>告知可以填入特定變數,在搭配換行符\n做些排版上的變化,最後呈現的結果會是這樣:
當然,越詳盡的message內容可以帶來更接近預期的回答格式,但是所消耗的token也是隨之上升,也就意味著每次呼叫的金額成本更高,在還沒用完免費額度之前多多嘗試吧!
其他的常用參數:
- temperature:一個0 ~ 2的數值,數字越小可以讓回答更趨於保守,反之則是更自由發揮,如果想要更加符合上下文的回答範例,便可以考慮將其調低。
- max_tokens:AI回應的tokens上限,開越小越省錢,但是回應內容有可能被截斷,可以嘗試在system中要求其回應應低於多少字來簡化長度。
其他prompt範例
英文老師
{
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: "你是一名英文文法老師,負責校驗學生的英文文法,請你檢查學生給予的文句是否有文法或拼寫錯誤,並提供修改後的結果與為何這樣修改。"
},
{
role: "system",
content: "輸出範本:修改後的文句為: \n <修改後的文句> \n 修改理由為: \n <修改理由>"
},
{
role: "assistant",
content: "嗨,請你提供一段英文文句,我來幫你檢查文法或拼寫錯誤。"
},
{ role: "user", content: event.message.text }
]
}
文章摘要
{
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: "你是一名文章摘要翻譯人員,請你根據使用者提供的文章,條列出5項內的文章重點與短文總結,並翻譯成台灣繁體中文。"
},
{
role: "system",
content: "輸出範本:- <文章重點> \n - <文章重點> \n <短文總結>"
},
{
role: "assistant",
content: "請提供原文。"
},
{ role: "user", content: event.message.text }
]
}
▍部署於render
先將專案推到githib上,待等等部署用。
註冊並進入render dashboard,建立一個新的Web service。
接著會看到連接專案的介面:
上方為連接github專案的介面,下面則是直接搜尋連接公開專案,如圖中所述,連接github專案可以很簡單的有自動部署,所以這裡就簡單紀錄連接github的部分。
點擊“Configure account”,會跳轉到Github要求授權,這裡可以選擇要讓render可以存取所有專案或是部分專案。
這裡只選擇了這次的LINE-Bot專案,按下Install後,會再度回到render dashboard並看到專案出現在清單中了。
點擊Connect開始進行部署設定。
Build command記得修改成安裝package的指令,接著展開Advanced填寫環境變數。
填寫三個.env中的環境變數,並打開Auto-deploy。
Create Web Service就按下去吧!
往上看就會發現已經有實際的部署位置了,複製起來後前往LINE developer後台,替換掉Webhook settings > Webhook URL原本連往ngrok的網址,並驗證是否成功:
實際回到LINE上面戳看看:
有正確運作了,唯獨就是render免費方案是會休眠的,太久沒戳會要等他啟動就是了,不過真的要隨時使用就不能當免費仔啦XD
以上就是本次LINe-Bot串接chatGPT的實作紀錄,從各項服務的帳號申請、express的架設、串接chatGPT、優化prompt、再到部署上線,說長不長,說短也不短呢!
若文章中間有誤還煩請指教,如果想看repo的話,Github repo在此供參考,喜歡的話也歡迎送我一顆星XD
若本篇文章對您有所幫助,您可以:
幫這篇文章拍個手,讓我感受到您的肯定 👏🏻
點擊下方的「Follow」來追蹤我 🔔
當然也歡迎留言敲碗其他內容歐!🥁