Често установявам, че изграждам умение за Alexa, когато искам да си поиграя с API. Гласовият интерфейс е лесен за започване и мога да създам нещо, без да се притеснявам за визуален дизайн или CSS :) Преди известно време създадох пакет Dead or Alive, който е на npm… нека да видим какво е необходимо, за да го превърна в игра за Alexa, в която потребителят трябва да определи дали няколко знаменитости са мъртви или живи...

Ето демонстрация на готовата статия, работеща в конзолата за разработчици на Alexa:

Дизайн на играта

В моята проста игра потребителят играе три рунда. Във всеки кръг ги питат дали дадена знаменитост е мъртва или жива. Играта проверява техния отговор, като използва моя пакет жив или мъртъв, който от своя страна използва Wikipedia като източник на данни. Alexa отговаря, за да каже на потребителя дали е прав или не и да му прочете кратко биографично резюме за знаменитостта. Всеки правилен отговор носи една точка и след три кръга потребителят научава крайния си резултат.

Играта има набор от знаменитости, от които може да избира, и проверява състоянието им на живи или мъртви спрямо Wikipedia, като използва моя съществуващ пакет. За да съхраня набора от знаменитости, имах нужда от база данни… Избрах Redis, тъй като исках също да кеширам търсенията от Wikipedia, така че да не правя твърде много заявки към нея, а Redis е идеален както за съхранение на данни, така и за кеширане. Както ще видим, има и някои свойства на Redis Sets, които са полезни тук.

Играта също трябва да запомни за коя знаменитост потребителят е бил попитан в текущия кръг и резултата на потребителя. Alexa поддържа състоянието на сесията за всяко взаимодействие с умение и аз ще използвам това, за да ги съхранявам, тъй като не е необходимо да се поддържат в база данни и не се споделят между потребителите.

Настройка на база данни

Имах нужда от набор от знаменитости, от които кодът може да избира произволно всеки път, когато потребител започне нова игра. Записът за всяка знаменитост трябва да се състои от тяхното име и професия, така че потребителите да имат някакъв контекст. Реших да включа 100 знаменитости в JSON файл, например:

Моделирах всеки запис на знаменитост в Redis като Redis Hash с name и bio полета. За името на ключа използвах името на знаменитостта с интервали, заменени с долна черта, и реших да добавя префикс към всички ключове с doa:, за да ги разгранича от всичко друго, което беше в същия екземпляр на Redis. И така, ключът за Pharrell Williams ще бъде doa:Pharrell_Williams и данните изглеждат така:

> HGETALL doa:Pharrell_Williams

1) "name"
2) "Pharrell Williams"
3) "bio"
4) "Singer and producer"

Освен хешовете, имах нужда от Redis Set, съдържащ всяко от ключовите имена на знаменитости. Поставянето им в набор ми позволява да използвам командата SRANDMEMBER на Redis, за да избера на случаен принцип знаменитост, за която да попитам потребителите... ще разгледаме това по-късно.

Написах програма за зареждане на данни в Node.js, за да вземе моя JSON файл и да го импортира в Redis. Това използва функцията „pipeline“ на протокола Redis за зареждане на данните с клиента „ioredis“:

„Вижте пълния код за зареждане на данни в GitHub“.

Тъй като имах нужда от екземпляр на Redis, към който бекендът на моите умения да може да се свърже от AWS, създадох „безплатен екземпляр на Redis Labs от 30 Mb“ — тяхната безплатна пробна програма ви позволява да изберете в кой облак да е вашият екземпляр, така че успях за да избера AWS и източния регион на САЩ — запазване на данните ми близо до моя код за минимално забавяне. Пълно разкриване: Нает съм от Redis Labs.

Кодиране с Alexa Hosted Skills

Alexa Hosted Skills опростяват процеса на развитие на уменията и хостинг на Alexa, като ви позволяват да пишете, тествате и внедрявате код от една конзола, без да се налага да превключвате напред и назад от AWS Lambda. Никога не бях използвал това преди, така че реших да го изпробвам и да направя цялото си кодиране в браузъра. Това няма да бъде удар по удар урок за развитие на Alexa Skill, просто ще разгледаме някои от ключовите взаимодействия, които изградих.

