kun432's blog

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

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

Voiceflowで音声パーミッションをやってみる

f:id:kun432:20210313005910p:plain

AlexaにはSkill Connectionsという仕組みがあります。

スキルの中から別のスキルの特定の機能を呼び出すというものですね。上記のスライドでは、自分のカスタムスキルからプリンタースキルと連携して印刷を行うというのをやってました。他のスキルを呼び出すとき、connections.〜という特別なディレクティブを使うのですが、実は同じようなしくみを使ったものが他にもあります。

  • スキル内課金
  • Amazon Pay
  • 音声パーミッション

Voiceflowでは、以前にご紹介した Directive Step/Event Stepの2つを組み合わせることで、このしくみを実現できます。

今回は、リマインダーの音声パーミッションによる権限付与をやってみましょう。

Event Stepは現在ベータ版となっており、通常は表示されません。利用したい場合は、Voiceflowログイン後に出てくるIntercomサポート(右下の青いアイコン)に連絡して、Event Stepを有効にしてもらいたい旨を伝えれば良いと思います。

目次

サンプルスキル

少し古い記事になりますが、リマインダーは以下でご紹介していました。今見るとだいぶ画面も変わっていますね・・・・

最新のものを用意しました。右クリックでダウンロードして、Voiceflowにインポートできます。

https://raw.githubusercontent.com/kun432/voiceflow-samples/main/voice-permission/everyday-reminder-with-permission-card.vf

フローはこんな感じになります。見た目はだいぶ変わっていますが、設定はほとんど変わっていませんので、フローの詳細については説明を割愛します(以前の記事をご覧ください)。今回はこれをベースに音声パーミッションを追加したいと思います。

f:id:kun432:20210612174503p:plain

リマインダーのパーミッション

リマインダーを使うには、ユーザのパーミッション(許可)を受ける必要があります。フローの中ではここです。

f:id:kun432:20210612174621p:plain

f:id:kun432:20210612173136p:plain

最初にUser Info Stepでリマインダーのパーミッションをチェックします。許可されていなければ、No Accessに進みます。

f:id:kun432:20210612175215p:plain

そしてPermission Stepでリマインダーのパーミッションを求めます。

f:id:kun432:20210612175226p:plain

実際に実行してみるとこうなります。(説明のためにAlexa開発者コンソールのテストシミュレータを使用していますが、リマインダーは実機でないと確認できませんのでご注意ください。)

f:id:kun432:20210612175818p:plain

Alexaアプリで、アクティビティーを開いて権限を設定します。

f:id:kun432:20210612180421p:plain

f:id:kun432:20210612180430p:plain

そして再度スキルを起動します。

f:id:kun432:20210615223025p:plain

はい、先に進めましたね。これが今までのやり方です。

これまでのパーミッション設定のつらいところ

これまでのやり方のつらいところを少しシミュレーションを交えて説明してみます。

音声のやりとりが中断してしまう

まずこれです。初回実行時はユーザはスキルがどういうものかわかりません。リマインダーが使えるかどうかもわからない場合のほうが多いでしょう。その場合、初回の典型的な流れはこうなります。

アレクサ、毎日リマインダーをひらいて。

毎日リマインダースキルです。

(ユーザのリマインダーのパーミッションをチェックしてみよう。うーん、リマインダーのパーミッションは許可されていないな。ユーザに許可してもらおう)

このスキルでは、リマインダーへのアクセス権が必要になります。Alexaアプリのアクティビティーをご確認ください。

(権限の設定してもらわないと進めないし、一旦スキルは終了しよう)

(あ、権限を先に許可してあげないといけないのか・・・・スマホで設定してっと・・・あ、スキルが終了してるからもう一度呼び出さないと・・・)

アレクサ、毎日リマインダーをひらいて(ってさっきも言ったけどな・・・)

毎日リマインダースキルです。

(ユーザのリマインダーのパーミッションをチェックしてみよう。今度はOKだね。)

毎日何時何分にリマインダーをセットしますか?

・・・(続く)

パーミッションを許可するタイミングでスマホに持ち替えないといけないですし、そのタイミングでスキルは一旦終了させることが多いですよね。そうなると、ユーザにはスマホでパーミッションを設定してもらった上で再度スキルを起動してもらう必要があるわけですね。ここはちょっと面倒だしどうしてもテンポが悪くなります。これがめんどくさくて離脱してしまうユーザもいるんじゃないかと思います・・・

