[譯] 基於 Node.js 的 Alexa Skills Kit 釋出了!

Yuhanlolo發表於2018-07-12

我們今天很高興地宣佈,一個新的基於 Node.js,旨在幫助開發者們更加簡單和快捷地開發 Alexa skill 的 alexa-sdk 釋出了。通過 Alexa Skills KitNode.js,和 AWS Lambda 開發 Alexa skill 如今已成為最受歡迎的 skill 開發方式。Node.js 事件驅動,非阻塞的特性,使它非常適合開發 Alexa skill,並且 Node.js 也是世界上最大開源系統之一。除此之外,為每個月前一百萬個網路請求提供免費服務的亞馬遜網路服務系統(AWS)Lambda,能夠支援大部分開發者從事 skill 的開發。在使用 AWS Lambda 的同時,你不需要擔心管理任何 SSL 證書的問題(因為 Alexa Skills Kit 是被 AWS 信任的觸發器)。

在使用 AWS Lambda 建立 Alexa skill 的時候,加入 Node.js 和 Alexa Skills Kit 只是一個簡單的流程,但你實際上所需要寫的程式碼要比這複雜得多。我們已經意識到大部分開發 skill 的時間都花費在處理會話(session)的屬性、skill 的狀態持久化,建立回覆以及行為模式上面。因此,Alexa 團隊著手於開發一個基於 Node.js 的 Alexa Skills Kit SDK 來幫助你避免這些常見的煩惱,從而專注於你的 skill 自身的邏輯開發而不是樣板化編碼。

使用基於 Node.js 的 Alexa Skills Kit(alexa-sdk)加速 Alexa Skill 的開發

有了 alexa-sdk,我們的目標是幫助你在能夠避免不必要的複雜度的情況下,更快捷地開發 skills。今天我們要釋出的這個最新版本的 SDK 具備以下幾個特點:

  • 新版SDK是可託管的 NPM 安裝包,簡化了在任何 Node.js 環境下的開發
  • 可以通過內建事件建立 Alexa 的回覆
  • 為新的 session 內建幫助事件(Helper events),並且新增了未處理事件(unhandled events)來捕捉所有異常
  • 提供了能構建基於狀態機的使用者意圖(intent)處理的幫助函式(Helper function)
  • 這讓根據當前 skill 的狀態定義不同的事件管理器成為現實
  • 屬性持久化的配置在 Amazon DynamoDB 的幫助下變得更加簡單
  • 所有輸出的語音將自動封裝在 SSML 下
  • Lambda 事件和和上下文物件 (context objects) 將通過 this.event 讀取,並且可以通過 this.contextAbility 重寫內建函式,從而讓你的狀態管理和回覆建立更加靈活。例如,將狀態屬性儲存到 AWS S3 上。

安裝和除錯基於 Node.js 的 Alexa Skills Kit (alexa-sdk)

alexa-sdk 已經上傳到了 github,並且可以以 node 包的形式通過下面的指令在你的 Node.js 環境下安裝:

npm install --save alexa-sdk
複製程式碼

為了開始使用 alexa-sdk,你需要先匯入它的庫。你只需要在你的專案裡簡單地建立一個名為 index.js 的檔案然後加入以下程式碼:

var Alexa = require('alexa-sdk');

exports.handler = function(event, context, callback){

    var alexa = Alexa.handler(event, context);

};
複製程式碼

這幾行程式碼將會匯入 alexa sdk 並且為我們建立一個 alexa 物件以便之後使用。接著,我們需要處理與 skill 互動的️ intent。幸運的是,alexa-sdk 使得在我們想要的意圖(Intent)上啟用一個函式變得簡單。例如,建立一個為 ‘HelloWorldIntent’ 服務的事件管理器,我們只需要簡單地用以下程式碼實現:

var handlers = {

    'HelloWorldIntent': function () {

        this.emit(':tell', 'Hello World!');

                  }

};
複製程式碼

