所謂萬事起頭難, 當我們從頭到尾摸索一遍Alexa Skill的整個專案開發過程之後, 我們將發現原來「Hello World」是最難的! 而在熟悉開發環境與流程之後, 接下來天馬行空的應用都變得很簡單, 我們只須專注在程式設計的本業部份.
Alexa Skill的變數型別
本案例希望Alexa語音助理可以透過詢問使用者名字的方式並將得到的名字資料(字串)儲存起來, 直到使用者反問時再將名字資料萃取出來並透過Echo回答說出. 因此, 透過本案例我們將知道Alexa Skill的資料定義與存取方法.
增加自訂的變數型別
在Alexa Skill Kit開發網頁的Interaction Model步驟, 除了於前例「開始寫第一個 Alexa Skill – Hello World!」所提到開發者須定義的「Intent Schema」與「Sample Utternaces」之外, 我們可以發現還有一項之前略過的「Custom Slot Type」定義. (其它細項操作/步驟請參考前例的說明)
其實我們想要的只是簡單的字串型別(以儲存名字), 但目前似乎只能先透過這個方式達到目的. 按下「Add Slot Type」之後就可以編輯我們自訂的資料型別, 此時系統需要我們填入一些初始值, 我們隨便填入/列舉一些值給它即可(當然我們將來也可檢查資料是否在定義/列舉的集合之中). 舉例來說, 我們取一個稱LIST_OF_NAME的資料型別, 並預設有A, B與C三個成員. 讀者也可以自己取一個稱STRING的資料型別, 並只留一個如string之類的預設值給它. 這個新增的資料型別, 我們將在定義「Intent Schema」時用上.
變數值的定義方式
還記得前案曾說明Alexa Skill的運作流程嗎? ASK會先將使用者語音輸入全部辨識成文字, 接著指認出「Invocation(要被激活的App名稱)」與「Intent(剩餘的文字指令)」並將所有資料以JSON打包成Lambda Request遞送到AWS. 因此, 初學者若想知道如何定義包含使用者自訂變數的「Intent
Schema」, 可以試著先從ASK模擬器將資料打包成Lambda Request的內容中嗅出一些端倪.
舉例來說, 若我們希望使用者透過說出「My name is {myName}」時來處發「MyNameIsIntent」,同時將{myName}這個變數值存起來. 另一方面, 希望當使用者說出「What’s my name」時來處發「WhatIsMyNameIntent」並且將變數值取出/說出.
從上圖模擬器的Lambda
Request監視窗中, 我們可以清楚看到, 我們所定義的「Intent」是包含自訂變數{myName}的, 並以稱為「slot」的結構來存放. 請參考上圖右上方區塊的「Intent
Schema」規則, 本例為包含變數的「MyNameIsIntent」定義方式, 其中定義了{myName}的變數與其資料型別. ASK在成功截取使用者名字語音之後, 會自動將値填入slots.myName的value欄位.
變數值的存取方式
在知道ASK如何成功觸發「Intent」並合併使用者名字以JSON(Lambda Request)遞送至AWS之後, 接下來我們須進一步了解如何在Lambda的程式中取出它. 如下圖, 因為intent是以JSON物件形式當參數被傳遞到我們的Intent Handler, 因此我們可以非常容易地以物件名稱的方式取值, 本例為intent.slots.myName.value.
而Lambda函式之間的參數傳遞也是透過稱「sessionAttributes」的JSON物件形式交換, 如下圖, 我們從intent的JSON物件中將値取出後透過系統預設的回呼函式將該變數打包. 我們可以在模擬器的Lambda Response監控窗中看到它, 如本例中系統將區域變數Name透過回呼函式將該變數與值打包在sessionAttributes裏面.
透過下面這段測試程式, 讀者將可以更清楚地猜出回呼函式在底層對sessionAttributes的資料封裝與其取用的方式. 本例中, 我們在setAttributeInSession中設定兩個區域變數(含值)並指定給sessionAttributes, 最後在getAttributeInSession中透過sessionAttributes將值取回.
程式架構
請參照前案之Alexa語音服務的運作流程圖, Alexa Skill程式的撰寫與其開發環境/架構目前分成兩個區塊協同運作, 也就是由兩個不同的網頁/服務所構成. 因此在開始為Alexa開發新的技能專案前, 建議我們心裡能先有一個藍圖來規劃需要的「Intent
Schema」(包含自訂變數)、「Sample
Utterances」與被觸發之後的「Intent Handler」, 如下圖所示.
左半部描述在的Alexa Skill Kit (ASK) 開發者平台, 我們將在此為Alexa定義新的技能. 當使用者「調用(Invoke)」自己寫的App程式「myName」, 緊接著當我們說出的語音指令符合所預先例舉的樣本(Sample
Utterances)並觸發所定義的「意圖(Intent)」, 本例為「MyNameIsIntent」與「WhatIsMyNameIntent」.
接著ASK將資料(包括自訂變數與值)打包成JSON並提出AWS/Lambda請求, 而右半部則是描述在AWS/Lambda平台將針對各個「Intent」提供底層服務(Intent Handler)的實作.
讓Alexa記得並說出使用者名字的Skill
Step1: Skill Information
Step2: Interaction Mode
ASK/Intent Schema
意圖模式(Intent
Schema)是以JSON的格式來定義. 如下, 我們為新的Skill添增一個稱為「MyNameIsIntent」的意圖, 包含自訂資料型態的變數{myName}, 用來詢問/儲存使用者名字, 以及一個「WhatISMyName」的意圖, 用來取出/回應使用者名字並透過Echo發聲.
{
"intents": [
{
"slots": [
{
"name": "myName",
"type": "LIST_OF_NAME"
}
],
"intent": "MyNameIsIntent"
},
{
"intent": "WhatsMyNameIntent"
},
]
}
|
ASK/Sample utterances
為了讓Alexa能成功辨識使用者語音指令並觸發我們所定義的「Intent」, 因此我們須先預想幾種可能的使用者招喚方式, 例如:
MyNameIsIntent my name
is {myName}
WhatsMyNameIntent what's my
name
|
Step3: 撰寫Lambda程式
這個步驟須將焦點暫時從ASK網頁移到AWS/Lambda網頁, 為我們設定的「Intent」實作底層的處理函式(Handeler). 我們以前案「開始寫第一個 Alexa Skill – Hello World!」的程式為藍圖/範本當開發者的起點, 因此只須改寫下面三個函式. 資料的存取與傳遞方式請參考前面的說明.
// Called when the user
specifies an intent for this skill.
function onIntent(intentRequest,
session, callback) {
console.log(`onIntent requestId=${intentRequest.requestId},
sessionId=${session.sessionId}`);
const intent =
intentRequest.intent;
const intentName =
intentRequest.intent.name;
// Dispatch to your
skill's intent handlers
if (intentName === 'MyNameIsIntent')
{
setNameInSession(intent, session,
callback);
} else if (intentName === 'WhatsMyNameIntent')
{
getNameFromSession(intent, session,
callback);
} else {
throw new Error('Invalid intent');
}
}
// MyNameIsIntent Handler
function setNameInSession(intent,
session, callback) {
var sessionAttributes = {};
var cardTitle = intent.name;
var nameSlot = intent.slots.myName; // slot of MyNameIsIntent in JSON
var speechOutput = "I'm not sure what your name is. Please try
again.";
var repromptText = "";
var shouldEndSession = false;
if (nameSlot) {
var Name = nameSlot.value;
sessionAttributes = {Name}; // return custom
variable in JSON
speechOutput = Name + ". I now know your name is " + Name +
". You can ask me your name
by saying, what's my name?";
}
repromptText = speechOutput;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText,
shouldEndSession));
}
// WhatIsMyNameIntent
Handler
function getNameFromSession(intent,
session, callback) {
var sessionAttributes = {};
var speechOutput = "I'm not sure what your name is.";
var repromptText =
"";
var shouldEndSession = false;
if (session.attributes)
{
var Name = session.attributes.Name; // fetch variable from sessionAttributes (JSON)
if (Name) {
speechOutput = `Your name is
${Name}. How are you?`;
shouldEndSession = true;
}
}
repromptText = speechOutput;
callback(sessionAttributes,
buildSpeechletResponse(intent.name, speechOutput, repromptText,
shouldEndSession));
}
|
按儲存之後請將網頁編輯器最右上方的ARN碼複製下來, 它將用來綁定ASK的外部服務service endpoint, 它的格式為: 「arn:aws:lambda:us-east-1:xxxx:function:myName」.
Step4: Configuration
完成Lambda程式並取得ARN碼之後我們須回到ASK的頁面繼續完成configuration, 如下圖, 在service enpoint中勾選AWS Lambda並填入ARN code.
Step5: 測試
ASK提供了文字轉語音輸出的模擬器與Echo語音助理的模擬器, 因此即使我們手邊並沒有真正的Echo硬體設備也能模擬真實的結果. 從模擬器中可以清楚看見底層資料以JSON的遞送/交換過程, 當使用者說出「my name is …」, ASK從列舉的utternaces中成功辨識/匹配的「MyNameIsIntent」並將自訂的變數與值打包成JSON交由Lambda處理. 之後setNameInSession函式(Intent Handler), 把變數與值存放在「sessionAttributes」. 接著當使用者詢問「what is my name」時, 再次符合列舉的utternaces而發出Lambda請求並觸發「WhatIsMyNameIntent」, 最後透過getNameInSession函式把變數與值從「sessionAttributes」取回之後再以JSON傳回ASK接手後續的Echo發聲與手機文字顯示等服務.
測試步驟:
筆者: Alexa, ask
my Name (調用自己寫的App)
(註: 本例因沒有任何接續的語音指令「Intent」可供與sample utterances匹配, 系統會觸發預設的AMAZON.HelpIntent, 其對應的Handler為getWelcomeResponse函式, 有興趣的讀者可以自行修改該函式當作Skill使用說明)
筆者: my name is
Hock.
筆者: what’s my
name?