パーミッションをもらうタイミングを考慮する必要がある

さらにこのパーミッションをもらうタイミング、これも重要です。ダメな例を紹介しましょう。

アレクサ、毎日リマインダーをひらいて。

毎日リマインダースキルです。毎日何時何分にリマインダーをセットしますか?

朝の9時で

毎日午前9時にリマインダーをセットします。よろしいですか?

はい

(ユーザのリマインダーのパーミッションをチェックしてみよう。うーん、リマインダーのパーミッションは許可されていないな。ユーザに許可してもらおう)

このスキルでは、リマインダーへのアクセス権が必要になります。Alexaアプリのアクティビティーをご確認ください。

(権限の設定してもらわないと進めないし、一旦スキルは終了しよう)

それならもっと早く言ってよ!!!

一通り最後まで進んだ段階でパーミッションが必要、設定して最初からやり直してね、というのはユーザからするととても面倒ですよね。最初の例のように一番最初の時点でチェックしたほうがまだマシですね。

初回だけとはいえ、スマホに持ちかえて設定してもらって再度スキルを起動してもらう、というのはどうしても面倒なのですね。

音声パーミッションのいいところ

これを回避できるのが音声パーミッションになります。

音声パーミッションでは、この一連のやりとりを、スマホに持ちかえることなく音声でそのまま設定ができて、かつ、シームレスにスキルの同じセッション内で行うことができます。つまり、これまでのリマインダーが抱えていたユーザ側の負担が減るというわけです。

音声パーミッションの流れは公式ドキュメントにあります。

developer.amazon.com

図にするとこういう感じになります。

f:id:kun432:20210617003754p:plain

パーミッションを取得するところで、スキルがAlexaのエンドポイントに所定のフォーマットでリクエストを投げます。リクエストを受けたAlexaのクラウドは、パーミッションを取得するために必要な音声でのやりとりをスキルに成り代わって行ってくれます。この間、スキルは何もする必要はなく、単に結果を受け取るだけになります。これがSkill Connectionsの仕組みです。スキル開発者からすると、面倒なやり取りをやらなくて済むので、とても楽ちんですね。

Directive/Event Stepを使った音声パーミッションの実装

では実際にVoiceflowでやってみましょう。デモを修正する形で進めていきます。

音声パーミッションの場合、セッション内でシームレスにパーミッション取得ができます。Alexaアプリでやっていた時のように、ユーザビリティを考慮してスキル起動の最初のほうでパーミッションをチェックする、というような必要はありません。ここはバッサリと消しちゃいます。

f:id:kun432:20210615231556p:plain

f:id:kun432:20210615231608p:plain

そしてリマインダーを設定する直前で「リマインダーを設定してよいか?」の「確認」を求めるChoice Blockの”Yes”、つまり「はい」と答えた場合のフローを変更します。

f:id:kun432:20210615231836p:plain

この部分は実際にリマインダーを設定する箇所になりますが、これらは後で使うので、削除せずに別の場所に避けておきましょう。

f:id:kun432:20210615232109p:plain

Choice Stepの"Yes"に、User Info Stepをつなげてremindersを選択、つまりここでリマインダーのパーミッションをチェックします。

f:id:kun432:20210616005505p:plain

そしてチェックが成功(success)、つまりパーミッションが与えられている場合は、さきほど避けておいたリマインダー設定を行うステップをつなげます。

f:id:kun432:20210616010803p:plain

チェックが失敗した場合、つまりパーミッションが与えられていない場合はパーミッションを取得する必要があります。ここからが今回の本題です。

最初に少しご紹介したとおり、ここはSkill Connectionsの仕組みを使って、まずディレクティブの送信です。ディレクティブを送信する場合はDirective Stepを使います。以下のようにDirective Stepを配置して、User Info Stepの"no access"からつなげます。

f:id:kun432:20210616011416p:plain

そしてDirective Stepの設定に以下を入力します。

f:id:kun432:20210616011734p:plain

{
  "type": "Connections.SendRequest",
  "name": "AskFor",
  "payload": {
    "@type": "AskForPermissionsConsentRequest",
    "@version": "1",
    "permissionScope": "alexa::alerts:reminders:skill:readwrite"
  },
  "token": ""
}

