这是一个基于 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 文件夹中。