Codelab นี้คุณจะได้เรียนรู้การสร้าง LINE Chatbot ที่ทำงานร่วมกับ Dialogflow เพื่อให้ Dialogflow สามารถรองรับ Webhook Event ประเภทอื่นๆ นอกจาก Text Message ได้ด้วย เช่น รองรับการแชร์โลเคชั่น, การเลือกวันที่จาก DateTime Picker Action หรือ การกดปุ่มที่เป็น Postback Action เป็นต้น เหมาะสำหรับผู้ที่มีพื้นฐานมาบ้างแล้ว ได้ศึกษาเพิ่มเติมไปจนถึงระดับ Advance ใน Codelab เดียว
เนื้อหาของ Codelab นี้ จะเป็นลักษณะของการพัฒนาระบบขึ้นมาระบบหนึ่ง ซึ่งจะใช้เป็น Platform ในการลงทะเบียนข้อมูล โดยมีการใช้ Messaging API เป็น Chatbot ทำงานอยู่บน Cloud Functions for Firebase คอยรับ Webhook request จากนั้นจะส่งไปใช้ความสามารถด้าน NLP ของ Dialogflow ในการประมวลผลและจัดการเรื่อง Session ของบทสนทนา หลังจากได้รับข้อมูลครบถ้วนแล้ว จึงส่งไปเก็บไว้ที่ Cloud Firestore
ข้อมูลที่ต้องการเก็บ จะประกอบไปด้วย ชื่อ (Text Message Event), โลเคชั่นของผู้ใช้ (Location Message Event) และ วันที่ (DateTime Picker Action)
จุดเริ่มขบวนสำหรับการพัฒนาแอปพลิเคชันต่างๆบนแพลตฟอร์มของ LINE คือคุณจะต้องสมัครเป็น LINE Developer ก่อน
Provider คือชื่อผู้ให้บริการ ซึ่งจะไปแสดงตามหน้า consent ต่างๆ หรือเรียกได้ว่าเป็น superset ของแอปทั้งหลายที่เราจะพัฒนาขึ้นรวมถึง LIFF app ด้วย โดยการสร้างเพียงให้ระบุชื่อของ Provider ลงไป ซึ่งอาจจะตั้งเป็นชื่อตัวเอง, ชื่อบริษัท, ชื่อทีม หรือชื่อกลุ่มก็ได้
Channel เปรียบเสหมือนแอป หรือเรียกได้ว่าเป็น subset ของ Provider โดยมีอยู่ 3 รูปแบบ คือ LINE Login, Messaging API และ Clova Skill
เบื้องหลังของ Chatbot ตัวนี้ เราจะใช้บริการใน Firebase อย่าง Cloud Functions for Firebase และ Cloud Firestore ดังนั้นขั้นตอนนี้เราจะมาสร้างโปรเจค Firebase เพื่อใช้งานกัน
เนื่องจาก Cloud Functions for Firebase มีเงื่อนไขว่า หากต้องการไป request ตัว APIs ที่อยู่ภายนอก Google คุณจำเป็นจะต้องใช้ Blaze plan(เราจะต้องไปเรียก Messaging API ของ LINE)
เนื่องจาก Chatbot ตัวนี้จะเก็บข้อมูลการลงทะเบียนไว้ใน Cloud Firestore ดังนั้นให้เราจะมาเปิดใช้งาน database ตัวนี้กัน
ตัว Dialogflow เป็นเครื่องมือในการพัฒนาให้ Chatbot สามารถเข้าใจบทสนทนาแบบมนุษย์ (Conversation Development Tool) โดยเบื้องหลังจะมี Machine learning คอยประมวลผลให้ โดยเราสามารถนำไปใช้ได้เลย ไม่ต้องพัฒนาตัว NLP เอง แถมรองรับภาษาไทยอีกด้วย
Intent มาจากคำว่า Intention แปลว่า เจตนา หรือความปรารถนาของผู้ใช้ ซึ่งใน Diaglogflow เราจะต้องสร้าง Intents เป็นเรื่องๆไป เช่น เรื่องที่เกี่ยวกับทักทาย, เรื่องที่เกี่ยวกับสอบถามข้อมูล เป็นต้น ซึ่ง Chatbot ควรจะต้องเข้าใจและสามารถแยกแยะเรื่องต่างๆได้ ไม่ใช่ว่าผู้ใช้กำลังทักทายมา แต่ตัว Chatbot ดันไปพยายามตอบคำถาม
หลังจากที่เราสร้าง Agent จะมี Pre-defined Intents พื้นฐานมาให้ 2 ตัว ได้แก่
หน้าที่ของ Intent ตัวนี้คือ เมื่อไรที่ผู้ใช้ป้อนข้อความอะไรเข้ามาแล้วตัว Chatbot ไม่เข้าใจ มันจะวิ่งเข้า Intent ตัวนี้ เมื่อกดเข้าไปดูจะพบว่ามี section ที่ชื่อ Responses ซึ่งมีข้อความที่แสดงความไม่เข้าใจกรอกไว้เป็นตัวอย่างหลายบรรทัด เมื่อเราใช้งานจริงก็จะพบว่า หากเราพิมพ์อะไรที่มันไม่เข้าใจ มันจะสุ่มคำพูดใน responses ตอบกลับไปให้
หน้าที่ของ Intent ตัวนี้คือเกี่ยวกับเรื่องการทักทายกับผู้ใช้ กดเข้าไปดูจะพบว่าจะมีส่วนที่ชื่อ Training Phrases ที่สอนให้ Dialogflow เข้าใจว่าเมื่อผู้ใช้พิมพ์คำที่อยู่ในบริบท หรือ context เหล่านี้เข้ามา ก็ให้สุ่มคำตอบใน Responses กลับไป (ผู้ใช้ไม่ต้องพิมพ์เป๊ะๆก็ได้ มันจะพยามเข้าใจเอง)
ในวันนี้เราจะทำ Chatbot ที่สามารถลงทะเบียนผู้ใช้ โดยจะเก็บข้อมูล ชื่อ ตำแหน่ง และวันที่ เรามาเริ่มสร้าง Intent แรกกันเลย
เมื่อเราสร้าง Intent แรก เสร็จแล้ว ขั้นตอนต่อไป คือการสร้าง Intent อีกตัว มารับค่า ชื่อของผู้ใช้ ที่กำลังจะตอบมา โดยมีขั้นตอน ดังนี้
{
"line": {
"type" : "text",
"text": "ขอบคุณ คุณ$name กรุณาแชร์โลเคชั่นของคุณ",
"quickReply": {
"items": [
{
"type": "action",
"action": {
"type": "location",
"label": "แชร์โลเคชั่น"
}
}
]
}
}
}
ขั้นตอนต่อไป คือการสร้าง Followup Intent อีกตัว มารับ ตำแหน่งของผู้ใช้
{
"line": {
"type": "text",
"quickReply": {
"items": [
{
"action": {
"label": "เลือกวันที่",
"data": "selected_date",
"mode": "date",
"type": "datetimepicker"
},
"type": "action"
}
]
},
"text": "ตำแหน่งของคุณคือ $latitude,$longitude ขั้นตอนต่อไปกรุณาเลือกวันที่"
}
}
ขั้นตอนต่อไป คือการสร้าง Followup Intent อีกตัว มารับ วันที่ผู้ใช้เลือก
เมื่อเราสร้าง Agent จนเสร็จแล้ว ขั้นตอนต่อไป คือการทดสอบการทำงานร่วมกับ LINE Chatbot ที่เราสร้างขึ้นมาในขั้นตอนก่อนหน้านี้
โดยการไปตั้งค่าการเชื่อมต่อระหว่าง LINE กับ Dialogflow ดังนี้
ขั้นตอนการเชื่อมต่อเรียบร้อยแล้ว แต่สังเกตว่าตอนที่เราส่ง Location ให้ Bot ระบบไม่ได้ทำงานต่อ เนื่องจาก Dialogflow ไม่รู้จัก Location Message Event เราจึงต้องมี Proxy server ขึ้นมาตัวนึง คั่นกลางระหว่าง LINE กับ Dialogflow เพื่อทำการรับค่า Webhook จาก LINE แล้วทำการแปลง Event ต่างๆให้อยู่ในรูปแบบที่ Dialogflow เข้าใจเสียก่อน
ไปเริ่มเตรียม Proxy Server กันเลย
Firebase CLI เป็นเครื่องมือที่จำเป็นสำหรับการ deploy ตัวฟังก์ชันที่เราพัฒนาขึ้น อีกทั้งยังสามารถจำลองการทำงานฟังก์ชัน(Emulate) ภายในเครื่องที่เราพัฒนาอยู่(Locally) ได้
npm install -g firebase-tools
firebase --version
firebase login
handle-non-text-event-with-df
) แล้วให้ shell เข้าไปในนั้นmkdir handle-non-text-event-with-df
cd handle-non-text-event-with-df
firebase init
ขั้นตอนนี้เราจะสร้างฟังก์ชันสำหรับคอยรับ webhook request จาก LINE เพื่อส่งต่อไปยัง Dialogflow อีกที
โดยในโฟลเดอร์ที่เราสร้างมา ให้เรา shell เข้าไปต่อในโฟลเดอร์ชื่อ functions
cd functions
@line/bot-sdk
และ express
npm i @line/bot-sdk express --save
firebase functions:config:set line.channel_access_token="xxxxx" line.channel_secret="xxxxx" dialogflow.agent_id="xxxxx"
firebase functions:config:get
@line/bot-sdk
และ express
เข้ามาconst functions = require('firebase-functions');
const line = require('@line/bot-sdk');
const express = require('express');
const config = {
channelAccessToken: functions.config().line.channel_access_token,
channelSecret: functions.config().line.channel_secret
}
const app = express();
app.post('/webhook', line.middleware(config), (req, res) => {
console.log('req.body', JSON.stringify(req.body, null, 2));
res.status(200).end();
});
exports.api = functions
.region('asia-northeast1')
.https
.onRequest(app);
firebase deploy --only functions
dialogflow.js
ไฟล์นี้จะทำหน้าที่แปลง LINE Event ไปเป็น Text Message Event ที่ dialogflow เข้าใจได้
แต่เพื่อไม่ให้ Codelab นี้ยาวเกินไป ผมได้แนบ sourcecode ไว้ให้แล้วที่
https://gist.github.com/kamnan43/fbfd113434573adf864d3aa1ef96ed32
ให้ copy ไปบันทึกเป็นไฟล์ชื่อว่า dialogflow.js วางไว้ที่เดียวกับ index.js
จากนั้นกลับไปแก้ไขไฟล์ index.js ตามลำดับ ดังนี้
dialogflow.js
const { postToDialogflow, createLineTextEvent, convertToDialogflow } = require('./dialogflow')
async function handleEvent(req, event) {
switch (event.type) {
case 'message':
switch (event.message.type) {
case 'text':
return handleText(req, event);
case 'location':
return handleLocation(req, event);
}
case 'postback':
return handlePostback(req, event);
default:
throw new Error(`Unknown event: ${JSON.stringify(event)}`);
}
}
handleText
, handleLocation
และ handlePostback
เพื่อจัดการข้อความ Text Message, Location Message และ Postback Message ตามลำดับasync function handleText(req) {
return await postToDialogflow(req);
}
function handleLocation(req, event) {
const message = event.message;
const newEvent = createLineTextEvent(req, event, `LAT : ${message.latitude}, LNG : ${message.longitude}`);
convertToDialogflow(req, newEvent);
}
function handlePostback(req, event) {
const data = event.postback.params.date;
const newEvent = createLineTextEvent(req, event, `DATE: ${data}`);
convertToDialogflow(req, newEvent);
}
app.post('/webhook', line.middleware(config), (req, res) => {
Promise.all(req.body.events.map(event => {
return handleEvent(req, event);
}))
});
firebase deploy --only functions
handleText ทำหน้าที่ ส่งต่อ Text Message ไปที่ dialogflow โดยตรง
handleLocation จะแปลง Location Message ไปเป็น Text Message ก่อน โดยแปลงให้อยู่ในรูปแบบ
`LAT : ${message.latitude}, LNG : ${message.longitude}`
handlePostback จะแปลง Postback Message (จาก DateTime Picker) ไปเป็น Text Message ก่อน โดยแปลงให้อยู่ในรูปแบบ
`DATE: ${data}`
ทั้งนี้เพราะ Dialogflow สามารถเข้าใจข้อความที่เป็น Text Message เท่านั้น
สังเกตุว่า รูปแบบข้อความทั้งสอง จะตรงกับ Training Phase ของ Intent ที่เราออกแบบไปก่อนหน้านี้แล้ว
หลังจาก Deploy แล้ว ลองทดสอบดูอีกครั้ง จะได้แบบนี้
จะสังเกตว่า Dialogflow สามารถอ่านค่า latitude, longitude จาก LINE Location Message ได้แล้ว
แต่เมื่อกดเลือกวันที่ ระบบจะยังไม่ทำงานต่อ นั่นเพราะว่า Intent สุดท้ายที่รับค่าวันที่ ต้องทำงานร่วมกับ fulfillment เพื่อเก็บค่าลงฐานข้อมูลนั่นเอง
ขั้นตอนต่อไป เราไปสร้าง fulfillment เพื่อรับค่าจาก Dialogflow ไปเก็บลง Firestore กัน
แก้ไขไฟล์ index.js ตามลำดับ ดังนี้
firebase-admin และ WebhookClient
const firebase = require('firebase-admin');
const { WebhookClient } = require('dialogflow-fulfillment');
firebase.initializeApp({});
handleFulfillment
เพื่อจัดการข้อมูลที่ส่งมาจาก Dialogflowasync function handleFulfillment(agent) {
const userId = agent.originalRequest.payload.data.source.userId;
const { name, latitude, longitude, selected_date } = agent.parameters;
const doc = {
uid: userId,
name,
latitude,
longitude,
selected_date: Date.parse(selected_date)
};
await firebase.firestore().collection('member').doc(userId).set(doc);
agent.add('บันทึกข้อมูลสำเร็จแล้ว');
}
app.use(express.json({ limit: '50mb' }));
app.post('/fulfillment', (request, response) => {
const agent = new WebhookClient({ request, response });
let intentMap = new Map();
intentMap.set('Register - date', handleFulfillment);
agent.handleRequest(intentMap);
});
โปรดสังเกตบรรทัด intentMap.set
และตรวจสอบว่า parameter ตัวแรก ตรงกันกับชื่อ Intent ใน Dialogflow ด้วย
firebase deploy --only functions
ยินดีด้วยครับ ถึงตรงนี้คุณก็มี LINE Chatbot ที่สามารถทำงานร่วมกับ Dialogflow ด้วย Event รูปแบบต่างๆ นอกเหนือจาก Text Message Event ได้แล้ว ลองนำไปประยุกต์ใช้กับ Event อื่นๆกันดูนะครับ