kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

Voicceflow Updates: 謎のEvent Blockを使ってみた〜Alexaのディレクティブとイベント〜

f:id:kun432:20200823170652p:plain

前回の最後に少しだけ触れましたが、APL for Audio のβテストであらたにDirective Blockが追加されましたが、もう一つ、Event Blockというのも追加されています。

f:id:kun432:20200823170644p:plain

で、これは何でしょうか?ということなのですが、今回のβテストでも詳細は明かされていません(今は無視しろみたいな感じっぽい)。

そこで、今回は勝手に推測していろいろやってみたというお話です。また、Event Blockを使うにあたり、Alexaの「ディレクティブ」と「イベント」を知っておくと理解がしやすいので、その点についてもご紹介します。

ということで、今回のお話は完全無保証です。詳細について公式のアナウンスもないし、いつリリースされるかもわからないので、あくまでも「こういうことがいつかできるようになったらいいなー」ぐらいのゆるい気持ちで見ていただけると幸いです。

あと今回は少しコード寄りの話含みます。ask-sdkでコードを書いている人ならわかるのではないかなと思います。

目次

ディレクティブとイベント

そもそもAlexaにおいてディレクティブとイベントとはなんでしょうか?それを知るために、まず、Alexaがどういうデータのやり取りを行うかをお話します。

通常、ユーザとAlexaのやり取りは音声のやりとりになります。

  • ユーザからの発話を受け取る
  • Alexaに喋らせる

というのが基本ですね。こんな感じです。

f:id:kun432:20200827233153p:plain

やや技術的な内容になっちゃいますが、これをもう少し詳細に書くと、概ねこういう感じになります。

f:id:kun432:20200828001237p:plain

ユーザとAlexa(というかEchoデバイス)のやり取りは音声ですが、実際には

  • ユーザから送られてきた音声をテキストに変換
  • テキストを解釈して、インテントにマッピング、そこからスロットを抽出
  • インテントやスロットから、スキルが何らかの処理を行う。
  • スキルの処理結果をテキストで返し、ユーザに届ける際に音声に変換する。

ということになります。実は結構複雑なのですが、ここでいちばん大事なことは、

「Alexaスキルは、基本的には、文字のやりとりで成り立っている」

ということなんです。

ちなみに、これをVoiceflowで表すとこうなります。

f:id:kun432:20200827233844p:plain

物凄くかんたんでわかりやすいですね。上で書いたような複雑なやり取りを意識しなくていいのはVoiceflowのよいところなのですが、これだけだと今回のDirective Block/Event Blockを理解するのは難しいと思います。もうしばらくお付き合いください。

で、音声とテキストのやりとりでも十分スキルは作れるのですが、いかんせん非常に表現の幅が狭かったり、機能的に物足りない場合もあります。例えば以下のような機能もありますよね。

  • ビジュアル(画面付きデバイス向けのAPL)
  • オーディオ(環境音スキルとかのオーディオデータ)
  • スキル内課金(スキル内商品の購入とか)

こういった「音声・テキスト」のやりとりに「機能を追加」するためのものが「ディレクティブ」と「イベント」だと思って貰えればいいかと思います。

ディレクティブ

「ディレクティブ」は、スキルからAlexaにレスポンスを返す際に「音声・テキスト」以外の機能を追加するために使用します。一番わかりやすいのは「APL」です。

f:id:kun432:20200828005009p:plain

APLは、通常の音声・テキストレスポンスに、APLドキュメントをディレクティブとして追加して返します。これにより、Alexaの発話とともに、画面付きデバイスの場合はビジュアルが表示されるという仕組みです。

違いを理解するために以下のサンプルのスキルを見てください。

f:id:kun432:20200828005608p:plain

f:id:kun432:20200828010008p:plain

「今日はいいお天気ですね。」ということをしゃべるだけのシンプルなスキルです。このスキルを起動して、返ってくるレスポンスのデータはこうなっています。

{
    "body": {
        "version": "1.0",
        "response": {
            "outputSpeech": {
                "type": "SSML",
                "ssml": "<speak>今日はいいお天気ですね。</speak>"
            },
            "reprompt": {
                "outputSpeech": {
                    "type": "SSML",
                    "ssml": "<speak>今日はいいお天気ですね。</speak>"
                }
            },
            "shouldEndSession": true,
            "type": "_DEFAULT_RESPONSE"
        },
        "sessionAttributes": {},
        "userAgent": "ask-node/X.X.X Node/vX.X.X"
    }
}

細かいところは置いといて、なんとなくAlexaの発話だけが返ってきているなということはわかりますよね。

