นี่คือแชทบอตที่ใช้ Lex ซึ่งจะคำนวณแคลอรี่ที่ได้จากการเดินทางไปร้านอาหารฟาสต์ฟู้ดต่างๆ เปิดใช้งานได้จาก FB Messenger Chatbot ที่สามารถเข้าถึงได้จากเพจ Facebook หรือผ่านแอพ Messenger บนโทรศัพท์ของคุณ
สารบัญ
บอทนี้ใช้ AWS Lex ซึ่งเป็นบริการที่ประกอบด้วยข้อมูลอัจฉริยะเพื่อให้สามารถถอดรหัสคำขอของผู้ใช้และทริกเกอร์ Intents ตามข้อมูลที่ให้ไว้ในโมเดล จากนั้น Intents จะเรียกใช้ฟังก์ชัน lambda ที่มีตรรกะทางธุรกิจเฉพาะสำหรับ Intent
ปัจจุบันมีจุดประสงค์ที่แตกต่างกันมากมายที่กระบวนการ NLU จัดเรียงไว้ นี่คือ "ฟังก์ชันหลัก" ของบอท
นอกจากนี้ยังมีเจตนาที่เสริมคุณสมบัติหลักอีกด้วย
จากนั้นก็มีเจตนาที่สร้าง 'บุคลิกภาพ' ของบอท สิ่งเหล่านี้ถูกสร้างขึ้นตามการใช้งานจริงของผู้ใช้ และป้องกันไม่ให้มีการใช้ข้อความแสดงข้อผิดพลาดทั่วไปในการตอบสนอง
ภายในแต่ละเจตนา มีการจัดเตรียมตัวอย่างคำพูดที่สร้างประโยคที่เป็นไปได้ที่ผู้ใช้อาจจัดเตรียมไว้ให้ ค่าของช่อง (เช่น Large Fry) จะถูกส่งต่อไปยังฟังก์ชัน lambda ซึ่งเป็นคุณลักษณะเฉพาะ
คุณสามารถรับข้อมูลสรุปจาก AWS CLI ได้โดยดำเนินการคำสั่งต่อไปนี้
aws lex-models get-bot --name FastFoodChecker --version-or-alias PROD
เป็นการผสมผสานระหว่างคำพูดและช่องตัวอย่างที่กำหนดว่าโมเดล NLU จะใช้เจตนาใด สิ่งเหล่านี้ได้รับการบำรุงรักษาใน Lex และใช้สำหรับการฝึกโมเดล
ปัจจุบัน นี่คือช่องแบบกำหนดเองที่ Intent ใช้
ไม่จำเป็นต้องระบุรายการในช่องเพื่อให้ NLU ใส่ค่าลงไป อย่างไรก็ตาม หากข้อมูลกระจัดกระจาย ก็อาจทำให้วิธีที่ NLU ตีความคำขอของผู้ใช้ลดลง
การใช้งานแชทบอทต้องมีการโต้ตอบตามธรรมชาติจึงจะเกิดขึ้นกับผู้ใช้ แนวคิดสำคัญประการหนึ่งคือการรวมช่องหลายช่องไว้ในเจตนาเดียว ตัวอย่างเช่น ผู้ใช้อาจถามว่า "บิ๊กแมค มันฝรั่งทอด และโค้กมีแคลอรี่เท่าไร" นั่นคือสามรายการที่แตกต่างกันซึ่งแต่ละรายการต้องแยกวิเคราะห์ออก ภายในแชทบอทนี้ การประมวลผลหลักมีช่องต่างๆ มากมายที่แมปเข้ากับเจตนา ตัวอย่างเช่น นี่คือช่องที่แมปเข้ากับเจตนาของ GetCalories
มีสองสิ่งที่ควรทราบในเรื่องนี้
ในคำขอตัวอย่างข้างต้น แบบจำลอง NLU จะแยกวิเคราะห์ข้อมูลจากคำพูดออกเป็นสามช่องที่แตกต่างกัน (อาหาร อาหารเพิ่มเติม และเครื่องดื่ม)
ลำดับของช่องไม่สำคัญต่อการแยกวิเคราะห์ แต่จะขับเคลื่อนสิ่งที่จะตอบสนองต่อไป (ช่องที่ 1 - คุณอยู่ที่ร้านอาหารไหน)
มีสองช่องที่ไม่จำเป็นในจุดประสงค์นี้ - ซอสมะเขือเทศและ PacketsKetchup ข้อมูลเสริมนี้จะถูกถามหากถามถึงมันฝรั่งทอดเป็นเครื่องเคียง สิ่งนี้ขับเคลื่อนโดยโค้ดในฟังก์ชัน Lambda ที่ถูกเรียกใช้ใน Hook โค้ดการตรวจสอบ
ตรรกะทั้งหมดในการกำหนดการตอบสนองต่อเจตนาที่แตกต่างกันได้รับการประมวลผลในชุดฟังก์ชันแลมบ์ดา ฟังก์ชัน lambda ใดที่จะเรียกใช้ได้รับการจัดการภายใน Lex และตั้งค่าที่ระดับเจตนา ช่วยให้สามารถสร้างโมดูลาร์ภายในแอปพลิเคชันได้ ทำให้ฟังก์ชันมีน้ำหนักเบา
มีสองจุดที่แตกต่างกันภายใน Lex ที่สามารถเรียกใช้ฟังก์ชันแลมบ์ดาได้ วิธีแรกคือผ่านการตรวจสอบขั้นพื้นฐาน และชื่อแอตทริบิวต์ที่ระบุเรียกว่า invocationSource มีค่าที่เป็นไปได้สองค่าสำหรับสิ่งนี้ - DialogCodeHook และ FulfillmentCodeHook นี่คือตำแหน่งที่ระบุฟังก์ชัน Lambda เหล่านี้ใน Lex Bot
เมนูแบบเลื่อนลงเมนูแรกคือการตรวจสอบความถูกต้อง และเรียกใช้ฟังก์ชัน lambda ทุกครั้งที่มีการเรียกบอต คุณลักษณะที่ส่งผ่านเรียกว่า DialogCodeHook เมนูแบบเลื่อนลงที่สองคือ Fulfillment และจะถูกเรียกเมื่อสล็อตบังคับเสร็จสมบูรณ์แล้วเท่านั้น และการตรวจสอบความถูกต้องจากการเรียกครั้งแรกเสร็จสมบูรณ์ ซึ่งช่วยให้ฟังก์ชันต่างๆ แตกต่างกัน ช่วยให้สามารถปรับขนาดได้ดีขึ้นในการสร้างบอท
นี่คือภาพรวมของแต่ละฟังก์ชันที่เขียนอยู่ในปัจจุบัน
lambda.js - ฟังก์ชั่นหลักที่จัดการการตรวจสอบพื้นฐานสำหรับการสืบค้น ซึ่งมาในโหมด DialogCodeHook เท่านั้น
Calculator.js - การคำนวณการตอบสนองสำหรับแคลอรี่จริงในมื้ออาหารได้รับการจัดการโดยฟังก์ชั่นนี้ และมาจาก FulfillmentCodeHook
pizza.js - จัดการจุดประสงค์ในการคำนวณแคลอรี่ในพิซซ่า รวมถึงจุดประสงค์ - WhatPizzaTypes
misc.js - จัดการกับจุดประสงค์ง่ายๆ เช่น ความช่วยเหลือ การแนะนำ และรายละเอียดเพิ่มเติมเกี่ยวกับมื้ออาหาร
chinese.js - จัดการกับความต้องการเกี่ยวกับอาหารจีน และเชื่อมต่อช่องต่างๆ เข้าด้วยกันเพื่อสร้างมื้ออาหาร
ฟังก์ชั่นหลักของบอทนี้คือสามารถตอบคำถามเกี่ยวกับจำนวนแคลอรี่ในมื้ออาหารต่างๆ ได้ แม้ว่าช่องที่ Lex ใช้จะมีประโยชน์ในการฝึกโมเดล NLU แต่ก็ไม่มีความสามารถในการทำหน้าที่เป็นไฟล์ค้นหา นั่นคือสิ่งที่วัตถุ json เข้ามาซึ่งถูกจัดเก็บไว้ในโฟลเดอร์ /src/data/
นี่คือตัวอย่างรูปแบบ
[
{
" restaurant " : " Chipotle " ,
" foodItems " :[
{ " foodName " : " Chicken Burrito " , " foodType " : " Burrito " , " protein " : " chicken " , " calories " :975},
{ " foodName " : " Steak Burrito " , " foodType " : " Burrito " , " protein " : " steak " , " calories " :945},
{ " foodName " : " Carnitas Burrito " , " foodType " : " Burrito " , " protein " : " carnitas " , " calories " :1005},
ฟังก์ชันแลมบ์ดาอ้างอิงถึงออบเจ็กต์เหล่านี้เพื่อตอบสนองต่อคำถามต่างๆ และเพื่อคำนวณการบริโภคแคลอรี่สำหรับผู้ใช้
รายการอาหารแต่ละรายการอาจซ้ำกันเพื่อการสะกดและวลีที่แตกต่างกันที่ใช้ในการดึงข้อมูล ตัวอย่างเช่น.
{ " foodName " : " Fries " , " calories " :340},
{ " foodName " : " Fry " , " calories " :340},
{ " foodName " : " Frys " , " calories " :340},
{ " foodName " : " French Fries " , " calories " :340},
{ " foodName " : " French Fry " , " calories " :340},
{ " foodName " : " Medium Fries " , " calories " :340},
{ " foodName " : " Medium Fry " , " calories " :340},
นอกจากนี้ยังมีตารางค้นหาซอส น้ำสลัด และการปรับเปลี่ยนรายการแต่ละรายการอีกด้วย ตัวอย่างเช่น.
[
{
" dressingName " : " Ranch " ,
" calories " :200,
" carbs " :11,
" restaurantNames " :[ " McDonalds " ]
},
{
" dressingName " : " French " ,
" calories " :300,
" carbs " :22,
" restaurantNames " :[ " McDonalds " ]
},
เนื่องจากโมเดล NLU ไม่ได้แก้ไขการสะกดที่ผู้ใช้ให้มา จึงขึ้นอยู่กับฟังก์ชัน Lambda ที่จะจัดการตรรกะส่วนนี้
การจัดการช่องที่กำหนดเองขนาดใหญ่อาจเป็นเรื่องยาก โดยเฉพาะอย่างยิ่งหากข้อมูลเป็นแบบไดนามิก การค้นหาอาหารหลักมีค่าที่ไม่ซ้ำกันหลายร้อยค่า และเพิ่มขึ้นตามการใช้งานของผู้ใช้ กระบวนการสร้างช่องนี้เป็นไปโดยอัตโนมัติ และข้อมูลสำหรับช่องที่กำหนดเองจะนำมาจากออบเจ็กต์ข้อมูล Foods.json ซึ่งดำเนินการผ่าน AWS CLI ที่สามารถโหลดสิ่งเหล่านี้ได้โดยตรงจากบรรทัดคำสั่ง ไฟล์ทั้งหมดอยู่ในไดเรกทอรี [slots}(https://github.com/terrenjpeterson/แคลอรี่counter/tree/master/src/slots) เพื่อใช้อ้างอิง นี่คือขั้นตอนที่ใช้ในการสร้าง
ไวยากรณ์มีลักษณะเช่นนี้
# foods.json is the data object that will be passed to the lambda function
request= $( < foods.json )
# invoke the lambda function from the command line and write the output to output.json
aws lambda invoke --function-name convertFoodsObjForSlot --payload " $request " output.json
data= $( < output.json )
# invoke lex to create a new version of the FoodEntreeNames custom slot using the data from output.json
aws lex-models put-slot-type --name FoodEntreeNames --checksum < enter latest checksum here > --enumeration-values " $data " >> sysout.txt
นอกจากนี้ ค่าตรวจสอบยังมาจากการปรับใช้สล็อตที่กำหนดเองก่อนหน้านี้ คุณสามารถค้นหาเช็คซัมปัจจุบันสำหรับสล็อตได้ด้วยคำสั่ง get-slot-type
# find the latest information about a custom slot
aws lex-models get-slot-type --name FoodOptions --slot-type-version ' $LATEST '
กุญแจสำคัญในการสนทนาระยะยาวอย่างมีประสิทธิภาพระหว่างผู้ใช้และบอทคือการจัดการบริบทของการสนทนา ตัวอย่างเช่น กล่องโต้ตอบอาจดำเนินไปเป็นเวลาหลายนาที และก่อให้เกิดเจตนาหลายอย่าง
ส่วนหนึ่งของการอำนวยความสะดวกคือการออกแบบความลื่นไหลของการสนทนา ข้อความแสดงข้อผิดพลาดไม่ควรฉับพลันเกินไป และควรนำผู้ใช้ไปสู่ข้อความค้นหาอื่น เจตนาควรส่งข้อมูลระหว่างกันด้วย ซึ่งสามารถทำได้โดยการบันทึกข้อมูลเซสชันเมื่อดำเนินการตามเจตนา ซึ่งจะช่วยให้มีเจตนาต่อไปในการดึงข้อมูลและไม่ต้องการให้ผู้ใช้ทำซ้ำกับคำขอแต่ละครั้ง
ในตัวอย่างข้างต้น การสนทนาเริ่มต้นด้วยการที่ผู้ใช้ระบุว่าพวกเขากำลังรับประทานอาหารที่ร้านอาหารใด สิ่งนี้จะคงอยู่ในเซสชันตามเจตนาของ FoodTypeOptions กล่องโต้ตอบจะเปลี่ยนไปที่รายละเอียดของมื้ออาหาร แต่ชื่อร้านอาหารจะถูกบันทึกไว้ นอกจากนี้ การตอบสนองเบื้องต้นต่อจำนวนแคลอรี่นั้นสั้น แต่ให้คำอธิบายที่ละเอียดมากขึ้นหากผู้ใช้พูดว่า 'รายละเอียดเพิ่มเติม' เป็นอีกครั้งที่ข้อมูลถูกจัดเก็บไว้ในข้อมูลเซสชัน และถูกส่งกลับโดยเป็นส่วนหนึ่งของกรอบงาน Lex นี่คือตัวอย่างของวัตถุชิ้นหนึ่ง
{
" messageVersion " : " 1.0 " ,
" invocationSource " : " FulfillmentCodeHook " ,
" userId " : " 1712299768809980 " ,
" sessionAttributes " : {
" restaurantName " : " Burger King " ,
" foodName " : " Whopper " ,
" foodCalories " : " 660 " ,
" extraName " : " Onion Rings " ,
" extraCalories " : " 410 " ,
" drinkCalories " : " 310 " ,
" drinkName " : " 32 oz. Large Coke " ,
" totalCalories " : " 1380 "
},
" bot " : {
" name " : " FastFoodChecker " ,
" alias " : " PROD " ,
" version " : " 42 "
},
" outputDialogMode " : " Text " ,
" currentIntent " : {
" name " : " DailyIntakeAnalysis " ,
" slots " : {},
" slotDetails " : {},
" confirmationStatus " : " None "
},
" inputTranscript " : " Analyze my meal "
}
ฟังก์ชัน lambda ในบอตนี้ไม่มีสถานะโดยสิ้นเชิง ดังนั้นข้อมูลใดๆ จากการเรียกใช้ก่อนหน้านี้จะต้องมาทางออบเจ็กต์คำขอ
หนึ่งในคุณสมบัติในอินเทอร์เฟซผู้ใช้แชทบอทหลัก (Messenger, Slack ฯลฯ) ก็คือปุ่มต่างๆ สิ่งเหล่านี้ช่วยลดความพยายามของผู้ใช้ด้วยการจัดเตรียมตัวเลือกต่างๆ มากมายเช่นนี้
แต่ละแพลตฟอร์มการส่งข้อความมีการใช้งานรูปแบบนี้ของตัวเอง และนี่คือสิ่งที่ Messenger ใช้ Lex จัดการการแปลเพื่อให้ปุ่มอยู่ในรูปแบบที่ถูกต้อง และภายใน Lex แอตทริบิวต์ responseCard จะต้องระบุรายละเอียดเฉพาะของปุ่ม
การปรับเปลี่ยน Lex ทำได้อย่างสมบูรณ์ผ่านคอนโซล ฟังก์ชัน lambda ที่รองรับตรรกะทางธุรกิจนั้นโฮสต์อยู่ใน AWS lambda และปรับใช้จากโฮสต์ EC2
สคริปต์การปรับใช้แบบเต็มคือ /src/build.sh แต่คุณสามารถดูภาพรวมโดยย่อได้ในคำแนะนำต่อไปนี้
# this creates the build package as a zip file containing the code and relevant data objects
zip -r foodbot.zip lambda.js data/restaurants.json data/foods.json data/drinks.json
# this CLI command copies the build package to an s3 bucket for staging
aws s3 cp foodbot.zip s3://fastfoodchatbot/binaries/
# this CLI command takes the package from the s3 bucket, and overlays the lambda function 'myCalorieCounterGreen'
aws lambda update-function-code --function-name myCalorieCounterGreen --s3-bucket fastfoodchatbot --s3-key binaries/foodbot.zip
# this CLI command invokes the lambda function with the data object read into request, and writes out a response to the testOutput data object.
aws lambda invoke --function-name myCalorieCalculatorGreen --payload " $request " testOutput.json
กระบวนการนี้เกิดขึ้นซ้ำสำหรับแต่ละฟังก์ชันแลมบ์ดาที่ Lex เรียก ซึ่งรวมถึงการมีเงื่อนไขการทดสอบอย่างน้อยหนึ่งเงื่อนไขสำหรับแต่ละฟังก์ชัน lambda เพื่อให้แน่ใจว่าการปรับใช้ทำอย่างถูกต้อง
หนึ่งในหัวข้อในการออกแบบบอทคือการมีบุคลิกภาพ สิ่งที่ต้องพิจารณาเมื่อออกแบบ Intent คือคำถามที่เป็นไปได้ทั้งหมดที่ผู้ใช้อาจถาม ซึ่งควรรวมถึงคำถามนอกประเด็น เช่น 'คุณชื่ออะไร' หรือการตอบสนองทางอารมณ์ เช่น 'โอ้ ไม่' หรือ 'คุณมันห่วย' สิ่งเหล่านี้ง่ายต่อการเขียนโค้ด - โดยปกติจะเป็นเพียงแค่การตอบกลับคำของ่ายๆ โดยไม่มีช่องที่เกี่ยวข้อง และมีแนวโน้มที่จะทำให้กล่องโต้ตอบเป็นธรรมชาติมากขึ้น
ตัวอย่างเช่น ต่อไปนี้เป็นคำตอบสั้นๆ ที่เข้ารหัสไว้ในฟังก์ชัน misc.js ซึ่งจะตอบกลับเมื่อมีคนถามว่าชื่อบอทคืออะไร ในแบบจำลอง คำพูดของ 'คุณชื่ออะไร' จะช่วยแก้ปัญหานี้ได้
if (intentName === ' MyName ' ) {
console.log( " user requested bot name " ) ;
return getBotName(intentRequest, callback) ;
}
...
function getBotName(intentRequest, callback) {
const sessionAttributes = intentRequest.sessionAttributes || {} ;
var botResponse = " My name is Chuck. I'm a chatbot that helps people sort out " +
" fast food options. Talking about food all day makes me hungry!!! " ;
callback(close(sessionAttributes, ' Fulfilled ' ,
{ contentType: ' PlainText ' , content: botResponse })) ;
}
ในส่วนหนึ่งของความพยายามครั้งแรก ฉันพยายามเผยแพร่แชทบอทนี้ไปยังร้านค้าหย่อน ส่วนหนึ่งของสิ่งนั้น ฉันจำเป็นต้องสร้างเว็บไซต์เพื่อการสนับสนุนสาธารณะของแอป อยู่ในระหว่างดำเนินการ และเรียกว่า calalocountbot.com โฮสต์โดย s3 และแหล่งที่มาอยู่ในโฟลเดอร์ /website