這是一個基於 Lex 的聊天機器人,它將計算前往不同快餐店所產生的卡路里。它是透過 FB Messenger 聊天機器人啟用的,可以透過 Facebook 頁面或手機上的 Messenger 應用程式存取該聊天機器人。
目錄
該機器人使用 AWS Lex - 一種包含智慧的服務,能夠根據模型中提供的數據破解用戶請求並觸發意圖。然後,意圖呼叫包含特定於意圖的業務邏輯的 lambda 函數。
目前,NLU 流程可分為許多不同的意圖。這是機器人的「核心功能」。
還有一些意圖可以補充核心功能。
然後是形成機器人「個性」的意圖。這些是根據真實用戶使用情況創建的,並防止使用通用錯誤訊息來回應。
在每個意圖中,提供了建構使用者可以提供的潛在句子的範例話語。槽的值(即 Large Fry)會作為唯一屬性傳遞給 lambda 函數。
您可以執行以下命令從 AWS CLI 取得摘要資訊。
aws lex-models get-bot --name FastFoodChecker --version-or-alias PROD
它是樣本話語和槽的組合,決定 NLU 模型將呼叫哪個意圖。這些在 Lex 中維護,並用於訓練模型。
目前,以下是意圖使用的自訂插槽。
不需要在槽中指定項,NLU 即可將值放入其中。但是,如果資料稀疏,則可能會降低 NLU 解釋使用者請求的方式。
聊天機器人的可用性需要與使用者進行自然互動。一個關鍵概念是如何將多個插槽合併到一個意圖。例如,用戶可以詢問“巨無霸、薯條和可樂中有多少卡路里?”這是三個不同的項目,每個項目都需要解析出來。在這個聊天機器人中,主要處理有許多不同的槽映射到意圖。例如,以下是映射到 GetCalories 意圖的插槽。
其中有幾點要注意。
在上面的範例請求中,NLU 模型會將話語中的資料解析為三個不同的槽(食物、額外和飲料)。
槽順序對於解析並不重要,但它確實會影響下一個回應(槽 1 - 您在哪家餐廳?)
此意圖中不需要兩個槽 - Ketchup 和 PacketsKetchup。如果需要薯條作為配菜,則需要提供此可選資訊。這是由驗證程式碼掛鉤中呼叫的 Lambda 函數中的程式碼所驅動的。
制定對不同意圖的反應的所有邏輯都是在一系列 lambda 函數中處理的。要呼叫哪個 lambda 函數在 Lex 中管理,並在意圖層級設定。這使得能夠在應用程式中建立模組化,從而保持功能的輕量級。
Lex 中有兩個不同的位置可以呼叫 lambda 函數。第一種是透過基本驗證,標識它的屬性名稱稱為 invokingSource。有兩個潛在值 - DialogCodeHook 和 FulfillmentCodeHook。以下是 Lex Bot 中指定這些 Lambda 函數的位置。
第一個下拉清單是“驗證”,每次呼叫機器人時都會呼叫 lambda 函數。它傳遞的屬性稱為 DialogCodeHook。第二個下拉清單是“履行”,只有在完成強制槽並且初始呼叫的驗證完成後才會呼叫。這允許功能不同,從而在構建機器人時實現更好的可擴展性。
以下是目前編寫的每個函數的概述。
lambda.js - 處理查詢的基本驗證的主函數,僅在 DialogCodeHook 模式下取得。
calculate.js - 計算飲食中實際卡路里的反應由函數處理,並由 FulfillmentCodeHook 提供。
Pizza.js - 處理計算披薩卡路里的意圖,包括意圖 - WhatPizzaTypes。
Misc.js - 處理簡單的意圖,例如幫助、介紹和有關膳食的更多細節。
chinese.js - 處理中餐的意圖,並將不同的槽耦合在一起形成一頓飯。
機器人的核心功能是能夠回答不同餐點中含有多少卡路里的查詢。雖然 Lex 使用的槽有助於訓練 NLU 模型,但它們無法用作查找文件。這就是儲存在 /src/data/ 資料夾中的 json 物件的來源。
這是格式範例。
[
{
" 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},
lambda 函數引用這些物件來回應不同的查詢,並計算使用者的卡路里消耗。
每個食物項目可以針對用於檢索的不同拼字和短語進行重複。例如。
{ " 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 完成的,AWS CLI 可以直接從命令列載入這些內容。所有文件都包含在 [slots}(https://github.com/terrenjpeterson/caloriecounter/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 函數都會重複此過程。這包括為每個 lambda 函數至少設定一個測試條件,以確保部署正確完成。
機器人設計的主題之一是擁有個性。設計意圖時需要考慮的是使用者可能會問的所有問題是什麼。這應該包括離題的問題,例如“你叫什麼名字”或情緒反應,例如“哦不”或“你很糟糕”。這些很容易編碼 - 通常只是一個簡單的請求回應,不涉及槽,並且確實會使對話框更加自然。
例如,以下是在 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 })) ;
}
作為最初工作的一部分,我試圖將此聊天機器人發佈到 Slack 商店。作為其中的一部分,我需要建立一個網站以供公眾支持該應用程式。這是一項正在進行中的工作,名為 caloriecountbot.com。它由 s3 託管,來源位於 /website 資料夾中。