これにDisplay Blockを追加してみます。

f:id:kun432:20200828010451p:plain

f:id:kun432:20200828010756p:plain

※画像はpixabayから使用させていただきました。https://pixabay.com/ja/photos/garden-flower-5000586/

Display Blockを追加したあとで返ってくるレスポンスのデータはこんな感じになります。(長いのでちょっと省略+わかりやすさを踏まえて修正しています)

{
    "body": {
        "version": "1.0",
        "response": {
            "outputSpeech": {
                "type": "SSML",
                "ssml": "<speak>今日はいいお天気ですね。</speak>"
            },
            "reprompt": {
                "outputSpeech": {
                    "type": "SSML",
                    "ssml": "<speak>今日はいいお天気ですね。</speak>"
                }
            },
            "directives": [
                {
                    "type": "Alexa.Presentation.APL.RenderDocument",
                    "token": "xxxxxxxxxx",
                    "document": {
                        "type": "APL",
                        "version": "1.1",
                        "settings": {},
                        "theme": "dark",
                        〜〜〜 省略 〜〜〜
                    },
                    "datasources": {
                        "bodyTemplate7Data": {
                            "type": "object",
                            "objectId": "bt7Sample",
                            "title": "今日の天気は晴れ",
                            "backgroundImage": {
                            〜〜〜 省略 〜〜〜
                            },
                            "image": {
                                "contentDescription": null,
                                "smallSourceUrl": null,
                                "largeSourceUrl": null,
                                "sources": [
                                    {
                                        "url": "https://xxxxxxxx.com/images/garden.jpg",
                                        "size": "small",
                                        "widthPixels": 0,
                                        "heightPixels": 0
                                    },
                        〜〜〜 省略 〜〜〜
                        }
                    },
                    "timeoutType": "SHORT"
                }
            ],
            "shouldEndSession": true,
            "type": "_DEFAULT_RESPONSE"
        },
        "sessionAttributes": {},
        "userAgent": "ask-node/X.X.X Node/vX.X.X"
    }
}

重要なのは以下の部分です。最初のSpeak Blockだけ、だった場合のレスポンスに、"directives"というのが追加されていて、中に何やらAPLに関連するようなものが含まれているんだなぁということがわかるでしょうか?

           〜〜〜 省略 〜〜〜
            "reprompt": {
                "outputSpeech": {
                    "type": "SSML",
                    "ssml": "<speak>今日はいいお天気ですね。</speak>"
                }
            },
            "directives": [
                {
                    "type": "Alexa.Presentation.APL.RenderDocument",
                    "token": "xxxxxxxxxx",
                    "document": {
                        "type": "APL",
                        "version": "1.1",
            〜〜〜 省略 〜〜〜

はい、これが「ディレクティブ」です。通常の発話に加えて、この部分で画面表示に必要な情報を追加しているわけですね。

APL以外にも「ディレクティブ」を使ってできることは他にもあります。例えば、前回ご紹介したAPL for Audioもディレクティブを使っています。

f:id:kun432:20200823151416p:plain

そして、Voiceflowでこのディレクティブを実現するのが前回ご紹介した「Directive Block」です。

イベント

次に今回のテーマである「イベント」です。「イベント」は、「ディレクティブ」とは逆で、Alexaからスキルに対して「音声」以外のリクエストを受け付けるために使います。でこれが一番わかりやすいのも「APL」です。以下のデモをご覧ください。

ちなみにこのサンプルスキルは、日本のAlexa Championの一人である「がおまる」さんのハンズオンを流用させていただきました。この場を借りてお礼を申し上げます。ハンズオンに興味のある方は以下を御覧ください。

懐かしの「へぇボタン」ですね。「へぇボタン」というとAlexaが「へぇ」と言うと同時に、画面下部のカウンターがカウントアップされます。が、それだけではなく、画面をタッチした場合にも同じように動きますよね。「画面タッチ」はまさに「音声以外のリクエスト」ですよね。これが「イベント」です。

わかりやすく図で書いてみましょう。まずは音声で受け付ける場合。

f:id:kun432:20200828030928p:plain

はい、APLなのでディレクティブが追加されていますね。

次にタッチで受け付ける場合です。

f:id:kun432:20200828025450p:plain

このサンプルでは音声の場合と最終的な結果は同じにしてありますが、音声とタッチでは入り口が異なります。音声の場合はインテントから起動されるのに対して、タッチの場合はイベントから起動されます。したがって、イベントを受け付ける場合は入り口を別に作って上げる必要があるわけです。

そして、Voiceflowでこのイベントを実現するのが今回ご紹介した「Event Block」なのではないかと思っています。

Event Blockを使ってみよう。

ということで、実際にEvent Blockを使って、さきほどご紹介した「へぇボタン」を作ってみましょう。

まず、最初に音声だけでカウントアップしていくスキルを作りましょう。

f:id:kun432:20200830165356p:plain

ポイントだけかいつまんで説明します。

最初にカウンターとして使う"count"変数を初期化しておきます。

f:id:kun432:20200830165510p:plain

Speak Blockで「へぇボタン、と言うか、画面のボタンをタッチしてください。」と話させると同時に、Display Blockで画面にボタンを表示します。

f:id:kun432:20200830165630p:plain

Display BlockにアップロードするAPLのJSONファイルはこんな感じです。

APLドキュメントの詳細は後で説明しますが、プレビューだとこういう感じで、ボタンとカウンターが表示されています。

f:id:kun432:20200830191304p:plain

カウンターの部分は、データソースで指定されているmyCountが表示されるようになっています。

f:id:kun432:20200830191429p:plain

以下のように修正すると、Voiceflowの変数"count"を表示されるようになります。

f:id:kun432:20200830191858p:plain

そのあと、インテントで「へぇボタン」という発話を受けます。

f:id:kun432:20200830192213p:plain

「へぇボタン」と発話されたら、count変数をカウントアップ、「へぇ」と言ったあとに再度待ち受けます。

f:id:kun432:20200830192322p:plain

これで声だけで動く「へぇボタン」ができました。テストしてみるとこんな感じです。

f:id:kun432:20200830192620p:plain

これをタッチ対応にしていきます。タッチ対応を行うには2つの対応が必要です。

  1. APLドキュメントで、ボタンがタッチされたらイベントを送信するようにする
  2. スキル側でそのイベントを受け取れるようにする

1については、すでにAPLドキュメントに実装してあります。その部分を抜粋します。

          "items": [
            {
              "type": "Pager",
              "width": "100vw",
              "height": "100vh",
              "id": "myPager",
              "initialPage": 0,
              "items": [
                {
                  "type": "TouchWrapper",
                  "onPress": [
                    {
                      "type": "SendEvent",
                      "arguments": [
                        "touch"
                      ]
                    }
                  ],
                  "item": {
                    "type": "pushLayout",
                    "imageUrl": "${payload.push.button_off}"
                  }
                },

APLコンポーネント、ここではボタンになりますが、これを"TouchWrapper"コンポーネントでラップしてあげます。"OnPress"でボタンをタッチしたことを認識します。ボタンが押されると"SentEvent"でargumentsに指定された値を送信します。今回は特に値のやり取りをする必要はない(押されたらイベントが送られればそれでよい)ので、"touch"というのはダミーです。

ちなみに、厳密にはここはPagerコンポーネントの一つ目の子コンポーネントに対してTouchWrapperを設定しています。Pagerを使ってボタンが押されたらAPL Pagerコマンドでボタン画像を切り替えて「ボタンが押された」という感じを出すためなのですが、そこは今回やりませんので動きません。あしからず。

次にこのイベントをスキル側で受け取れるようにしましょう。ここでやっとEvent Blockの出番です!Event Blockをドラッグ&ドロップして、カウンターをカウントアップしているブロックにつなげます。

f:id:kun432:20200830195218p:plain

Event Blockの設定です。APLのイベントはAlexa.Presentation.APL.UserEventというリクエストが送信されてきますので、これをリクエスト名に指定します。値を受け取る場合は下のところでイベントから送られてきた値をVoiceflowの変数とマッピングすることで受け取れますが、今回は使いません。例えばボタンが複数あるような場合にどのボタンが押されたかを判定するようなケースではこれを使います。

f:id:kun432:20200830195630p:plain

はい、これで完成です。テストして、最初のご紹介した動画のようになればOKです!

まとめ

いかがだったでしょうか?ディレクティブとイベントを使うと、Voiceflowでは長らくできなかったAPLのタッチ操作に対応することができるというのがわかると思います。タッチ以外にもディレクティブやイベントを使うとできるようになることがたくさんありますので、これはとても楽しみですね!

ただ、逆にいうと、

  • Alexaのいろんな機能やその仕組みを知る必要がある。
  • ディレクティブやイベントはコードを書いている感覚に近い。つまり、コード的な知識があったほうが理解しやすい。

というところで、現状のDirective/Event Blockは、どうしても技術的な知識が必要な印象で、誰でもかんたんに使えるというものにはなっていません。実際にリリースされる際には、そのあたりがもう少しわかりやすくなっていることを期待したいです!