Это чат-бот на базе Lex, который будет подсчитывать калории, потраченные при поездках в различные рестораны быстрого питания. Он активируется с помощью чат-бота FB Messenger, доступ к которому можно получить со страницы Facebook или через приложение Messenger на вашем телефоне.
Оглавление
Этот бот использует AWS Lex — сервис, который содержит интеллект, позволяющий расшифровывать запросы пользователей и инициировать намерения на основе данных, представленных в моделях. Затем намерения вызывают лямбда-функции, которые содержат бизнес-логику, специфичную для намерения.
В настоящее время процесс NLU сортирует множество различных намерений. Вот «основные функции» бота.
Есть также намерения, которые дополняют основные функции.
Кроме того, есть намерения, которые формируют «личность» бота. Они были созданы на основе реального использования пользователями и предотвращают использование общего сообщения об ошибке для ответа.
В рамках каждого намерения предоставляются образцы высказываний, которые создают потенциальные предложения, которые может предоставить пользователь. Значение слота (т. е. крупная сошка) передается лямбда-функции как уникальный атрибут.
Вы можете получить сводную информацию из интерфейса командной строки AWS, выполнив следующую команду.
aws lex-models get-bot --name FastFoodChecker --version-or-alias PROD
Это комбинация образцов высказываний и слотов, которые определяют, какое намерение будут вызывать модели NLU. Они сохраняются в Lex и используются для обучения моделей.
В настоящее время здесь представлены пользовательские слоты, используемые намерениями.
Элемент не обязательно должен быть указан в слоте, чтобы NLU мог поместить в него значение. Однако если данных мало, это может ухудшить интерпретацию NLU запросов пользователя.
Для удобства использования чат-бота необходимо естественное взаимодействие с пользователем. Одна из ключевых концепций заключается в том, как объединить несколько слотов в одно намерение. Например, пользователь может спросить: «Сколько калорий в Биг-Маке, картофеле фри и коле?» Это три разных элемента, каждый из которых необходимо проанализировать. В этом чат-боте основная обработка имеет множество различных слотов, которые соответствуют намерениям. Например, вот слоты, которые соответствуют намерению GetCalories.
Здесь следует отметить несколько моментов.
В приведенном выше примере запроса модели NLU будут анализировать данные из высказывания в три разных слота («Еда», «Дополнительно» и «Напиток»).
Порядок слотов не имеет значения для синтаксического анализа, но он определяет, каким будет следующий ответ (слот 1 — в каком ресторане вы находитесь?).
Есть два слота, которые для этого не требуются — Ketchup и PacketsKetchup. Эта дополнительная информация запрашивается, если в качестве гарнира запрашивается картошка фри. Это управляется кодом функции Lambda, которая вызывается в ловушке кода проверки.
Вся логика формирования ответов на различные намерения обрабатывается серией лямбда-функций. Выбор лямбда-функции для вызова определяется в Lex и задается на уровне намерения. Это позволяет реализовать модульность приложения, сохраняя при этом легкость функций.
В Lex есть два разных места, которые могут вызывать лямбда-функцию. Первый осуществляется посредством базовой проверки, а имя атрибута, которое его идентифицирует, называется invoctionSource. Для этого есть два потенциальных значения — DialogCodeHook и FulfillmentCodeHook. Вот где эти лямбда-функции указаны в Lex Bot.
Первый раскрывающийся список — «Проверка» — вызывает лямбда-функцию каждый раз при вызове бота. Атрибут, который он передает, называется DialogCodeHook. Второй раскрывающийся список — «Выполнение» — вызывается только после заполнения обязательных слотов и завершения проверки из первоначального вызова. Это позволяет использовать разные функции, обеспечивая лучшую масштабируемость при создании бота.
Вот обзор каждой написанной на данный момент функции.
Lambda.js — основная функция, выполняющая базовую проверку запросов, полученных только в режиме DialogCodeHook.
Calculate.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.
Управление большими пользовательскими слотами может оказаться затруднительным, особенно если данные являются динамическими. Основной поиск продуктов питания содержит несколько сотен уникальных значений, число которых увеличивается в зависимости от использования пользователем. Процесс создания этого слота автоматизирован, а данные для пользовательского слота берутся из объекта данных food.json. Это делается через интерфейс командной строки AWS, который может загружать их непосредственно из командной строки. Все файлы для справки содержатся в каталоге [slots}(https://github.com/terrenjpeterson/culturalcounter/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 "
}
Лямбда-функции в этом боте полностью не сохраняют состояние, поэтому любые данные предыдущих вызовов должны проходить через объект запроса.
Одной из функций основных пользовательских интерфейсов чат-ботов (Messenger, Slack и т. д.) являются кнопки. Они уменьшают усилия пользователя, предоставляя ряд подобных опций.
Каждая платформа обмена сообщениями имеет собственную реализацию этого шаблона, и вот что использует Messenger. Lex обрабатывает преобразование, чтобы привести кнопки в правильный формат, а внутри Lex атрибуту responseCard необходимо предоставить подробную информацию о кнопке.
Модификация Lex осуществляется полностью через консоль. Лямбда-функции, обслуживающие бизнес-логику, размещаются в лямбда-версии AWS и развертываются с хоста 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. Это включает в себя наличие хотя бы одного тестового условия для каждой лямбда-функции, чтобы гарантировать правильность развертывания.
Одна из тем дизайна ботов — это индивидуальность. При разработке намерений следует учитывать все возможные вопросы, которые может задать пользователь. Это должно включать вопросы не по теме, такие как «как тебя зовут» или эмоциональные ответы, такие как «о нет» или «ты отстой». Их легко кодировать — обычно это простой запрос-ответ без использования слотов, и это делает диалоги более естественными.
Например, вот краткий ответ, закодированный в функции 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. В рамках этого мне нужно было создать сайт для общественной поддержки приложения. Эта работа находится в стадии разработки и называется калорииcountbot.com. Он размещен на s3, а исходный код находится в папке /website.