注意上面出現的一個新語法規則 “:tell”? alexa-sdk 遵循 tell/ask 的響應方式來生成你的[語音輸出回覆物件](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference#Response Format)。如果我們想要問使用者問題的話,我們需要把以上程式碼改成:

this.emit(‘:ask’, ’What would you like to do?’, ’Please say that again?’);
複製程式碼

事實上,你的 skill 生成的許多回復都遵循一樣的語法規則。下面是一些常見的 skill 回覆生成的例子:

var speechOutput = 'Hello world!';

var repromptSpeech = 'Hello again!';

this.emit(':tell', speechOutput);

this.emit(':ask', speechOutput, repromptSpeech);

var cardTitle = 'Hello World Card';

var cardContent = 'This text will be displayed in the companion app card.';

var imageObj = {

    smallImageUrl: 'https://imgs.xkcd.com/comics/standards.png',

    largeImageUrl: 'https://imgs.xkcd.com/comics/standards.png'

};

this.emit(':askWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, imageObj);

this.emit(':tellWithCard', speechOutput, cardTitle, cardContent, imageObj);

this.emit(':tellWithLinkAccountCard', speechOutput);

this.emit(':askWithLinkAccountCard', speechOutput);

this.emit(':responseReady'); // 在回覆建立之後,返回 Alexa 服務之前被呼叫。Calls :saveState。

this.emit(':saveState', false); // 事件管理器將 this.attributes 的內容和當前管理器的狀態儲存到 DynamoDB,然後將之前內建的回覆傳送到 Alexa 服務。如果你想用別的方式處理持久化狀態,可以重寫它。其中的第二個屬性是可選的並且可以通過將它設定為 ‘true’ 以強制儲存。

this.emit(':saveStateError'); // 在儲存狀態的過程出錯時被呼叫。如果你想自己處理異常的話,可以重寫它。
複製程式碼

一旦我們建立好事件管理器,在新的 session(NewSession)場景下,我們需要用之前建立的 alexa 物件中的 registerHandlers 函式去註冊這些管理器。

exports.handler = function(event, context, callback){

    var alexa = Alexa.handler(event, context);

    alexa.registerHandlers(handlers);

};
複製程式碼

你也可以同時註冊多個事件管理器。與其建立單個管理器物件,我們建立了一個新的 session,其中有許多處理不同事件的不同管理器,並且我們可以通下面的程式碼同時註冊它們:

    alexa.registerHandlers(handlers, handlers2, handlers3, ...);
複製程式碼

你所定義的事件管理器可以相互呼叫,從而保證你的 skill 的回覆是統一的。下面是 LaunchRequest 和 IntentRequest(在 HelloWorldIntent 中)都返回 “Hello World” 訊息的一個例子。

var handlers = {

    'LaunchRequest': function () {

        this.emit('HelloWorldIntent');

    },

    'HelloWorldIntent': function () {

        this.emit(':tell', 'Hello World!');

};
複製程式碼

一旦你註冊了所有的意圖管理器函式,你只需要簡單地用 alexa 物件裡的執行函式去執行 skill 的邏輯就可以了。最後一行程式碼是這樣的:

exports.handler = function(event, context, callback){

    var alexa = Alexa.handler(event, context);

    alexa.registerHandlers(handlers);

    alexa.execute();

};
複製程式碼

你可以從 github 上下載完整的示例。我們還提供了最新的基於 Node.js 和 alexa-sdk 開發的 skill 示例:FactHelloWorldHighLowHowToTrivia

讓 Skill 的狀態管理更簡單

alexa-sdk 會根據當前狀態把即將接受的 intent 傳送給正確的管理器函式。它其實只是 session 屬性中一個簡單的字串,用來表示 skill 的狀態。在定義 intent 管理器的時候,你也可以通過將表示狀態的字串新增到 intent 的名稱後面來模仿這個內建傳送的過程,但事實上 alexa-sdk 已經幫你做到了。

比如說,讓我們根據上一個管理新的 session 事件的例子,建立一個簡單的有“開始”和“猜數”兩個狀態的猜數字遊戲。

var states = {
    GUESSMODE: '_GUESSMODE', // User is trying to guess the number.
    STARTMODE: '_STARTMODE'  // Prompt the user to start or restart the game.
};

var newSessionHandlers = {

 // 以下程式碼將會切斷任何即將輸入的 intent 或者啟動請求,並且把它們都傳送給這個管理器。

  'NewSession': function() {

    this.handler.state = states.STARTMODE;

    this.emit(':ask', 'Welcome to The Number Game. Would you like to play?.');

   }

 };
複製程式碼

注意當一個新的 session 被建立時,我們簡單地通過 this.handler.state 把 skill 的狀態設定為 STARTMODE。此時 skill 的狀態將會自動被持久化在 session 的屬性中,如果你在 DynamoDB 裡設定了表格的話,你可以選擇將它持久化於各個 session 當中。

值得注意的是,NewSession 是一個很棒的捕捉各種行為的管理器,同時也是一個很好的 skill 入口,但它不是必需的。NewSession 只會在一個以它命名的函式中被喚醒。你所定義的每一個狀態都可以有它們自己的 NewSession 管理器,在你使用內建留存時被喚醒。在上面的例子中,我們可以更加靈活地為 states.STARTMODE 和 states.GUESSMODE 定義不同的 NewSession 行為。

為了定義回覆 skill 在不同狀態下的 intents,我們需要使用 Alexa.CreateStateHandler 函式。任何在這裡定義的 intent 管理器將只會在特定狀態下工作,這讓我們的開發操作更加靈活!

例如,如果我們在上面定義的 GUESSMODE 狀態下,我們想要處理使用者對一個問題的回覆。這可以通過 StateHandlers 實現,就像這樣:

var guessModeHandlers = Alexa.CreateStateHandler(states.GUESSMODE, {

    'NewSession': function () {

        this.handler.state = '';

        this.emitWithState('NewSession'); // 等同於 Start Mode 下的 NewSession handler

    },

    'NumberGuessIntent': function() {

        var guessNum = parseInt(this.event.request.intent.slots.number.value);

        var targetNum = this.attributes["guessNumber"];

        console.log('user guessed: ' + guessNum);



        if(guessNum > targetNum){

            this.emit('TooHigh', guessNum);

        } else if( guessNum < targetNum){

            this.emit('TooLow', guessNum);

        } else if (guessNum === targetNum){

            // 通過一個 callback 函式,用 arrow 函式儲存正確的 ‘this’ context

            this.emit('JustRight', () => {

                this.emit(':ask', guessNum.toString() + 'is correct! Would you like to play a new game?',

                'Say yes to start a new game, or no to end the game.');

        })

        } else {

            this.emit('NotANum');

        }

    },

    'AMAZON.HelpIntent': function() {

        this.emit(':ask', 'I am thinking of a number between zero and one hundred, try to guess and I will tell you' +

            ' if it is higher or lower.', 'Try saying a number.');

    },

    'SessionEndedRequest': function () {

        console.log('session ended!');

        this.attributes['endedSessionCount'] += 1;

        this.emit(':saveState', true);

    },

    'Unhandled': function() {

        this.emit(':ask', 'Sorry, I didn\'t get that. Try saying a number.', 'Try saying a number.');

    }

});
複製程式碼

另一方面,如果我們在 STARTMODE 狀態下,我可以用以下方式定義 StateHandlers:

var startGameHandlers = Alexa.CreateStateHandler(states.STARTMODE, {

    'NewSession': function () {

        this.emit('NewSession'); // 在 newSessionHandlers 使用管理器

    },

    'AMAZON.HelpIntent': function() {

        var message = 'I will think of a number between zero and one hundred, try to guess and I will tell you if it' +

            ' is higher or lower. Do you want to start the game?';

        this.emit(':ask', message, message);

    },

    'AMAZON.YesIntent': function() {

        this.attributes["guessNumber"] = Math.floor(Math.random() * 100);

        this.handler.state = states.GUESSMODE;

        this.emit(':ask', 'Great! ' + 'Try saying a number to start the game.', 'Try saying a number.');

    },

    'AMAZON.NoIntent': function() {

        this.emit(':tell', 'Ok, see you next time!');

    },

    'SessionEndedRequest': function () {

        console.log('session ended!');

        this.attributes['endedSessionCount'] += 1;

        this.emit(':saveState', true);

    },

    'Unhandled': function() {

        var message = 'Say yes to continue, or no to end the game.';

        this.emit(':ask', message, message);

    }
複製程式碼

我們可以看到 AMAZON.YesIntent 和 AMAZON.NoIntent 在 guessModeHandlers 物件中是沒有被定義的,因為對於該狀態來說,“是”或者“不是”的回覆是沒有意義的。這樣的回覆將會被 ‘Unhandled’ 管理器捕捉到。

還有就是,注意在 NewSession 和 Unhandled 這兩個狀態中的不同行為。在這個遊戲中,我們通過呼叫 newSessionHandlers 物件中的 NewSession 管理器“重置” skill 的狀態。你也可以跳過這一步,然後 alexa-sdk 將會為當前狀態呼叫 intent 管理器。你只需要記住在呼叫 alexa.execute() 之前去註冊你的狀態管理器,否則它們將不會被找到。

所有屬性將會在你的 skill 結束 session 時自動儲存,但是如果使用者自己結束了當前的 session,你需要 emit ‘:saveState’ 事件(this.emit(‘:saveState’, true)來強制儲存這些屬性。你應該在 SessionEndedRequest 管理器中做這件事,因為 SessionEndedRequest 管理器將會在使用者通過“退出”或回覆超時結束當前 session 的時候被呼叫。你可以看看以上的程式碼示例。

我們將上面的例子寫在了一個高/低猜數字遊戲中,你可以點選這裡下載

通過 Amazon DynamoDB 持久化 Skill 屬性

很多人喜歡將 session 屬性值儲存到資料庫中以便日後使用。alexa-sdk 直接結合了 Amazon DynamoDB(一個 NoSQL 的資料庫服務)讓你只需要幾行程式碼就可以實現屬性儲存。

簡單地在你呼叫 alexa.execute 之前為 alexa 物件中的 DynamoDB 的表格設定一個名字。

exports.handler = function(event, context, callback) {
    var alexa = Alexa.handler(event, context);
    alexa.appId = appId;
    alexa.dynamoDBTableName = ’YourTableName'; // That’s it!
    alexa.registerHandlers(State1Handlers, State2Handlers);
    alexa.execute();
};
複製程式碼

之後,你只需要呼叫 alexa 物件的 attributes 為你的屬性設定一個值。不再需要其他輸入而得到單獨的函式!

this.attributes[”yourAttribute"] = ’value’;
複製程式碼

你可以提前手動建立一個表格或者為你的 Lambda 函式的 DynamoDB 提供建立表格許可權然後一切都會自動生成。不過你要知道,在第一次喚醒 skill 的時候,建立表格可能會花費幾分鐘的時間。

嘗試擴充套件猜數字遊戲:

  • 讓它能夠儲存你每次遊戲中所猜的平均數
  • 加入聲音效果
  • 給玩家有限的猜數字時間

想要獲取更多關於學習使用 Alexa Skills Kit 開發的資訊,可以看看下面的連結:

基於 Node.js 的 Alexa Skills Kit
Alexa 開發者播客
Alexa 開發培訓
關於 Alexa Skills 的介紹
101 條語音互動設計指南
Alexa Skills Kit (ASK)
Alexa 開發者論壇

-Dave (@TheDaveDev)

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章