Избрах Node.js, за да изградя уменията си, но можех да избера и Python. Ето как изглежда средата за разработка:

Едно ограничение, което открих с Alexa Hosted Skills, беше, че не можех да задам променливи на средата в конзолата и да ги препратя в кода си… Исках да направя това, за да запазя името на хоста, порта и паролата на Redis извън кода… затова използвах пакет dotenv и файл .env, в който да ги запазите. Не забравяйте да не предавате секретни файлове на контрола на източника!

Като цяло смятах, че хостваната среда за умения е доста лесна за работа и можете да изтеглите своя код и да използвате „подходящ“ редактор, ако предпочитате.

Създаване на модела за гласово взаимодействие

Моделът за гласово взаимодействие работи по същия начин като всяко друго умение на Alexa - зададох намерения за всяко от взаимодействията, които исках потребителят да има с моята игра...

  • Започнете нова игра
  • Отговор, който показва, че потребителят смята, че знаменитостта е жива
  • Отговор, който показва, че потребителят смята, че знаменитостта е мъртва

Намеренията и примерните изказвания (думи, които трябва да задействат намерението) могат да бъдат конфигурирани в конзолата за разработчици на Alexa чрез попълване на формуляри в браузъра:

Това създава JSON документ, който можете да изтеглите като част от проекта и да запазите в контрола на източника. „Ето моят последен модел на взаимодействие в GitHub“.

Код: Стартиране на нова игра

Всеки път, когато потребител иска да започне игра, трябва да избера три произволни знаменитости от пула в Redis — по една за всеки рунд на играта. Командата Redis SRANDMEMBER прави това за мен - връща произволен член от набора от имена на ключове за знаменитости, които създадох, без да премахва този член от набора.

Реших да съхранявам набора от три знаменитости на всяка игра също в Redis, като използвам име на ключ, което включва идентификатора на сесията, предоставен от Alexa, така че да мога лесно да определя кой набор принадлежи на кой потребител. Създавам тази настройка, като извиквам SRANDMEMBER отново и отново, докато не бъдат върнати три различни имена на знаменитости:

Сега имам съхранени три известни личности, също така зададох време за живеене на ключа на сесията в Redis... в случай че потребителят изиграе част от играта и я изостави... Redis ще изчисти тези временни данни автоматично, когато TTL изтече. Това не позволява данните от стари сесии да заемат място в Redis. Ако потребителят завърши играта си, изрично ще изтрия тези данни, за да се отърва от тях по-рано, тъй като ще знам, че вече не са необходими.

Това може да бъде по-ефективно, тъй като прави многобройни мрежови двупосочни пътувания до Redis и обратно. Бих могъл да оптимизирам това и да го направя Lua скрипт, който да се изпълнява в Redis сървъра, въпреки че този метод ще блокира Redis сървъра да не прави нищо друго, докато изпълнява скрипта.

Сега имам знаменитостите за играта на този потребител в набор в Redis, трябва да задам първоначалния резултат на 0 и да попитам потребителя за първата знаменитост... Зададох резултата в атрибутите на сесията на Alexa, след което избирам произволна знаменитост от потребителския набор от 3 и съхранявайте това име в атрибутите на сесията... така че когато потребителят отговори, да знаем за кое име отговаря:

Как работи getRandomCeleb? Ето кода:

При даден идентификатор на сесия на Alexa, той генерира името на ключа на Redis, в който съхраних набора от знаменитости за тази сесия. След това използва командата Redis SPOP, за да премахне и върне произволен член от този набор.

И накрая, за да получа някакъв контекст за потребителя, извиквам getCeleb, което на свой ред използва командата Redis HGETALL, за да извлече хеша за тази знаменитост… съдържащ този допълнителен контекст, който искаме да дадем на потребителя, например за Фарел Уилямс, това ще бъде „ Певица и продуцент”:

Използвам тези стойности, за да създам низ, който Alexa говори на потребителя, питайки ги дали смятат, че знаменитостта е мъртва или жива.

Код: Проверка на отговора на потребителя и актуализиране на неговия резултат