音声パーミッションを呼び出すためのディレクティブはConnections.SendRequestになります。スキルがリマインダーへの読み書き権限("permissionScope": "alexa::alerts:reminders:skill:readwrite")を要求("@type": "AskForPermissionsConsentRequest")しているのがなんとなくわかりますよね。ディレクティブの各項目については、公式ドキュメントに記載がありますので、詳細はそちらを参照してください。

そして、ここからは少しトリッキーですが、Custom Code Blockをつなげて以下を入力します。

_response = {
  shouldEndSession: null
}

f:id:kun432:20210616013308p:plain

さらに、ここにRepromptをつなげます。Repromptさせる文言は設定する必要はありません。

f:id:kun432:20210616012253p:plain

ここは何をしているかというと、Voiceflowではステップの先に別のステップを繋げないと、そこでスキルのセッションが(明示的に)終了してしまいます(細かく確認できていませんが、shouldEndSessionというセッションを切断するためのフラグが有効になるのではないかなと思っています)。Skill Connectionsの仕組みでは、ディレクティブを送信する場合、ユーザとスキルのセッションは一旦切れるのですが、明示的にセッションを切っていいわけではないようで、以下のような記載があります。

Connections.StartConnectionディレクティブを返す際は、shouldEndSessionフラグを未定義のままにする必要があります。

音声パーミッションのディレクティブとはちょっと違うのですが、おそらくこのあたりも踏まえて、セッションが切れないようにするための処理だと思ってもらえればいいです。

これで、リマインダーのパーミッションがない場合は、音声パーミッションを要求するためのディレクティブが送信されます。

次に、Skill Connectionsでは送信したディレクティブのレスポンスとしてConnections.Reponseリクエストを受けとる必要があります。これを受け取るためにEvent Stepを使います。

後で色々つなげるので、少し離れたところに配置してください。

f:id:kun432:20210617230028p:plain

Event Stepの設定をしていく前に、Connections.Responseリクエストのフォーマットを見てみましょう。

{
  "type":"Connections.Response",
  "requestId":"amzn1.echo-api.request.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "timestamp":"2021-06-17T03:30:04Z",
  "locale":"ja-JP",
  "status":{
    "code":"200",
    "message":"OK"
  },
  "name":"AskFor",
  "payload":{
    "isCardThrown":false,
    "permissionScope":"alexa::alerts:reminders:skill:readwrite",
    "status":"ACCEPTED"
  },
  "token":""
}

Connections.SendRequestリクエストに対するレスポンスは、Connections.Reponseリクエストとして送信されてきます。statusでリクエストが正常に処理されたかが確認できます。ここはユーザが権限を許可したかどうか、ということではないのでご注意ください。status.code200なら正常でそれ以外がエラーになります。

payloadにもstatusがありますよね。こちらがユーザが権限を許可したかどうか、になります。以下の2通りです。

  • ACCEPTED(ユーザがパーミッションを許可した)
  • DENIED(ユーザがパーミッションを拒否した)

ここを見て、パーミッションが許可されていればリマインダーを設定、そうでなければスキルを終了すればよいでしょう。

これ以外にもう一つ、NOT_ANSWEREDというステータスもあります。これはユーザが回答しなかった、もしくはユーザの発話を認識できなかった場合を示します。ただこの場合はstatus.code204になるようです。

{
  "type":"Connections.Response",
  "requestId":"amzn1.echo-api.request.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "timestamp":"2021-06-15T18:23:41Z",
  "locale":"ja-JP",
  "status":{
    "code":"204",
    "message":"Session cancelled by user."
  },
  "name":"AskFor",
  "payload":{
    "isCardThrown":false,
    "permissionScope":"alexa::alerts:reminders:skill:readwrite",
    "status":"NOT_ANSWERED"
  },
  "token":""
}

では、上記に合わせて、Event Stepを設定していきましょう。

f:id:kun432:20210617233018p:plain

Event Request NameにはConnections.Reponseを指定します。 次に、Event Stepで受け取った情報はdataという名前で受け取れるので、一旦まるっとdata変数に入れておきます。dataの中には上のConnections.Responseリクエストがそのまま入ってますので、それぞれのパラメータにはdata.*でアクセスすることができます。

