Voiceflowのアップデートがいろいろある中で、最後にご紹介するのはPromptブロックです。これVoiceflowに慣れていないとちょっとわかりにくいと思いますので、内容的には中級者以上だと思っていただければと思います。
そもそも
Promptブロックの話をする前に、そもそもなぜPromptブロックが必要になったか?とう経緯的な話をします。まずは以下のフローを見てもらいましょう。
よくあるお天気アプリをイメージして、天気と気温が聞けるというサンプルを作ってみました。実際には住所情報などから天気・気温情報をどこかのAPIから取得してくるとかやると思いますが、サンプルなのでそこはご容赦ください。スキルの動きはこんな感じになります。
はい、普通ですね。天気や気温を聞いたあと、別のことを聞きたいか?をユーザに確認して、「はい」の場合には、ちょっと線が見にくいですが、もう一度別のインテントを受けるためにグルっとChoiceブロックに戻しています。「いいえ」の場合にはスキルを終了します。
では少し違った聞き方をしてみましょう。「はい」「いいえ」のところで、直接、天気か気温を聞きたいとします。
答えてくれませんね。ここはChoiceブロックでYesIntentかNoIntentだけしか設定していません。そして、ChoiceブロックのElseではそれ以外は再度聞き直すようにしています(ちなみにここは先日ご紹介した新しいElseの設定「Reprompt」を使っています。)したがって、ここではweather_intentやtemperature_intentを呼び出すことはできません。当然ですね。
次に、weather_intentやtemperature_intentに対して、ワンショット発話ができるか確認してみましょう。いわゆる「アレクサ、〇〇〇(スキル名)に〜して(インテントを呼び出すサンプル発話)」というやつです。
ここ以前とは少し動きが変わっているようで、なんとなく動いてるように見えますが、本来のワンショット発話の場合、最初の発話をスキップしてそのインテントが呼ばれるのが正しい動きのはずです。つまりこう。
アレクサ、◯◯◯◯に天気を聞いて
明日の天気は晴れのち曇りだよ。他にも聞きますか?
で、Voiceflowでワンショット発話を行う場合、以前にご紹介したIntentブロックを使う必要があります。
Intentブロックを使うとこうなります。
Intentブロックをそれぞれのインテントフローにつなげてワンショット発話に対応することで、Homeから流れる会話フローに沿うことなく、直接インテントを呼び出すことができるということですね。
これ、何を言っているかというと、Voiceflowではインテント呼び出し時に「ステート管理」というものを行っているということです。
ステート管理とは?
ステート管理、という言葉、多分ask-sdkでコードを書いている方ならわかると思いますが、これ開発者的視点に立った言葉なのでVoiceflowで始めた方はわかりにくいと思います。少し説明します。
アレクサ自体(というか、alexa skills kitとかask-sdkですね)は本来すべてのインテントを「グローバル」に受けます。つまりそこには前提となるコンテキストは基本的にありません。今回の例でいうと、
- 「天気を教えて」という発話に対するweather_intent
- 「気温を教えて」という発話に対するtemperature_intent
- 「他にも聞きますか?」という問いに対する「はい」「いいえ」を受けるためのYesIntent/NoIntent
を「会話フローのどこからでも」「いつでも」受けることができます。正しくないかもですが、イメージとしてはこんな感じ。
※実際にはいつでも・どこからでもいけるのでもっと線は多くなると思います。
ただ、これは以下の点で都合が悪いです。
- 文脈を無視して受けることが可能なので、開発者の想定した会話パス以外の動きになってしまう。例えば、「天気を教えて」「気温を教えて」という発話を期待しているところで「はい」「いいえ」を言われた場合とか。
- 今回のように「はい」「いいえ」をビルトインインテントであるYesIntent/NoIntentを使って、かつ2箇所で使用している場合、どちらの会話パスでの「はい」「いいえ」なのか見分けがつかない。
こういうことですね。
そこで、コードで書く場合は、セッションアトリビュートというものを使って、「状態」≒「現在のコンテキスト」を常に持ち回すことにより、見分けるようにします。今回のケースだと、
- weather_intentのフローに入ると「今weather_intentのフローにいるよ」、temperature_intentのフローに入ると「今回はtemperatureのフローの方だよ」ということを記憶しておいて、Yes/Noが発話された場合には「どちらのフローにいるか?」を踏まえた上で処理を行う(今回の例だとどちらも動きは同じなのであまり意味はないですが)。
- フロー内でChoiceブロックに指定していないインテントについては呼び出せないようにする。最初のChoiceブロックのフロー、つまりweather_intent/temperature_intentに「入る前野フロー」は「現在のフロー」の外側になるため、呼び出せない。
Voiceflowでは、会話を「フロー」という考え方に基づいて線でつなげていく仕組みになっています。つまり、線が引かれていない処理にいきなり流れることはありません。線が分岐するタイミングで必要なステート管理を裏で行うことで、開発者が作った会話フローが成り立つようになっているのだと思われます。
会話フロー的な考え方の弊害
こういっためんどくさいステート管理を意識せずにスキル開発が行えるのがVoiceflowの素晴らしいところですが、逆に弊害もあります。上で述べた通り、線が引かれていない処理にいきなり流れることはない、というのは逆を言うと、会話フローに柔軟性がないということの裏返しでもあります。こういうことです。
天気を教えて
明日の天気は晴れのち曇りだよ。他にも聞きますか?
気温をおしえて
ごめんなさい、よくわかりません。はい、か、いいえ、で答えてください。
人間同士の会話ならこういうことはないですよおね。
人間は常に決められた会話のフローに沿って話をしているわけではありません。コンテキストを理解しているので、わざわざ「はい」という明示的なものを省略していきなり目的を話すこともします。こういうのに対応しようと思うと、どこからでもインテントを「グローバル」に呼び出せるような仕組みが欲しくなります。つまりアレクサの標準的な仕組みのほうがいい場合もあるわけです。
Voiceflowでもこれに対応するためにいくつかの方法があります。
Intentブロック
まずはさきほどワンショット発話で説明したIntentブロックです。Intentブロックは、あるインテントをスキル「内」のどこからでも、またスキルの「外」(つまりワンショット発話)からでも呼び出せるようにするためのブロックです。
Intentブロックを見てもらうとわかると思うのですが、Intentブロックから他のブロックに線を引くことはできますが、逆に他のブロックからIntentブロックに線を引くことはできません。つまり本来Homeから始まる会話フローと別の会話フローを始めるための仕組みといってもよいです。
さきほど、ワンショット発話のためにIntentブロックを追加しました。きちんと動くか見てみましょう。
そしてIntentブロックはスキル内からいつでも呼び出せます。こちらも試してみましょう。
最初にテストしたときには「はい」「いいえ」しか受けれなかったのですが、きちんと受け取れるように変わってますね。
コマンドフロー
これ多分初めて取り上げるかもしれません。とはいいつつ、申請までやったことがあれば必ず触っているStop/HelpのFlowブロックが使っています。
標準的なFlowブロックの使い方は、以前に書いた英語向けスキル内課金のチュートリアルの中に少し書いています。(これもコンテンツを新しいUI向けに更新しないと、汗)
図にするとこんな感じです。
プログラミングで言うところの、GOTO、もしくは関数に近い使い方で、別のフローに行って戻ってくる、みたいな感じですね。
で、もちろんこれだけだとグローバルにはならないのですが、「ヘルプ」や「ストップ」もFlowブロックなのにグローバルに受けれるようになっています。なんででしょうか?
ということで、Homeブロックを見てみましょう。
呼び出しのプロンプトが表示されている下に「Commands」というのがあり、StopとHelpが表示されていますね。次に、Homeブロックをクリックして、"What are commands?"と書いてあるところをクリックしてください。
コマンドは、ユーザーがプロジェクトのどこからでもアクセスすることができます。例えば、ユーザーが「ヘルプ」と言うと、ヘルプフローが起動します。ヘルプインテントはコマンドに格納されているので、ユーザーはフローを終了すると、プロジェクト内で以前にいた場所に戻ります。
コマンドは、プロジェクト内のジャンプとリターンの関数と考えることができます。
はい、つまり、フローの入り口と出口を指定せずに、インテントが呼び出されれると、それに結びついたフローに飛ぶという「グローバルなChoice&Flowブロック」のようなものということです。ヘルプコマンドの設定を見てみましょう。
はい、インテントとフローが設定できるようになっていますね。ヘルプはビルトインインテントなのでサンプル発話が不要ですが、自分でカスタムインテントを設定した場合にはこうなります。
はい、サンプル発話も設定可能ですね。
ちなみにヘルプがどんな動きになるか?は以下の記事で紹介しています。
つまり、Voiceflowのヘルプは、ヘルプの説明を行った後、ヘルプを呼び出した時点、厳密に言うと、ユーザ発話が発生する手前のブロックに戻ってくるのですね。
・・・
会話の中で急に違う話をすると、「あれ?さっきまでなんの話してたんだっけ?」ってことは日常でもありがちですが、Voiceflowのヘルプではそういうことが起きにくいようになっているというわけです。図にするとこんな感じ。
非常に地味な感じなんですけど、これ、真面目にコードでやろうとすると、今会話のどこにいるか?という「状態管理」をしないといけなくて、かなり面倒なんですよね。それが特に意識することなく実現されていて、開発者としては文言を変えるぐらいだけで簡単にできてしまうのはほんとすごいなーと思いますね!
ただ、上にも書いたとおり、コマンドはどこに戻るか?を指定できません。ユーザがコマンドを呼び出した場所に必ず戻ってきます。したがって、会話フローには影響を与えないけど、いつでも聞けるような、まさに「ヘルプ」や「ストップ」向きの機能になっています。
Promptブロックができるまではどうしていたか?
ここまでに述べたように、Voiceflowは、あくまでも「線を引いて会話フローを作る」というのが基本的な前提になってます。この前提から外れない範囲でグローバルなインテントを作ろうと思うとこういうふうになります。グローバルインテントのデザインパターン的な感じですね。まず一つ目。
今回あえてChoiceブロックのElseをパスでつないでいますが、すべてが線でつながる感じで、Voiceflowのもともとの設計思想ともあっているのでとても自然です。ただ以前のUIに比べるとちょっと見にくくなってしまった感じはしますが。
これを、ちょっとひねってみましょう。Intentブロックは本来はワンショット発話のためですが、グローバルに受けれるというのをうまく活用するとこうなります。デザインパターン2です。
これ海外のユーザによるものなのですが、最初見たときは私も驚きました。Choiceブロックにインテントはありません。Elseだけです。その代わり、すべてのインテントをIntentブロックでグローバルに受けるということです。ChoiceにインテントがなければElseもいらないじゃないか?と思われるかもしれませんが、Choiceを置くことで発話待ち状態にするためとどのインテントにもマッチしない場合の処理になります。逆にChoiceを置かなければスキルは終了してしまうので必要なのです。
これ本当に動くのか?と思っていたのですが、動くらしいです。私個人の過去の経験則的には、Intentブロックはワンショット発話以外でグローバルに使おうとするとステート管理がおかしくなっていた印象があるのと、元々のフロー指向なところを崩すのは設計思想的にどうなの?という思いもありちょっと懐疑的な印象でした。また、Voiceflowを始めたばかりの方にこれを説明するのもいきなりは難しいだろうと。ただ現実的にもフロー指向ではやはり辛い部分はありますし、発想としてはとても斬新です。
ちょっと脱線しましたが、さらにひねってみましょう。デザインパターン3です。
上に比べると少し冗長なところはありますが、重要なのは、インテントごとに完全に独立したフローに分かれてしまっています。Intentブロックを使ってグローバルに受ける、ということと、ElseだけのChoiceブロックを使うという点については上のパターンと変わりません。ただ、こうなるともう「小さな会話フローの集合体」みたいな感じですね。
さらに!さらに!ひねってw、コマンドフローを使ってみます。デザインパターン4です。
もうここまで来ると何やってるのかさっぱりわかりませんねw コマンドフローは完全に会話フローは異なる概念似用に思えるので、ちょっと頭から外しておきます。
ただ、Voiceflowにおける対話モデルのデザインパターンとしては、実はこういうのもあるということで、頭の片隅ぐらいにおいておいてもらえればと思います。
あらためてPromtpブロックとは?
ということで、前フリが非常に長くなってしまいましたが、今回新しく追加されたPromptブロックについてです。Promptブロックはグローバルなインテントを受ける状態にするためのブロックです。
グローバルなインテントを実現するための肝は以下の2つです。
で、Promptブロックが実現するのは上記のうち2を置き換えるものになると思います。最初にご紹介したお天気アプリのサンプルでやってみましょう。
Intentブロックを追加して、weather_intentとtemperature_intentをグローバルにしたところからスタートです。
まず、画面中央のChoiceブロックを削除しましょう。右クリックでDeleteすればOKです。Speakブロックとくっついてますので、Speakブロックの方は削除しないようにしてください。
weather_intentとtemperature_intentのChoiceブロックも消しちゃいましょう。
完全にフローが外れた状態になりましたね。では、Promptブロックを配置して以下のように線でつなげてください。
Promtpブロックの設定を開いてRepromtpをクリック、どのインテントにもマッチしない場合の発話を設定します。
↓ ↓ ↓ ↓ ↓
少しブロックを移動して見やすくしましょう。
最後のSpeakブロックはもう不要ですね、削除しましょう。
ではテストしてみましょう。
はい、Promptブロックがグローバルにインテントを待ち受けていますね。
まとめ
今回はちょっと難しいかったかもしれませんが、ChoiceブロックにElseのみ、という一見不可解な感じよりはすっきりしますね(普通思いつかないですよね)。デザインパターン1のように普通に線を引いてもいいのですが(それはそれでわかりやすいというメリットはあると思います)、どうしても線が見にくくなってしまうので、Promptブロックを使うとスッキリします。グローバルなインテントを作りたい場合にはぜひ活用してみてください。
なお、上でご紹介したデザインパターンは、Voiceflow公式のFacebookグループに投稿されていた、ブラジルの開発者、Joao Paulo Alqueres さんのデザインパターンを参考にさせていただきました。この場を借りて感謝したいと思います。Muito obrigado, Joao!