Когато потребителят отговори, за да каже дали смята, че знаменитостта е мъртва или жива, трябва да определя дали е отговорил правилно и ако е така, да актуализирам резултата си. Имам отделни манипулатори на намерения за мъртви и живи отговори, които извикват обща функция handleDeadOrAliveAnswer.

В тази обща функция трябва да знам коя знаменитост гледа потребителската сесия, която е отговорила... и съхраних това в сесията на Alexa. Извеждам името и го предавам на функция, наречена validateAnswer, която връща обект, съдържащ информация за знаменитостта... Проверявам свойствата на този обект, за да видя дали предположението на потребителя е правилно, и ги използвам, за да създам речевия низ, който Alexa ще отговори с. Ако са прави, намирам текущия им резултат в техните атрибути на сесията и добавям 1 към него:

Как работи validateAnswer? Прави някои проверки, след което извиква getCelebStatus, проверявайки върнатия обект, за да види дали потребителят е прав или не:

Работата, за да се види дали знаменитостта е мъртва или жива, се извършва във функцията getCelebStatus… това първо проверява в Redis, за да види дали преди това сме определили състоянието на тази знаменитост и сме го кеширали… ако не, използва моя отделен wikipediadeadoralive модул за вземете информация за знаменитостта от Wikipedia, като кеширате резултата в Redis за известно време, за да спестите дублиращи се търсения:

Обърнете внимание, че ioredis връща празен обект, когато хешът не съществува, откривам това чрез преброяване на броя на ключовете... ако знаете по-добър начин за сравнение с празен обект, уведомете ме!

Докато правех видеообзора на този проект, разбрах, че задавам TTL за всички търсения в Уикипедия, независимо дали знаменитостта се оказва мъртва или жива... Мога да оптимизирам това, така че да няма TTL за резултатите за мъртви знаменитости, тъй като това статусът няма да се промени... някой, който е жив, може да е умрял до следващия път, когато имаме нужда от статуса му, така че определено искаме TTL за тях!

Код: Преминаване към следващия рунд

След като установите дали потребителят е отговорил правилно или не и съответно актуализирате резултата си, следващата задача е да започнете следващия кръг, като го попитате за друга знаменитост. Обаждам се на getRandomCeleb отново, за да получа следващата знаменитост, да актуализирам съответно сесията на потребителя и да ги попитам дали смятат, че този нов човек е мъртъв или жив:

Код: Играта приключи!

Краят на играта се обработва в handleDeadOrAliveAnswer, който обработва отговорите на потребителя.

Както видяхме в „Преминаване към следващия рунд“, следващата знаменитост, за която да попитате потребителя, се избира на случаен принцип от Redis Set знаменитости на текущата игра с помощта на командата SPOP във функцията getRandomCeleb. Когато тази функция не върне нищо, знам, че няма повече рундове, които потребителят да играе, така че казвам на потребителя техния резултат и почиствам атрибутите на сесията, които проследяват резултата и текущата знаменитост:

Това гарантира, че ако потребителят се опита отново да извика манипулатора на отговорите, ще му бъде казано, че няма текуща игра.

Тестване на уменията

Кодът на уменията може да бъде тестван директно в конзолата за разработчици на Alexa, нямате нужда от Echo или друго хардуерно устройство на Alexa. В раздела „Тест“ можете да въведете или да говорите с Alexa и да видите обмена между платформата Alexa и кода на функцията Lambda:

Отговорите на Alexa се появяват в конзолата и ако имате увеличен звук, те също се изговарят вместо вас. Ето пример за преминаване през сесия:

Тестване на реално устройство

Без да оповестявате умението си на обществеността, можете да го тествате на Echo устройства, които са свързани с вашия акаунт в Amazon. Ето как го изпробвам с действителното си устройство:

Опитайте сами!

Ако искате да изпробвате този проект, сложих кода за Alexa за зареждане на умения и данни в GitHub, заедно с моя JSON файл с данни за знаменитости. Чувствайте се свободни да клонирате репото тук и вземете безплатен екземпляр на Redis Cloud тук. Ако изградите нещо забавно с него, ще се радвам да чуя от вас!