【Alexa】ASK SDKにスキルエンジニアの強い味方「ASK SDK Utilities」が登場。 #Alexa #AlexaDevs

せーのでございます。

Alexaスキルを開発する時に、必要なものなので何回も何回も書くのだけれど、いい加減これもう書くのが面倒くさい、という記述ってありますよね。

例えばこれ。

handle(handlerInput) {

        var slotXXX = handlerInput.requestEnvelope.request.intent.slots.XXX.value;

はい、スロットの値を取ってくる式ですね。もう何百回これを書いたことか。SDKかV2になって目的の値に到達するまで長くなり、書くのがどんどん億劫になっていきます。Visual Studio CodeなどスニペットのでないIDEで開発する場合は微妙なタイプミスが起こったり。

今回はそんな「あるあるの関数」が簡単に取れるようになる「ASK SDK Utilities」がリリースされたのでご紹介します。

使い方

ASK SDK Utilitiesは「ask-sdk-core」ライブラリの中に入っています。ちょっと宣言部分を覗いてみると

.......
var RequestEnvelopeUtils_1 = require("./util/RequestEnvelopeUtils");
exports.getAccountLinkingAccessToken = RequestEnvelopeUtils_1.getAccountLinkingAccessToken;
exports.getApiAccessToken = RequestEnvelopeUtils_1.getApiAccessToken;
exports.getDeviceId = RequestEnvelopeUtils_1.getDeviceId;
exports.getDialogState = RequestEnvelopeUtils_1.getDialogState;
exports.getIntentName = RequestEnvelopeUtils_1.getIntentName;
exports.getLocale = RequestEnvelopeUtils_1.getLocale;
exports.getRequestType = RequestEnvelopeUtils_1.getRequestType;
exports.getSlot = RequestEnvelopeUtils_1.getSlot;
exports.getSlotValue = RequestEnvelopeUtils_1.getSlotValue;
exports.getSupportedInterfaces = RequestEnvelopeUtils_1.getSupportedInterfaces;
exports.isNewSession = RequestEnvelopeUtils_1.isNewSession;

.......

このように関数名が直接exportsされています。ですので使い方としては

const accesstoken = Alexa.getAccountLinkingAccessToken(handlerInput.requestEnvelope);

と、「Alexa」オブジェクトの後ろに直接書くといいでしょう。

種類

ASK SDK Utilitiesには様々な値を簡単に取ってくるユーティリティ関数が用意されています。
ざっと見ていきましょう。

RequestEnvelopeUtils

まずはスキルの要、RequestEnvelopeに関するユーティリティです。

関数名 内容
getLocale(RequestEnvelope): string; Echoのロケール
getRequestType(RequestEnvelope): string; リクエストタイプ
getIntentName(RequestEnvelope): string; インテント名
getAccountLinkingAccessToken(RequestEnvelope): string; Account Linkingのアクセストークン
getApiAccessToken(RequestEnvelope): string; API使用時のアクセストークン
getDeviceId(RequestEnvelope): string; EchoのデバイスID
getDialogState(RequestEnvelope): string; ダイアログモデル使用時の現在の状態
getSlot(RequestEnvelope, slotName): Slot; スロット名
getSlotValue(RequestEnvelope, slotName): string; スロットの値
getSupportedInterfaces(RequestEnvelope): SupportedInterfaces; Echoが画面付きかどうか
isNewSession(RequestEnvelope): boolean; 初回アクセスかどうか

Handleの中に書きそうなものだけでなく、CanHandleの条件に書くものも結構入っていていいですね。

SsmlUtils

次はSSMLを書く際によく使うユーティリティです。

関数名 内容
escapeXmlCharacters(string): string; SSML内のキャラクターをエスケープする

SSMLはマークアップランゲージなので、中にタグ的な要素(“<“とか”>”とか)やクオーテーション系が入るとエラーになりますね。escapeXmlCharactersはそれをエスケープしてくれる、という便利関数です。

ViewportUtils

最後にViewportに関するユーティリティです。
Viewportというのは画面付きデバイスのデザインをAPLで書く際に使うパッケージグループです。

関数名 内容
getViewportOrientation(width, height): ViewportOrientation; 画面が縦長か横長か
getViewportSizeGroup(size): ViewportSizeGroup; dp単位の画面幅/高さによるサイズ
getViewportDpiGroup(dpi): ViewportDpiGroup; dpi単位での画面密度のグループ
getViewportProfile(requestEnvelope): ViewportProfile; Echoの画面形状

ここら辺はAPLや画面デザインに慣れていない人にはちょっとわかりにくいのでもう少し詳しく掘り下げてみましょう。

ViewportOrientation

画面付きEchoはEcho Spotなどの幅と高さが同じデバイスとEcho Showのように画面が横長のデバイスがあります。タブレットにもAlexaは搭載され始めていますので、将来的には縦長の表示も考えられます。
APLをデザインする際には横長か同じかによって変わりますので、この関数に幅と高さを入れて、デバイスがどういう形なのかを把握します。

ViewportSizeGroup

ViewportSizeを理解するためにまず「dp」という単位を理解しましょう。
dpとは一般に「画面非依存ピクセル」「密度非依存ピクセル」と呼ばれます。

画面には様々な解像度があり、解像度が高ければ1pxの大きさは小さくなります。つまり「この四角の大きさは10px x 10px」と指定しても、実際に表示される大きさは画面によって違うわけです。

dpというのはある解像度の画面における1pxを基準にして、それより高い解像度の場合は大きく、低い解像度の場合は小さくなることで、見た目の大きさを合わせるための単位です。
例えば四角の大きさを「10dp × 10dp」とした場合、ある画面でそれが10px × 10pxで表示されたとしたら、その倍の解像度のある画面では20px × 20pxで表示されます。

dpは「携帯電話を見るときの距離で画面を持ち、画面のピクセル密度が1インチ(3cm)あたり約160ピクセルであると仮定した場合の画面のビジュアルサイズ」となります。

Alexaではこのdp単位で幅と高さを入力した場合の画面サイズをグループとして管理しています。

  • 600dp未満: viewportSizeXSmall
  • 600dp以上960dp未満: viewportSizeSmall
  • 960dp以上1280dp未満: viewportSizeMedium
  • 1280dp以上1920dp未満: viewportSizeLarge
  • 1920dp以上: viewportSizeXLarge

この関数ではこのdp単位の数字によってこれらのサイズグループが返ってきます。

ViewportDpiGroup

dpi(dot per inch)とはその名の通り1インチあたりのドットの数、つまり画面の密度を指します。

ここら辺から一気にややこしくなるので、特に興味のない人は「へー、画面の密度ね。よく印刷物とかディスプレイとかで使うやつね。OKOK」で読み飛ばしちゃってください。要はdpiが高いほど綺麗な画像で、ViewportDpiGroupはその綺麗さを

”XLOW”|”LOW”|”MEDIUM”|”HIGH”|”XHIGH”|”XXHIGH”

でグループ分けしている、ということです。

さて、本来dpiというのは同じ画素数でも画面のサイズによって変わります。iPhoneのRetinaディスプレイは画素数的にはフルHD(1080p)も無いのですが、かなり綺麗ですよね。一方50インチくらいのTVになるとフルHDではぼやっと見えてしまいますので4K(2160p)くらいあると綺麗です。画面の幅や高さに対して入っているピクセルの数が多いと密度が高い、つまりdpiが高く、綺麗に見えるのです。

ですがViewportにおけるdpiというのは画面の大きさに関わらず一定として計算されます。具体的には

dpi = 160 * (ピクセルサイズ/dpサイズ)

となります。 これは画面のサイズによって適切と言われる視聴距離が違うため、結果的に人間から見た画面の大きさは同じになる、という考えからきています。

例えばフルHD(1080p)の場合、適切な視聴距離は「画面の高さの3倍」と言われています。Amazon的には画面インチの2倍、となっています。画面インチは対角線の距離を指す(40インチTVなら対角線の長さが40インチ)ので、16:9なら対角線の長さは高さの約2倍になります。少し遠いですね。アメリカ基準なのでしょうか。日本の視聴距離は視力1.0を基準にすると綺麗に見える距離、としていますので、アメリカの視聴者の視力基準が違うのかもしれません。

話が少しそれました。Amazonの基準で言うとフルHDのdpiは画面に関わらず320となります。公式に当てはめるとdpサイズは2となります。つまり、10px × 10pxの四角は320dpiの場合2倍の20px × 20pxで表示されることになります。

ViewportProfile

最後はViewportProfileです。これは画面の大きさや形によってカテゴリ分けするものです。
Alexaの場合、このカテゴリは

  • hubRoundSmall(画面が丸くて小さい。Echo Spotなど)
  • hubLandscapeMedium(画面が横長で少し小さめ。初代Echo Showなど)
  • hubLandscapeLarge(画面が横長で少し大きい。二代目Echo Showなど)
  • tvLandscapeXLarge(画面が横長でかなり大きい。FireTVなど)

と分かれています。デバイスに話しかけた時にAlexaに対して、話しかけられたデバイスの情報がrequestEnvelopに入ってきます。その中にこのProfile情報も入ってきます。それをこのユーティリティで簡単に取り出せるわけです。

やってみた

ローカル

では簡単なものからやってみましょう。 通常通り作業用のディレクトリを作ったらnpmでask-sdkをインストールします。

npm install --save ask-sdk

お手持ちのエディタでインストールされたask-sdkのCHANGELOGやpackage.jsonを確認してみましょう。

**#  (2019-03-07)** [2.5.0](https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/compare/v2.4.0...v2.5.0) 


**### Bug Fixes**

* update peer dependency of ask-sdk-model ([#526](~https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/issues/526~)) ([9167297](~https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/commit/9167297~))


**### Features**

* add RequestEnvelopeUtils functions ([#525](~https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/issues/525~)) ([14be7a8](~https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/commit/14be7a8~))
* auto escape invalid SSML characters ([#522](~https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/issues/522~)) ([7a4f215](~https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/commit/7a4f215~)), closes [#472](~https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/issues/472~)



2019年4月23日時点での最新のバージョンは「2.5.1」となっています。Utilitiesの機能は2.5.0にて追加されたんですね。ちなみにコンポーネント版であるask-sdk-coreも2.5.1、ask-ask-modelは1.9.4が最新となります。

後は普通通りスキルを作っていくだけです。血液型を聞くスキルを作ってみます。

Intentに血液型を返す発話を書き込みます。

血液型を表すSlotを作ります。

Intentにて設定していた変数と作ったSlotをつなげます。

これでビルドすると出来上がりです。

つづけてバックエンドです。こんな感じでスロットを取り出す部分にユーティリティを使ってみました。ちなみにここではSkilbuilderはStandardを使っていますが、Customでask-sdk-coreだけでも動きます。

const Alexa = require('ask-sdk');

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speechText = 'こんにちは。あなたの血液型を教えてください。';
        return handlerInput.responseBuilder
            .speak(speechText)
            .reprompt(speechText)
            .getResponse();
    }
};
const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {

        const bloodtype = Alexa.getSlotValue(handlerInput.requestEnvelope, "bloodtype");
        const bloodtypeParent = Alexa.getSlot(handlerInput.requestEnvelope, "bloodtype")
        let speechText = bloodtype + 'なんですね。';

        console.log("getSlotValue: " + bloodtype);
        console.log("getSlot: " + JSON.stringify(bloodtypeParent, " ", 2));
        const val = bloodtypeParent.resolutions.resolutionsPerAuthority[0].values[0].value.name;

        if (val === "A型") {
            speechText +="きっと几帳面な方なんでしょうね。";
        }

        if (val === "B型") {
            speechText += "きっと才能あふれる方なんでしょうね。";
        }

        if (val === "O型") {
            speechText += "きっとおおらかな方なんでしょうね。";
        }

        if (val === "AB型") {
            speechText += "きっと芸術性の高い方なんでしょうね。";
        }

        return handlerInput.responseBuilder
            .speak(speechText)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

........

HelloWorldIntentでbloodtypeというスロットを拾って答えを返しています。
ではテストしてみましょう。

正常に動きますね。成功です。

alexa-hosted skill

hosted skillでもUtilitiesは使えます。やってみましょう。
開発者コンソールで「Alexaをホストにする」を選択してスキルを作ります。

「コードエディタ」を開きます。
デフォルトではバージョンがまだ低いので、最新のバージョンに上げます。hosted skillではpackage.jsonを書き換えてデプロイしてあげれば新しいライブラリを引いてきてくれます。

これでバージョンが上がりました。後は同じようにコーディングすればOKです。簡単ですね。

まとめ

以上、ASK SDK Utilitiesについてまとめてみました。
今回はサンプルとしてスロットの取得だけやってみましたが、canHandleでのリクエストタイプやインテント名などの取得は全てこのUtilitiesで置き換えられます。
とても使い勝手のいいライブラリなので公式からこういうのが出てくれるとありがたいですね。どんどん使っていきましょう。

参考リンク