あとは、Directive Stepで送信した際のイベント名(data.name)とマッチするか?リクエスト処理が正常に行われたか?(data.status.code)ユーザがパーミッションを許可したか?(data.paylaod.status)、は処理を判定して分岐するのに必要なため、それぞれ変数に入れています。

分岐させる前に少しだけいじります。Custom Code Stepをつなげて、以下の通り入力します。

f:id:kun432:20210617234323p:plain

Voiceflowでは、Condition Stepで値の比較を行う場合、数値は数値として比較、文字列は文字列として比較します。上記のリクエスト中のstatus.codeは文字列で返ってきています。

  "status":{
    "code":"204",
    "message":"Session cancelled by user."
  },

Condition Stepでは、文字列としての"200"と数値としての200は別のものとしてしか判断できず、数値を文字列として指定することもできないようです。そこで、一旦数値に変換を行っています。これで条件分岐に使うことができます。

ではここから取得した結果を踏まえて、分岐させていきましょう。まずは、Directive Stepで送信した際のイベント名とマッチするか?です。Connection.SendRequestではAskForという名前でリクエストを送っていました。これに対するレスポンスであるConnection.ResponseリクエストもAskForという名前で返ってきますので、ここが一致するか?つまりリクエストと対になるレスポンスか?を確認しています。

f:id:kun432:20210617235512p:plain

もしそれ以外の異なる名前のイベント名だった場合は処理を行わない(else)のですが、ここでもConnection.SendRequestのときと同じようにshouldEndSessionフラグをnullに指定しています。これがないと万が一違うイベントを受け取ってしまうと、セッションが終了してしまうためです。まあここはお約束みたいなものだと思ってください。

f:id:kun432:20210618001556p:plain

さらに、Condition Stepをつなげて、処理判定しましょう。

f:id:kun432:20210618001801p:plain

ここでは、

  • 200(処理が成功した)
  • 204(ユーザが何も回答しなかった)
  • それ以外(エラー)

という3つの分岐を設定します。処理が成功しなかった場合は、以下のようにSpeak Stepでメッセージを発話させてスキルを終了させましょう。

f:id:kun432:20210618002159p:plain

処理が成功した場合は、さらにユーザがパーミッションを許可したのか、拒否したのかをチェックします。ということで、さらにCondition Stepをつなげて条件を設定します。

f:id:kun432:20210618002438p:plain

パーミッションが拒否された場合は、メッセージを発話させてスキルを終了させます。

f:id:kun432:20210618002609p:plain

パーミッションが許可されると、リマインダーの設定が可能になりますので、元のフローにあるReminder Stepにつなげてリマインダーを設定させましょう。

f:id:kun432:20210618002808p:plain

最終的にはこういう感じになります。

f:id:kun432:20210618003422p:plain

スキルとしてはDirective Stepで一旦会話フローが途切れるのですが、そこからAlexaとユーザのパーミッション取得のやり取りが行われて、それが終わるとEvent Stepに戻ってきてスキルの会話フローが再開する、という感じになりますので、Directive StepからEvent Stepにジャンプする感じで考えればよいと思います。

テスト

ではテストです。せっかくの音声パーミッションなので音声でやってみましょう。

1回目はリマインダーのパーミッションがないため、音声によるリマインダーのパーミッション設定が行われた後、スキルを終了することなく、リマインダーの設定まで進んでいます。そして、2回目はすでにパーミッションがあるので、音声パーミッションは行われずにリマインダーが設定されていますね。

いちいちAlexaアプリを開くことなく、音声だけでシームレスに利用できるのがわかっていただけましたでしょうか?

まとめ

多くのユーザにスキルを継続的に利用してもらうための施策として、リマインダーはとても重要です。リマインダーのパーミッション設定は最初の1回だけ設定してもらえれば良いものですが、それでもユーザからすると煩雑で、ユーザビリティもお世辞にも良いとは言えず、そこで離脱するユーザも多かったのではないかと思います。音声だけでシームレスにできれば、リマインダー設定のハードルが大きく下がり、リマインダーによる定期的なリテンションも期待できるのではないでしょうか?

完成後のデモは以下よりダウンロードできますので、是非試してみてください!

https://raw.githubusercontent.com/kun432/voiceflow-samples/main/voice-permission/everyday-reminder-with-voice-permission.vf

※右クリックでダウンロード

次回は今回のSkill Connectionsのテクニックを応用して、VoiceflowでAmazon Payを実装してみたいと思います!