มาทบทวนการทำ chatbot กับคอร์ส Building LINE Chatbot using DialogFlow
ทำ chatbot เองก็แล้วแบบงูๆปลาๆ เลยขอเรียนทบทวนสักรอบนึง ผ่านคอร์สฟรีของ Skooldio จ้า สอนโดยพี่ตี๋ และคอร์สนี้ถูกเปิดตัวในงาน LINE Thailand Developer Conference 2019
มาเริ่มกันเลยจ้าาาา
Intro to LINE Messaging API
การส่งข้อความและโต้ตอบกับ chatbot นั้น เราจะทำผ่าน LINE Messaging API มี 2 รูปแบบ คือ
- Push Message : ส่งจาก chatbot ไปยังผู้ใช้ เช่น จองตั๋วหนัง แล้วระบบส่งตั๋วหนังไปให้ตามเวลา
- Reply Message : เป็นการโต้ตอบจริงๆ เช่น ผู้ใช้ทัก chatbot ก่อน แล้ว chatbot จะโต้ตอบกลับไปหา ไม่ว่าจะเป็นข้อความ รูป สติ๊กเกอร์ และอื่นๆ เช่น คำนวณค่า BMI
Webhook Events
การแชทจะมีแบบ 1:1 คือเราคุยกับบอท และ group & room ลากบอทมาป่วนในกรุ๊ปไลน์
webhook คือ service ที่เราพัฒนาขึ้นมาในรูปแบบ API และมาผูกกับแชทบอทของเรา การทำงานก็คือ จะนำ message ที่ได้ ไปส่งที่ LINE server ก่อน จากนั้นข้อความได้ที่จะถูก forward ไปที่ webhook แล้วทำให้ chatbot เราตื่นมาทำงาน ตาม process ที่เราวางไว้นั่นเอง
webhook events มีทั้งหมด 12 แบบด้วยกัน เช่น ส่งข้อความ เข้ากลุ่ม เพิ่มเพื่อน เดินเข้าใกล้ beacon
สามารถอ่านเพิ่มเติมได้ที่
Message Types
รูปแบบข้อความที่ chatbot ส่งไปได้ นอกจากข้อความยังส่งแบบอื่นๆได้อีก ซึ่งแบ่งเป็นแบบนี้
- Basic Message Types คนส่งได้ บอทก็ส่งได้
- Advanced Message Types บอทส่งได้ คนส่งไม่ได้
- Flex Message มีความยืดหยุ่นในการส่งข้อความ ว่าจะวางยังไงก็ได้ ทำให้ตัวข้อความมีความน่าสนใจยิ่งขึ้น
สามารถอ่านเพิ่มเติมได้ที่นี่
LINE Bot Designer
การสร้างข้อความรูปแบบพิเศษ เราต้องสร้างในรูปแบบ json format อ่าาา อาจจะดูยากไปเนอะ งั้นเอานี้ใช้ tool นี้เลย ใช้งานง่ายมากๆ ลากวางๆ ออกแบบได้โดยไม่ต้องเขียนโค้ด สามารถทำ prototype ให้ทีมดูได้ด้วยนะ ก่อนส่งไปให้ทีม dev เอาไปทำต่อจ้า
LINE Chatbot Setup
มาเป็น LINE Developer เพื่อสร้าง chatbot ตัวแรกกันเถอะ
เข้าไปที่เว็บนี้ แล้ว Log in เพื่อสมัครจ้า ซึ่งทางเรา ขอข้ามจ้าาา น่าจะไม่ยากเกินไป~~
สิ่งแรกที่สร้างกันก็คือ Provider นั่นเอง สิ่งที่แสดงความเป็นเจ้าของเจ้า chatbot นั่นเอง
พอได้ Provider แล้ว เราสร้าง Channel ต่อซึ่ง channel ก็คือ chatbot 1 ตัวนั่นเอง สามารถสร้างได้ 3 แบบคือ LINE Login, Messaging API และ Clova Skill
แน่นอนว่าในที่นี้ เลือก Messaging API สิฮ๊าบบบ เราจะสร้าง chatbot นี่เนอะ กรอกข้อมูลให้เรียบร้อย จากนั้น Add เพื่อนด้วย QR Code สิ่งที่เราพบหลังจากนั้นคือ ข้อความที่มาจาก Greeting messages นั่นเอง เมื่อเราพิมพ์อะไรไปหา chatbot นั้นเราจะได้รับข้อความแบบเดิมทุกครั้ง ซึ่งมาจาก Auto-reply messages นั่นเอง ดังนั้นให้ disble เพื่อไม่ให้ auto reply จ้า
Building Natural Conversation
เราจะได้ใช้ DialogFlow สร้าง chatbot กันล้าววววว ซึ่งมีการทำงาน 2 วิธี คือ
- Rule Base : ให้ตอบกลับตาม keyword ที่เรากำหนดไว้แบบเป๊ะๆ เช่น ขอนํ้าผลไม้แก้วนึง บอทถึงจะเอามาให้ ถ้าบอกว่า ขอนํ้าผลไม้หน่อย บอทจะไม่เข้าใจ
- AI Base : บอทจะเข้าใจเองว่าจะสื่อถึงอะไร เช่น ขอนํ้าผลไม้หน่อย บอทก็จะเสิร์ฟนํ้ามาให้
จากนั้นเข้าไปที่ DialogFlow เพื่อสร้าง Agent ขึ้นมา จากนั้นกด Sign-in เพื่อเข้าระบบ
แล้ว Agent คืออะไรหล่ะ? ในที่นี้ก็คือ chatbot นี่แหละจ้า การสร้าง Agent เราก็จะใส่ default language เป็นภาษาไทย เลือก timeline เป็น GMT+7:00 ส่วน Google Project จะใช้ของเดิม หรือของใหม่ก็ได้ เจ้า DialogFlow จะสร้างให้เราจ้า
ส่วน Intent มาจาก Intention หรือ เจตนา นั่นเอง ทำให้ chatbot สามารถคิด วิเคราะห์ แยกแยะ เจตนาของผู้ใช้นั่นเองงง โดยไม่ต้องเขียนโปรแกรม เช่น เราอยากดูดวงวันนี้ บอทก็จะเอาดวงวันนี้มาให้เรา เป็นต้น
Pre-defined Intents คือ Intent เบื้องต้นที่ DialogFlow เตรียมมาให้เรานั่นเอง มี 2 ตัว คือ
- Welcome Intent : คำทักทาย
- Fallback Intent : ตอบกลับเมื่อบอทไม่เข้าใจในเจตนา จะตกตรงนี้เสมอ
ส่วนประกอบต่างๆใน Intent
- Training phrases : เป็นประโยคที่สอน chatbot ให้เข้าใจเจตนา เช่น ข้อความที่เราคิดว่า user จะพิมพ์มา ยิ่งหลากหลาย ยิ่งดี
- Response : ตอบสนองโดยการส่งข้อความกลับไป สามารถระบุได้หลายประโยค เพื่อให้บอท random response เพื่อตอบกลับไป ซึ่งสามารถทดสอบได้ที่ด้านขวา ประมาณนี้
เรามาสร้าง chatbot ที่คำนวณ BMI กันเถอะ และนี่คือ dialog chatbot ที่เราจะทำกัน
Intent ตัวแรกที่เราจะสร้าง นั่นคือ BMI นั่นเอง และใส่ training phrases ตามแต่จินตนาการ เช่น "BMI", "รู้สึกอ้วนจัง", "ผอมไหมนะ", "หุ่นดีไหม" ยิ่งหลากหลาย ยิ่งดีย์
ส่วนช่อง response คือให้ chatbot ตอบกลับไปว่าอะไร เช่น "ส่งนํ้าหนักเป็น กก และส่วนสูงเป็น ซม" "โปรดระบุนํ้าหนักเป็น kg และส่วนสูงเป็น cm มาหน่อยค่ะ"
จากนั้นกด save เพื่อให้เจ้า Agent ทำการ learning และลองทดสอบกันสักนิด ลองพิมพ์ข้อความที่เรายังไม่ได้สอนมันดู เช่น
และแล้ว Intent ตัวแรกของเราก็ทำเสร็จแล้ว
ตัวต่อมาที่จะทำกันคือ BMI - custom นั่นเอง แต่ย้อนกลับไปที่ diagram แปปนึง
ตัว Intent BMI และ Default Intent นั้น เป็น Intent ชั้นนอก ส่วน BMI - custom ก็จะเป็นชั้นในที่ต่อจาก BMI เนอะ ดังนั้นการสร้าง Intent ที่ชื่อว่า BMI - custom นั้น เอาเม้าส์เราไปวางที่ BMI ให้มันขึ้นคำว่า Add follow-up intent แล้วกดเข้าไป
และเลือก custom เนื่องจากเรานำค่านํ้าหนักและส่วนสูงไปคำนวณต่อ
เสร็จแล้วก็จะได้แบบนี้
จากนั้นเข้าไปที่ intent BMI - custom ใส่ wording นํ้าหนักส่วนสูงลงไปแบบนี้เยยย
และใน DialogFlow จะแยกหน่วยและตัวเลขมาให้เราเลย แต่เราต้องการ value ดังนั้นคลิกกากบาทหน่วยทิ้งจ้า มีทริคแอบแนะนำก็ครืออออ ของเราพอพิมพ์ปุ๊ปเจอหน่วย ไม่เจอเลข พยายามเอา cursor คลุมแค่หน่วย แล้วค่อยคลุมตัวเลขเป็น number แล้วค่อยกาหน่วยทิ้ง
ไปที่ Action and parameters ต่อเลย เราจะมี input สองค่า นั่นคือ นํ้าหนัก และ ส่วนสูง เรามาแยกค่ากันให้เรียบร้อย ให้สีตรงกัน โดยการกดค่าเพื่อเปลี่ยน type แบบนี้
เรา require ค่านํ้าหนักและส่วนสูงเพื่อคำนวณ BMI ดังนั้นเราจึงติ๊กถูกทั้งสองค่าเลย แล้วจะมี prompts ตามหลังมาด้วย ว่าใส่ไม่ครบ
ส่วน response ก็จะให้ยืนยันข้อมูลที่เราส่งไปเนอะ การอ้างอิง parameter ใช้ $
นำหน้าจ้า
จากนั้นกด save และลองเล่นกัน
ด้วยคำถามที่ถามว่าใช่หรือไม่ ถูกหรือเปล่า เราต้องการคำตอบ yes หรือ no ดังนั้นเรามาสร้างตัวต่อไป เป็น BMI - custom - no
เข้าไปดูด้านใน ทาง DialogFlow มี default ของ Training phrases มาให้ด้วยหล่ะ
และใส่ response เพิ่มไป จากนั้นกด save และลองเล่นดูจะได้แบบนี้
สังเกตุว่าเราจะได้ค่านํ้าหนักและส่วนสูงมาแล้วด้วย
และสร้างในชั้นเดียวกันกับ BMI - custom - no ชื่อว่า BMI - custom - yes
เข้าไปดูก็เหมือนของ no ที่มี default มาให้แล้ว
แล้วใส่ response ไป
จากนั้นกด save แล้วลองอีกสักรอบ เมื่อ user เขา confirm แล้ว เอาไปคำนวณยังไงต่อดีนะ?
ตอนนี้ Intent เรามีครบถ้วนแล้วนะ และในแต่ละ Intent จะมี Context ซึ่งเป็นตัวสำคัญ เป็นตัวควบคุม follow-up มีการรับส่ง context หรือบริบทเกิดขึ้น
ซึ่งจะอยู่ด้านบนของ Intent นั้นๆ
มีช่องให้กรอก 2 ช่อง คือ input context รับ context มา และ output context ส่ง context ออกไป ซึ่งมันจะ generate context มาให้เรา เมื่อไปดูที่ลำดับชั้นถัดไปก็คือ BMI - custom ก็จะพบว่าตัว input รับจาก output ของ BMI จริงๆ
ลองทำดูเนอะ ไปที่ BMI - custom - no เรายังอยากให้ user ยังอยู่กับเรา ดังนั้น เราจึงเปลี่ยน reponse text เป็น "รบกวนขอนํ้าหนักเป็น kg และส่วนสูงเป็น cm มาอีกครั้งค่ะ" เพื่อให้เขากรอกใหม่อีกครั้ง พอกรอกใหม่ บอทกลับไม่เข้าใจ เพราะไม่มีการส่ง context กลับเข้ามาที่ BMI - custom ดังนั้นจึงเพิ่ม BMI-followup เข้าไปที่ output context
จึงจะกลับมาเหมือนเดิมได้
แล้วเราจะเอานํ้าหนักและส่วนสูงไปคำนวณต่ออย่างไรหล่ะ? ไปที่ BMI - custom - yes เลื่อนไปที่ Action and parameters จากนั้นสร้างตัวแปร weight กับ height เหมือนเมื่อกี้ แล้ว value มาจากไหนหล่ะ? พิมพ์ #
แล้วตามด้วย input context และ . ชื่อตัวแปรจาก intent ที่แล้ว
จะได้เป็นแบบนี้
แล้วก็กด save จ้า
LINE Integration
เราเอา chatbot ที่ทำเมื่อกี้เข้ากับ LINE กันดีกว่า ไปที่ Integration เลื่อนไปหา LINE แล้วเปิดขึ้นมา จะได้แบบนี้
copy เจ้า Webhook ที่ได้จาก DialogFlow ไปแปะใน LINE ไปที่แท็ป Messaging API ใน chatbot ของเรา จากนั้นก็ enable webhook จ้า
แล้วก็ copy Channel ID, Channel Secret และ Channel Access Token ไปใส่ใน DialogFlow
ถ้าเราอยากเพิ่มความน่าสนใจด้วย message แบบอื่นหล่ะ? เช่น มี button มาเพิ่ม
ไปที่ Intent BMI - custom เลื่อนไปด้านล่างตรง Response ไปดูที่แท็ป LINE
อันนี้จะบอกว่า ถ้าเรา enable จะมี response message ทั้งช่อง default และ LINE ส่งไป 2 ตัวนะ
ตัว DialogFlow รองรับ message ของ LINE 3 ตัว คือ Text, Image และ Button Template มี Card สามารถใส่รูปไปได้ด้วย และ Quick Replies เป็น button ที่มี text ซึ่งในที่นี้เลือก Quick Replies จ้า (แต่เดี๋ยวนี้ก็โอเคอยู่นะ มีให้เลือกน้อย เพราะก่อนหน้านี้ เลือกไปมันก็ไม่ส่งออก ต้องใส่ json ไปแทนอ่ะ)
จากนั้นใส่ title ที่ลอกมาจากแท็ป default และใส่ข้อความลงไปว่า ใช่ หรือ ไม่ใช่
และก็ save และทดสอบดูจ้า
เพิ่มลูกเล่นอีกนิดด้วยการใส่สติ๊กเกอร์ไลน์ ไปที่ BMI - custom - yes เลื่อนไปข้างล่างที่ Responses ไปที่แท็ป LINE แล้วเลือกข้อความเป็น custom payload
ข้างในก้อน json ที่ชื่อว่า line ต้องการ 3 ตัว อันได้แก่ type ในที่นี้คือ sticker, packageId และ stickerId ซึ่งสองตัวหลังสามารถ shopping ดูได้ตามนี้เลย
https://developers.line.biz/media/messaging-api/sticker_list.pdf
ตามความเข้าใจของเรา packageId น่าจะเป็น id ของชุดสติ๊กเกอร์ ส่วน stickerId ก็ตรงตัว id ของสติ๊กเกอร์ตัวน้านนน
ดังนั้นเจ้า custom payload หน้าตาจะเป็นงี้
{
"line": {
"type": "sticker",
"packageId": "11537",
"stickerId": "52002754"
}
}
ผลที่ได้
สิ่งที่ได้ไปคือ static chatbot ตอบคำตอบที่ตายตัวตามที่เรากำหนดไว้ เช่น ถามสำนักงานใหญ่อยู่ไหน ซึ่งมีที่เดียว ก็ตอบไปตามที่เรากำหนดไว้
Fulfillment
สำหรับ developer ถือว่าไม่ใช่ของแถม555555555 เรายังขาดเรื่องการคำนวณ BMI ใช่ไหม ซึ่งส่วนที่สามารถช่วยเราได้คือ fullfillment นั่นเอง
ไปที่ Intent BMI - custom - yes แล้วไป enable webhook ที่ fullfillment ซะ
ไปที่แท็ป Fulfillment จะเจอ 2 ตัว คือ
- webhook : เรามี host และ server เอง
- Inline Editor : อันนี้เป็น host ของ Google ซึ่ง power by Cloud Function for Firebase ไม่ต้องมี server เอง สะดวกสุดๆ ส่วน coding จะอยู่ใน
index.js
ซึ่งเขาทำ default ให้เราแล้ว ดังนั้นเราจึงกด enable แล้วไปที่package.json
แล้วอัพเป็นเวอร์ชั่นใหม่สุดๆจ้า โดยเฉพาะdialogflow-fullfillment
กลับมาที่ index.js
ที่มี code sample อยู่
intentMap
คือ map ตัวที่ intent ที่เราเปิด fullfillment ไว้ กับชื่อ function ที่เราเขียนขึ้นไป
ดังนั้นเราจะเพิ่ม function เข้ามาใน bot ของเราอีก 1 ตัว โดบการเพิ่มไส้ในของ intentMap มาอีก 1 ตัว ซึ่ง parameter ที่ใส่นั่น ตัวแรก คือ ชื่อ Intent ที่เราต้องการทำ fullfillment ในที่นี้คือ BMI - custom - yes นั่นเอง และตัวสุดท้ายคือ ชื่อ function โดยเราสร้าง function ใหม่ที่ชื่อว่า bodyMassIndex
นั่นเอง
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('BMI - custom - yes', bodyMassIndex);
agent.handleRequest(intentMap);
เรามาสร้าง function กันเลย รับค่า agent เข้ามา แล้วจะ response กลับไปเป็นค่า BMI
เรารับก้อน input แบบ Node.js จาก request.body
ซึ่งเป็นค่าที่ถูกส่งจากการ POST แล้วเรานำค่าจาก input context มาคือ ค่า weight
กับ height
จากนั้นเอาไปเข้าสูตร BMI และเอาแค่ทศนิยม 2 ตำแหน่ง พอได้ค่า BMI มาเสร็จก็ให้ Agent ส่งค่า BMI ที่ได้กลับไปจ้า
function bodyMassIndex(agent) {
let weight = request.body.queryResult.parameters.weight;
let height = request.body.queryResult.parameters.height / 100;
let bmi = (weight / (height * height)).toFixed(2);
agent.add(bmi);
}
ถ้าเรานำค่าที่ได้มาเปรียบเทียบ และเอา data จาก database ออกมา ใน Firebase จะมี database 2 แบบด้วยกัน ก็คือ Realtime Database และ Cloud Firestore
ก่อนอื่น เรามา include เจ้า Firebase Admin และ init database เสียก่อน
const admin = require('firebase-admin');
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://<database_name>.firebaseio.com"
});
โดย database_name
นั้น เอาจากชื่อ database ใน Firebase นี่แหละ ในตอนแรกเอามาจาก Realtime Database กันก่อน
จากนั้นเราจะเปลี่ยนจากการพ่น bmi ไปเฉยๆ เป็นการบอก size ว่าค่านี้ ฉันอ้วนยัง
และเราต้องสร้าง database ใน Realtime Database ให้สอดคล้องกันด้วย แบบนี้
สุดท้ายต่อ Realtime Database เพื่อนำค่าออกมาแสดง แบบนี้
function bodyMassIndex(agent) {
let weight = request.body.queryResult.parameters.weight;
let height = request.body.queryResult.parameters.height / 100;
let bmi = (weight / (height * height)).toFixed(2);
let result = "none";
if (bmi < 18.5) {
result = "xs";
} else if (bmi >= 18.5 && bmi < 23) {
result = "s";
} else if (bmi >= 23 && bmi < 25) {
result = "m";
} else if (bmi >= 25 && bmi < 30) {
result = "l";
} else if (bmi >= 30) {
result = "xl";
}
agent.add(bmi);
return admin.database().ref("bmi").child(result).once('value')
.then(snapshot => {
agent.add(snapshot.val());
});
}
และสร้าง database ที่ Cloud Firestore มี object พร้อมคำแปลพร้อมจ้า
ต่อกับ Cloud Firestore แบบนี้
function bodyMassIndex(agent) {
let weight = request.body.queryResult.parameters.weight;
let height = request.body.queryResult.parameters.height / 100;
let bmi = (weight / (height * height)).toFixed(2);
let result = "none";
if (bmi < 18.5) {
result = "xs";
} else if (bmi >= 18.5 && bmi < 23) {
result = "s";
} else if (bmi >= 23 && bmi < 25) {
result = "m";
} else if (bmi >= 25 && bmi < 30) {
result = "l";
} else if (bmi >= 30) {
result = "xl";
}
agent.add(bmi);
return admin.firestore().collection('bmi').doc(result).get
.then( doc => {
agent.add(doc.data().description);
});
}
Fullfillment ส่งได้แต่ text อย่างเดียวไหมอ่ะ? ส่งได้หลายอย่างเลยหล่ะ
ซึ่งในตอนที่เรามา fullfillment ใหม่ๆนั้น จะเจอหน้า package.json
ใช่ม้าาา และใน dependencies นั้น ที่พี่ตี๋บอกให้อัพเดตอันนึง จะได้ไหมเอ่ยย เจ้า "dialogflow-fulfillment"
นั่นเอง โดยทำสิ่งที่จะทำต่อไปนี้ได้ เมื่อมันเป็น version 0.6.1
ขึ้นไปเท่านั้นนาจา
กลับมาที่ index.js ด้านบนๆเราจะเห็นสิ่งนี้
const {Card, Suggestion} = require('dialogflow-fulfillment');
เพิ่มเจ้า instant ตัวหนึ่ง ชื่อว่า น้อง Payload
const {Card, Suggestion, Payload} = require('dialogflow-fulfillment');
จากนั้นเพิ่มตัวแปร ที่ชื่อว่า packageId
และ stickerId
ลงไป เพราะในที่นี้จะส่งเป็นสติ๊กเกอร์เนอะ แบบนี้
let result = "none";
let packageId = "1";
let stickerId = "1";
if (bmi < 18.5) {
result = "xs";
packageId = "11538";
stickerId = "51626519";
} else if (bmi >= 18.5 && bmi < 23) {
result = "s";
packageId = "11537";
stickerId = "52002741";
} else if (bmi >= 23 && bmi < 25) {
result = "m";
packageId = "11537";
stickerId = "52002745";
} else if (bmi >= 25 && bmi < 30) {
result = "l";
packageId = "11537";
stickerId = "52002762";
} else if (bmi >= 30) {
result = "xl";
packageId = "11538";
stickerId = "51626513";
}
จากนั้นสร้าง payload ขึ้นมา ตามโครงสร้าง object ของ message ตัวนั้นๆ
let payloadJson = {
"type": "sticker",
"packageId": packageId,
"stickerId": stickerId
};
ดึง Payload ด้านบนมา สร้างใหม่ มี 3 parameters เช่นกัน parameter แรกใส่คำว่า LINE, parameter ต่อมาใส่ json payload และ parameter สุดท้าย บอกว่าให้ส่งข้อความนะ
let playload = new Payload("LINE", payloadJson, {sendAsMessage: true});
แล้วเอา playload ไปส่งค่าไปยัง user
return admin.firestore().collection('bmi').doc(result).get.then( doc => {
agent.add(playload);
agent.add(doc.data().description);
});
โค้ดทั้งหมดจ้า
ผลที่ได้จ้า
ปล. ของเราไปพังที่ Cloud Firebase เดี๋ยวไปดูย้อนหลังอีกรอบนึงว่าเกิดจากอะไร แต่ใน Realtime Database เรียกได้ตามปกติ
และจริงๆแล้ว Blog Tutorial การสร้างน้องบอทจะอยู่ที่นี่
เรียนจบแล้วพยายามสรุปเป็นหน้าเดียว เผื่อเอาไปใช้ต่ออย่างถูกวิธี เพราะทำน้องหมีตัวแตกพังตรงเชื่อม intent กันนี่แหละ
ถ้าอยากอ่านต่อ ไปตรงนี้เลยจ้า
สุดท้าย ฝากร้านจ้า