kun432's blog

技術ネタ、読書記録、など。2015年から人生をやり直し中です。

Voiceflow TIPS #18 Googleスプレッドシート連携で作るゼロカロリースキルもどき 〜スプレッドシートの更新〜

Voiceflow夏休みAdvent Calendarの18日目です。

Googleスプレッドシート連携シリーズ第4回です。検索・登録機能まで実装した「ゼロカロリースキル」に「更新」機能を追加していきたいと思います。

前回のあらすじはこちら。

ではまずスキルの実行イメージから。


アレクサ、「ゼロカロリースキル」をひらいて

ゼロカロリースキルです。カロリーを知りたい食べ物の名前を言ってください

カツカレーを更新

カツカレーは、カツとカレーのカロリーが相殺されるから、という理由で、現在登録されています。更新する理由をお聞きします。最初に、理由、最後に、から、をつけていってください。例えば、理由、ほにゃほにゃだから、という感じで言ってください。

理由、カツとカレーのカロリー同士が喧嘩して相殺されるから

カツカレーは、カツとカレーのカロリー同士が喧嘩して相殺されるから、カロリーゼロ、で更新します。よろしいですか?

はい

カツカレーを更新しました。カロリーを知りたい食べ物の名前を言ってください・・・


さぁ、ではやってみましょう。

スキルの全体図

f:id:kun432:20190818182854p:plain

前回と同様に、更新用のフローを別に追加します。線が徐々にすごいことになってきてますね・・・こうならないように実際にはFlow Block使って処理ごとにフローを分けることをおすすめしますが、今回は全体のわかりやすさを優先してます。

Interactin Blockへ更新フローの追加

まずは、最初から更新を目的とする場合です。前回と同じように、更新用のフローを新たに作っていきますので、更新用のフローへの入口となるインテントを追加します。Home -> Speak の次にあるInteraction Blockをクリックします。

f:id:kun432:20190818184537p:plain

Interaction Blockの設定を順に見ていきます。Slotタブは今回も変更不要です。

f:id:kun432:20190818184914p:plain

Intentsタブに新たに更新用インテントのサンプル発話を登録していきます。"+ Add Intent"をクリックして、インテント名は"update_intent"、サンプル発話は以下のような感じで登録します。

f:id:kun432:20190818184927p:plain

Choicesタブに更新用のフローを追加します。"+ Add Choice"をクリック、ドロップダウンから先ほど作成した"update_intent"を選択、"+ Add Variable Map"をクリックして、他のインテントと同じように、スロット"[slot_recipe]"(食べ物名を受け取るスロット)と変数”varRecipe”(食べ物名を入れておくフロー変数)のマッピングを作成します。

f:id:kun432:20190818184937p:plain

これで、更新用フローができました。ここからブロックを繋げていきます。

更新フローの作成

f:id:kun432:20190818185305p:plain

更新の場合、すでに食べ物名が登録されていることが前提となります。が、万が一、登録されていない場合はどうなるでしょうか?ということで、一旦登録されているかどうかのチェックを行います。

Integration Blockを使って食べ物名の存在チェックをしましょう。Integration Blockをおいて、先程のInteraction Blockから線をつなげます。

f:id:kun432:20190818191531p:plain

Integration Blockの設定を行います。基本的にやっていることは「検索」と同じです。

f:id:kun432:20190818192345p:plain

で、スプレッドシート検索の結果と変数と紐付ける Mapping Output のところですが、後でやる「更新」の場合に備えて、少しだけ前のやり方と変わります。順にやっていきます。"+ Add Mapping"をクリックして、

f:id:kun432:20190818193636p:plain

カラムのところは"Row Number"を選択します。"Row Number"はスプレッドシートの何行目か?を取得するために予め用意されているビルトインの設定で、これを取得するようにします。で、これを保存しておく変数を作ります。変数を選択するドロップダウンの一番下に"Create variable"があるのでこれをクリックします。

f:id:kun432:20190818193942p:plain

左のメニューが変数のメニューに変わりますので、ここでフロー変数"varRowNum"を作成します。

f:id:kun432:20190818194022p:plain

再度、Integration BlockのMapping Outputの設定に戻って、カラム"Row Number"と変数"varRowNum"を紐付けます。

f:id:kun432:20190818194141p:plain

最後に、検索時と同じようにカラム"reason"と変数"varReason"を紐付けるマッピングを追加すればOKです。

f:id:kun432:20190818194242p:plain

最終的にはこんな感じです。

f:id:kun432:20190818194453p:plain

ちょっと見にくいので、Expandで表示したものも貼っておきます。

f:id:kun432:20190818194729p:plain

では、Test Integrationをクリックしてテストしてみます。

f:id:kun432:20190818200137p:plain

適当にすでに登録済みのものを入力してみてください。今回は「ラーメン」で検索してみました。

f:id:kun432:20190818200202p:plain

検索のときと結果は変わらないですよね。1点だけ補足しておくと、先程Mapping Outputで設定した"Row Number"はこの結果の"row"の値になります。

f:id:kun432:20190818200355p:plain

実際のスプレッドシートの方も見てみましょう。確かに行番号が取れているのがわかります。更新時は、この行番号をとっておくことが重要になります。

f:id:kun432:20190818200500p:plain

スプレッドシートの検索処理に失敗した場合のフローも用意しておきます。ただし、このフローは結果が0件の場合ではなく、あくまでもスプレッドシートへのアクセスで何か問題があった場合なのでご注意ください。

f:id:kun432:20190818201036p:plain

スプレッドシートの検索結果をチェックします。If Blockをおいて、以下のように、"varReason"の値が定義されていない、すなわち、検索結果が0件だったかどうかをチェックする条件を入力します。ここは expression を使ってください(expressionの使い方はTIPS #12の「⑤⑥⑦ If Blockで検索結果の判定」を参照してください)

f:id:kun432:20190818201631p:plain

ちょっと条件のところは見にくいのでいかに貼っておきます。変数のところはそのままコピペでは動かないので注意してくださいね。

equal({varReason},undefined)

"varReason"の値が定義されていない、すなわち、検索結果が0件だった場合は、更新ができないので、その旨を発話させて、

f:id:kun432:20190818201733p:plain

最初のInteraction Blockに戻してやります

f:id:kun432:20190818201748p:plain

ということで、お気づきになられた方がいらっしゃるかもしれませんが、これ、検索フローでやったこととほとんど同じです。

f:id:kun432:20190818203159p:plain

なので、こういう場合は、ブロックのコピペを使うと良いです。ということで、先程のIf BlockとSpeak Blockをコピペで作る手順もご紹介しましょう。

ブロックのコピー&ペースト

この状態から・・・

f:id:kun432:20190818203355p:plain

Shiftキーを押しながら、検索フローのIf Block と Speak Blockをクリックします。少しだけハイライトされているのがわかりますか?(違いが見えにくいですよね・・・これは既に開発元に修正要望上がってるのでそのうち治るかもしれません。)

f:id:kun432:20190818204259p:plain

この状態で、WindowsならCtrl+C、MacならCommand+Cを入力すると、以下のように選択したブロックがクリップボード上にコピーされたことがわかります。

f:id:kun432:20190818204311p:plain

ペーストしたい場所がだいたい中心になるようにCanvasを動かして、WindowsならCtrl+V、MacならCommand+Vを入力してみてください。すると、

f:id:kun432:20190818204748p:plain

あとは、位置を整えて、線を引いて、文言等修正しないといけないところは修正すればOKです!かんたんですね! If BlockでExpression使う場合とか、Code Blockで似たようなコードを書く場合などは、煩雑だしミスも起こりやすいと思うので、コピペもうまく活用してもらえればと思います。

少し脱線しましたが、続けましょう。If Blockで登録されていることが確認できたら、更新の準備ができたことになりますので、更新後の理由を取得していきます・・・ってこれも聞いたことありますよね?

これ、登録時のフローとほぼ同じですね!ということは、ここフローごとマルっとコピペして修正すればOKですね!

先程は、Shiftキーを押しながら、ブロックを順にコピーしましたが、Shiftキーを押しながら範囲選択してコピーすることもできます。

f:id:kun432:20190818213945p:plain

f:id:kun432:20190818214127p:plain

で、ちょっとCanvasを動かしてペーストします。

f:id:kun432:20190818214435p:plain

同じフローがまるっとコピーされました!範囲選択した範囲内に限り、線もコピーされていますね。あとは、更新フローからつなげて、修正していけばOKというわけですね。楽ちんです!順次修正していきます。

更新フローの作成(続き)

まず、最初のSpeak Block。ここは文言の修正だけです。登録時は理由の発話の仕方だけを説明していましたが、更新時は今登録されている理由の内容も聞いた上で、理由の発話について説明します。

f:id:kun432:20190818220802p:plain

次、実際に理由を取得するInteraction Block。ここは変更する必要なしです。理由を取得するだけなので、登録時も更新時もやることは全く変わりませんので、同じインテントをそのまま使えます。

f:id:kun432:20190818221015p:plain

更新の確認を発話するSpeak Block。ここも「登録」を「更新」に変えるだけです。

f:id:kun432:20190818221125p:plain

確認に対して「はい」「いいえ」を受け取るInteraction Block、ここも変更する必要はありません。

f:id:kun432:20190818221238p:plain

で、「いいえ」の場合、ここも「登録」を「更新」に変更するだけです。そして、最初の検索のInteraction Blockに戻します。

f:id:kun432:20190818221500p:plain

f:id:kun432:20190818221511p:plain

では、Integration Blockで更新処理です。

Integration Blockでスプレッドシートの更新

Integration Blockを登録フローからコピペしてきたので、現在はスプレッドシートへの(新規)登録になっています。

f:id:kun432:20190818222816p:plain

これを更新処理に変更していきます。まず最初の”I want to”のところ、更新なので"Update Data"を選択します。

f:id:kun432:20190818230241p:plain

アカウントは選択するだけです。

f:id:kun432:20190818230257p:plain

スプレッドシートの設定が消えているので、検索・登録と同じように設定してください。

f:id:kun432:20190818230616p:plain

次に、With Settingで条件を設定するのですが、設定項目が、検索の時とも登録の時から変わってますよね?

f:id:kun432:20190818230757p:plain

更新時は、「何行目("Row Number")のカラム("recipeName"と"reason")をどう更新するか?」という設定の仕方になります。したがって、スプレッドシートの「何行目」のデータなのか?ということが必要になります。更新フローの最初で「何行目」か?を事前に取得していたのはこのためなのです。

ということで、必要なデータは全て揃っていますよね?それぞれ入力しましょう。Row Numberは"{varRowNum}"、recipeNameは"{varRecipe}"、reasonは"{varReason}"というふうに、変数を設定します。終わったらNextをクリックします。

f:id:kun432:20190818231307p:plain

では、Test Integrationでテストしてみましょう。

f:id:kun432:20190818231604p:plain

更新の場合は、既存のデータを変更することになりますので、こういう警告が出ます。この辺の配慮は嬉しいですね。Confirmをクリックして進めましょう。

f:id:kun432:20190818231710p:plain

では更新します。既に登録されている食べ物名、かつ、その行番号を設定して、RUNをクリックします。

f:id:kun432:20190818232351p:plain

以下のように"Updated"となっていればOKです。

f:id:kun432:20190818232535p:plain

スプレッドシートの方も更新されていますね!

f:id:kun432:20190818232558p:plain

はい、これで今日のメインテーマであるスプレッドシートの更新処理は完了です!残りのフローも修正していきましょう。

スプレッドシート更新エラーの場合のメッセージも「登録」から「更新」に変えます。

f:id:kun432:20190818233437p:plain

スプレッドシート更新に成功した場合もメッセージも「登録」から「更新」に変えます。そして、最初の検索のInteraction Blockに戻すように線をつなげます。

f:id:kun432:20190818233452p:plain

f:id:kun432:20190818233510p:plain

はい、これで更新フローは完了です。おつかれさまでした!

更新フローのテスト

ではテストです。

f:id:kun432:20190819005007p:plain

うまく更新できていますね!登録されていないものを更新しようとした場合のテストもやっておきましょう。

f:id:kun432:20190819005748p:plain

うまくいきませんね・・・・なぜかスプレッドシート自体へのアクセスが失敗した流れになってしまいます・・・検索フローのときはきちんとできていたのに・・・

f:id:kun432:20190819005823p:plain

で、調べててみたんですが、更新の場合は更新対象となるレコードの行番号が必要なので、row numberを取得するようにしているんですが、どうもこれがあると、Integration Blockの時点でfailになってしまう様子。ここちょっと不具合な気もします。ということで暫定ですが、ここだけ少し線を変えておきましょう

f:id:kun432:20190819010433p:plain

はい、これでテストします。

f:id:kun432:20190819010406p:plain

とりあえず良しですかね。ここは開発元にも確認投げてますので、回答きたらまた修正したいと思います。


ということで、検索・登録・更新と完了しましたので、残りは削除ですね。これでGoogleスプレッドシートでできることは一通りご紹介できたことになると思います。よろしければ次回もご覧くださいませ。

Voiceflow TIPS #17 音声・音楽データの再生(Stream Block編)

Voiceflow夏休みAdvent Calendarの17日目です。

前回に引き続き、音声・音楽データ再生の2回目、今回はStream Blockを使ってみたいと思います。

Stream Blockの最大の利点は、Speak Blockであった4分の制限がないことです。したがって、よくある30分〜1時間程度の環境音スキルを作るには最適ですね!

といいつつ、実は私もStream Blockをそれほど使ったことはありませんので、とりあえずいろいろやってみたいと思います。

Stream Blockの使い方

Speak Blockだけ用意して、Stream Blockをつなげます。

f:id:kun432:20190817215020p:plain

Stream Blockをクリックすると、メニューが表示されます。結構縦に長いので、スクロールしないと見えないですね・・・それはさておき。Speak BlockのAdd Audioと同じように、音声・音楽ファイルをドラッグアンドドロップでアップロードすればOKです。

f:id:kun432:20190817215443p:plain

今回は、フリーのBGM素材を提供されているサイト「DOVA-SYNDROME」さんで、素材を多数公開されている「しんさんわーくす」さんの以下の素材を使用させていただきました。

アップロードします。素材データの再生時間が、8分半ありますので、前回よりも少し時間がかかりますが、アップロードできました!(日本語のファイル名だとうまく表示ができなかったので、ファイル名だけ変更してアップロードしてます。)

f:id:kun432:20190817220403p:plain

▶を押すとその場で再生されます。また、時間が8:38であることがわかりますね。Speak Blockだと再生時間の合計が4分以上の場合、スキル実行時にエラーになっていましたよね。今回はどうなりますでしょうか?アップロードしてテストしてみましょう。

f:id:kun432:20190817220816p:plain

エラーになりました・・・・右下のメッセージを見ていただくとわかるのですが、「AudioPlayerはサポートされていない」とあります。AudioPlayerとはなんでしょうか?

公式のリファレンスは以下にありますが、

こっちのほうがわかりやすいかもしれません、

まず、AudioPlayerですが簡単に言うとユーザからの発話をトリガーに音楽を再生してくれる機能になります。

はい、「音楽再生専用の機能」ということですね。Stream Blockを使うとAudioPlayer機能を使った音楽スキルになるということですね。したがって、これまでやってきたような音声でいろいろやりとりして、というものとは少し目的も動作も違ってきます。その点については予め注意してください。細かいところは後で説明していきます。

で、AudioPlayerの機能を使ったスキルは、Alexa開発者コンソールではテストできないので、実機でテストしてみましょう。以下に動画を用意しました。

はい、これだけです。めっちゃかんたんですね!ということで、以上、Stream Blockのご紹介でした!

・・・というのでは面白くないので、もう少し見てみましょう。

Stream Blockに画像やタイトルなどをつける

Stream Blockの下の方にある入力欄に、タイトルや説明、アイコンおよび背景画像等を設定することができますね。これを入れるとどうなるのでしょうか?

f:id:kun432:20190817232300p:plain

※背景画像はPixabayで公開されている「bertvthul」さんの画像を利用させていただきました。

pixabay.com

アップロードした後、実機でテストしてみましょう。

f:id:kun432:20190817232831j:plain

f:id:kun432:20190817232845j:plain

画面付きデバイスだとこういう感じで表示されます。いいですね。

複数の音楽を再生する

先程の動画、気づいた方がいらっしゃるかもしれませんが、途中で画面が普段の時計画面に戻りました。

f:id:kun432:20190818155308p:plain

でも音楽は続いています。これってどういうことでしょうか?

以下のサイトの「AudioPlayerのもつライフサイクル」という箇所がわかりやすいと思います。

あくまでも私の理解なんですが、ポイントしては、

  • 通常のカスタムスキルと異なり、ずっとセッションが継続するわけではない。AudioPlayerスキルの場合、音楽を再生すると一旦セッションが切れる。イメージとしては、音楽再生前までは自分で作ったスキルの会話フローが動作していて、音楽再生が始まるとアレクサにおまかせ、するようなイメージ。
  • 音楽再生の途中、すなわち、スキルから見るとセッションがない状態でも、AudioPlayer特有のインテントが有効になっていて、例えば「一時停止」「次の曲」「前の曲」などを発話すると、スキル側のフローが有効になる。ただし、あくまでもAudioPlayer特有のもののみ。

っていう感じでしょうか。要は、音楽再生に特化しているので、通常のカスタムスキルのような自由な会話フローができるわけではないけど、音楽を聞くために必要な動作は予め用意したものを使える、という感じだと思います。

なので、スキル側としては、AudioPlayerで用意されたインテントに合わせて作れば良い、という感じですね。早速やってみましょう。

4曲ぐらい使って、やってみました。Stream Blockは4つあって、それぞれ違う音楽、違う画像を設定してあります。ここでも「しんさんわーくす」さんのものを利用させていただきました。

f:id:kun432:20190818003600p:plain

ちょっと線がすごいことになっていますが、少しピックアップして見てみましょう。

f:id:kun432:20190818003958p:plain

Stream Blockに、"next" と "previous"があるのがわかりますか?これがそれぞれ「次の曲」「前の曲」というAudioPlayerの標準機能に合わせた分岐となっています。1番目のStream Blockの次("next")は2番目、2番目の次は3番目、といった感じでつなげていきます。逆に"previous"の方は、2番目の前は1番目、1番目の前は4番目という感じですね。

実機で動かすとこんな感じになります。

「次の曲」「前の曲」で曲が変わっていますね。いい感じです。

あと、曲と曲の間で「サンプルサウンド1です」みたいなのが聞こえましたでしょうか?この部分はSpeak Blockで動かしています。つまり、曲の再生が始まるとスキルのセッションは一旦切れてAudioPlayerとしての動作しかできなくなりますが、曲が終わったタイミングでスキルにセッションが戻ってくるということですね(すいません、このあたりやや語弊がある言い方をしている自覚がありますので、あくまでもイメージです。不備等あればご指摘いただければと思います。)このあたりをうまく使えば、AudioPlayerの機能に限定されないスキルが作れそうな気がします。

まとめ

いかがでしたか?2回に渡って、音声・音楽データの再生をやってみました。まとめとしては、

  • スキルの会話フローの中に取り込む形ならSpeak Block
  • より音楽再生に特化するならStream Block

という感じかなと思います。環境音スキルはAlexaスキルの中でも非常に人気ですので、アイデアと音声データをお持ちであればぜひトライしてみて下さい。

あと、今回、無料素材をサンプルで使わせていただきましたが、素材を利用する際は、素材提供サイトや素材作成者等の利用規約等を必ずご確認の上でご利用いただくことをおすすめします。

Voiceflow TIPS #16 音声・音楽データの再生(Speak Block編)

Voiceflow夏休みAdvent Calendarの16日目です。1日ビハインドなので、キャッチアップしていきます!

今日は音声・音楽データ再生をやってみたいと思います。効果音的な使い方はもちろんですが、人気の環境音スキルなんかも、Voiceflowだととても簡単にできちゃいます!

Voicflowでの音声データ・音楽データの再生の方法は2つあります。

  1. Speak Blockを使う
  2. Stream Blockを使う

今回は、Speak Block編です。

Speak Block

Speak Blockでできるのは以下の3つです。ここは効果音的な使い方がメインになると思います。

  • 直接SSMLを書く
  • Add Audioで指定する

直接SSMLを書く

一番簡単なのはAlexa Skills Kitサウンドライブラリです。コピペするだけで使える効果音が2500個以上もあり、Alexaスキルからは無料で利用できます。

ブラウザで以下のURLにアクセスしてサウンドライブラリのページが開きます。 https://developer.amazon.com/ja/docs/custom-skills/ask-soundlibrary.html

f:id:kun432:20190817153322p:plain

使える効果音の一覧が表示されます。入力欄があるので、使いたい効果音のキーワードを入力して検索もできます。

f:id:kun432:20190817154104p:plain

使いたい効果音をクリックすると、メニューが表示されます。真ん中のHTMLタグっぽいのがSSMLです。▶をクリックすると、その場で再生もできます。"Copy to Clipboard" をクリックして、コピーします。

f:id:kun432:20190817154121p:plain

Voiceflowの画面で、Speak Blockのテキストボックスの中にペーストします。

f:id:kun432:20190817154504p:plain

はい、これだけですね。とてもかんたんです。SSMLであることすら意識しないぐらいですね。

もちろん、自分で用意した音声ファイルを使うようにSSMLで書くこともできます。先程のサウンドライブラリのSSMLで使用されていたaudioタグのsrcに自分で用意した音声ファイルのURLを指定すればOKです。

f:id:kun432:20190817160714p:plain

ただし、ファイルを自分で用意する場合にはいくつか注意があります。それは後ほど。

Add Audioで指定する

Add Audioを使うと、自分で用意した音声ファイルをVoiceflowにアップロードすることができます。

Speak Blockで"Add Audio"をクリックします。

f:id:kun432:20190817164724p:plain

音声ファイルをドラッグ&ドロップします。

f:id:kun432:20190817164816p:plain

アップロードされました。▶をクリックするとその場で再生されます。

f:id:kun432:20190817165022p:plain

とてもかんたんですね!

また、別の場所にある音声ファイルを指定することも可能です。Add Audioをクリックして、Add URLをクリック、音声ファイルのURLを指定します。

f:id:kun432:20190817165842p:plain

別の場所の音声ファイルをURLで指定する場合の注意

SSMLのaudioタグでのURL指定、Add AudioでのURL指定、どちらの場合も外部においたファイルをアレクサが参照することになりますので、所定の要件やフォーマットを満たす必要があります。

MP3ファイルのURLを指定します。次に挙げる要件と制限にご注意ください。

  • MP3は、インターネットからアクセス可能なHTTPSエンドポイントでホストされる必要があります。HTTPSであることが必須です。また、MP3ファイルをホストするドメインは、有効で、信頼できるSSL証明書を提示する必要があります。自己署名証明書は使用できません。
  • MP3には、ユーザーに固有な情報や、その他の機密情報が含まれていてはなりません。
  • MP3は、有効なMP3ファイル(MPEGバージョン2)である必要があります。
  • 音声ファイルは240秒以内でなければなりません。
  • ビットレートは48 kbpsでなければなりません。このビットレートは、話される内容と組み合わせて使用すると良好な結果が得られますが、音楽を再生するには一般に品質がやや不十分です。
  • サンプルレートは22050Hz、24000Hz、16000Hzのいずれかです。

必要に応じて、変換ソフトウェアを使用してMP3ファイルを必要なコーデックバージョン(MPEGバージョン2)とビットレート(48 kbps)に変換してください。

もしうまく再生できない場合などは上記をご確認ください。

なお、Add Audioでドラッグ&ドロップでファイルをアップロードした場合はVoiceflow側で最適なフォーマットに自動変換してくれますので、これを使うのが一番楽ちんです!(まれにアップロードがうまく行かない場合があります。)

多数の音声データを使う場合の注意

音声・音楽データを使うと、非常にかんたんに賑やかな感じが演出できるのでとてもおすすめなんですが、何も考えずにたくさん使うとこうなる場合があります。

f:id:kun432:20190817172825p:plain

開発者コンソールのスキルI/Oでエラーを見て見るとこんな感じ。

f:id:kun432:20190817172947p:plain

The SSML response exceeds the maximum number of 5 audio elements"

とあります。

また、別の場合には、スキルは起動するけど、途中で「スキルがリクエストに正しく応答できませんでした」になる場合もあります。

f:id:kun432:20190817173820p:plain

その場合のエラーはこんな感じ。

f:id:kun432:20190817173851p:plain

見えにくいのエラーメッセージを貼ります。

Invalid Audio Content for requestId amzn1.XXXXXXX Error: The total duration of audio content exceeds the maximum allowed duration of 240000 milliseconds

ここでもう一度、公式のリファレンスを見てみましょう。

サービスから送信する1回の応答に複数のaudioタグを含めることができますが、次の制限に従う必要があります。

  • 1回の応答の中で使用できる音声ファイルは5個までです。
  • 1回の応答に含めることのできるすべての音声ファイルの合計再生時間は240秒までです。

音声ファイルの使用には、数と時間の制限があるということですね。この「1回の応答」というのはどういうことでしょうか?猫の豆知識スキルのサンプルで説明します。

f:id:kun432:20190817174726p:plain

Alexaからの発話(音声や音楽含めて)が「1回の応答」になるということですね。これをVoiceflowに置き換えてみます。

f:id:kun432:20190817181352p:plain

Speak Blockの中で音声ファイルを使っていて、その後、Interaction Blockでユーザ発話を受け取る感じですね。Speak Blockが3つに別れていますが、Interaction Blockでユーザ側の発話になるまでは、Speak Blockを何個並べても、Alexaの発話としては1回の応答になるという点に注意してください。

なので、これもブロックの数は違うけど、中身的には同じになります。

f:id:kun432:20190817182018p:plain

Speak Blockの数がアレクサからの応答の数、ではないということ、と、1回の応答の中で使える音楽ファイルの数や再生時間の総合計には制限があるということをご理解いただければと思います。

Voiceflowでブロック並べていると、こういったところをあまり意識することはなく作れるので、それはそれで簡単なんですが、このやりとりの概念はとても重要です。他のブロックを使う場合もこの概念を理解していないと「なんで動かないの?」になることがあるかもしれません。そのあたりはまた別の機会で説明します。

まとめ

ということで、Voiceflowでの音声・音楽データの再生について、まずはSpeak Blockを使ったやり方について、ご紹介しました。注意点も書いてますが、そんなに気にせず、音楽をうまく使うだけで、スキルにかんたんに楽しい雰囲気を演出できるので、どんどん活用しましょう!

次回は、Stream Blockを使った音声・音楽データの再生についてご紹介します。Stream Blockを使うと、Alexaスキルで人気のカテゴリーである、環境音スキルがかんたんに作れますので、よければぜひ次回も御覧ください!

Voiceflow TIPS #15 Integration Block でカスタムAPI連携

1日遅れてしまいましたが、Voiceflow夏休みAdvent Calendarの15日目です。

Googleスプレッドシート連携は一休みして、今日はIntegration Blockを使って、他の外部のAPIとの連携をやってみたいと思います。

VoiceflowのIntegration Blockでできるのは以下の3種類です。

  1. Googleスプレッドシート連携
  2. Zapier連携
  3. カスタムAPI連携

中身的にはすべて、外部のAPIとの連携なのですが、1と2は一般的な使い方に絞って、VoiceflowのGUIの中で設定を可能にしたものとなってます。なので、Googleスプレッドシート・Zapier以外の、他のAPIと連携したい場合は「カスタムAPI」を使う必要があります。カスタムAPI連携を使うには、APIに関する基本的な知識や、接続したいAPIサービスごとの仕様などを確認したりする必要がありますが、使いこなせると非常に多くのサービスと連携ができるようになるので、ぜひ使ってみてください。

ではまずスキルの実行イメージから。


アレクサ、「郵便番号検索」をひらいて

郵便番号検索スキルです。7桁の郵便番号を言ってください。

6500042

郵便番号「6500042」ですね。お調べいたしますのでお待ち下さい。

(♪音楽)

郵便番号「6500042」は「兵庫県神戸市中央区波止場」です。

ご利用ありがとうございました。(♪音楽)


郵便番号をいうと住所を教えてくれるという非常にシンプルなものですね。こういうAPIはたくさんありますが、今回は事前登録等なしで使える、以下のAPIを使ってみようと思います。

なお、APIの利用については、API提供元の利用規約を必ず確認、同意の上、利用するようにしてください。

スキルの全体図

f:id:kun432:20190816011555p:plain

とてもシンプルですね。大まかな流れとしては、

  1. 郵便番号をユーザの発話から取得する
  2. 取得した郵便番号を元に、APIにリクエストする
  3. APIの実行結果が返ってくるので、それを整形して発話する。

という感じなので、ブロックの役割としてはこうなります。

f:id:kun432:20190816012114p:plain

それぞれ見ていきましょう。

ユーザの発話から郵便番号を取得する。

ここはそんなに難しくないですね。Capture Blockを使って、ユーザから郵便番号を取得し、変数に入れます。

f:id:kun432:20190816020821p:plain

f:id:kun432:20190816020702p:plain

f:id:kun432:20190816020713p:plain

f:id:kun432:20190817000851p:plain

ちょっとクセがあるのは、郵便番号のような数字だけど数字じゃない数値の扱いです。例えば、北海道札幌市北区の郵便番号は「0010000」なんですが(0で始まる郵便番号があることを初めて知りました・・・)、これをアレクサにそのまま読ませるとこうなります。

いちまん

何も指定しないと、純粋な数値として判断するので、最初のゼロがカットされちゃうのですね。こういう場合は、SSMLで読み方を指定してあげます。

<say-as interpret-as="digits">0010000</say-as>

読み上げはこうなります。

れい れい いち れい れい れい れい

「桁(digits)」と解釈して読む、ということですね。最初のより悪くはないですけど、実際に喋らせてみると、数字と数字の間は短いし、できれば「れい」じゃなくて「ぜろ」って読み上げてほしいですよね。

なので、ちょっと本来の使い方ではないのですが、こういうのもあります。

<say-as interpret-as="telephone">0010000</say-as>

書いてあるとおり、「電話番号」ですね。7桁と10桁のみなのですが、うまく読み上げてくれます。

ぜろ、ぜろ、いち、ぜろ、ぜろ、ぜろ、ぜろ

ちなみに、4桁とかでもちゃんと「ぜろ」と読んでくれますけど、数字と数字の間は短めです。

今のは発話する場合なんですが、受け取る場合も考慮が必要です。数字を受け取るスロットタイプには、AMAZON.Number、か、AMAZON.FourDigitNumberを使うのが良さそうですが、以下のクラメソさんのサイト見ると、AMAZON.FourDigitNumberを使うのが良さそうですね。FourDigitといいつつ、4桁以外も取れるようですし。

APIの実行

では今日の本題ですね。受け取った郵便番号をAPIに投げます。Integration Blockを使うのですが、まず最初にAPIの説明を見てみましょう。

郵便番号検索API - zipcloudを見るとこう書いてあります。

f:id:kun432:20190816214226p:plain

リクエストURL

ベースとなるURLは以下になります。

http://zipcloud.ibsnet.co.jp/api/search

これがAPI送信先になります。ただし、「何」について問い合わせるかはどこにも書いてませんね。そこで、

このURLにリクエストパラメータを加え、リクエストを行います。

リクエストパラメータと呼ばれる可変な値を付与します。今回の例だと「郵便番号」がこれにあたります。ではこのパラメータにはどんなものがあるのか?どういうふうに指定するか?というところが以下となっています。

パラメータ名 項目名 必須 備考
zipcode 郵便番号 7桁の数字。ハイフン付きでも可。完全一致検索。

3つほどのパラメータがありますが、必須なのは”zipcode”だけですね。これだけをパラメータ指定すればよいわけです。で実際に送る場合の例です。

(例)郵便番号「7830060」で検索する場合 http://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060

リクエストURLに”?”をくっつけて、”パラメータ名=パラメータの値”という風にリクエストパラメータを指定して送ればよいわけですね。で、こういうふうにURLにパラメータを指定して送るやり方を「GET」といいます。もう一つ、主要な贈り方としては「POST」というのもあります。「GET」か「POST」かは、APIの利用時には必要な情報で、これによってパラメータの指定の仕方が変わってきたりします。

APIを利用する場合は、こういったAPI側の仕様を事前に確認して、それに従ってリクエストをしないと結果が得られない、ということを覚えておいていただければよいかと思います。

では、必要な情報は揃いましたので、実際に設定していきましょう。

Integration Blockを置いて、右のメニューから「Custom API」をクリックします。

f:id:kun432:20190816220958p:plain

Custom APIのメニューが表示されますので、順次設定します。まず、最初にAPIへのリクエスト方法を選択します。今回は「GET」でしたよね?ということで、"Make a GET Request"をクリックします。

f:id:kun432:20190816221239p:plain

次にGETリクエストの設定をしていきます。URLのところが「リクエストURL」の入力になりますので、先程のURL「http://zipcloud.ibsnet.co.jp/api/search」を入力します。そして、「リクエストパラメータ」の設定を行いますので、”Param”をクリックします。

f:id:kun432:20190816221516p:plain

思い出してほしいのですが、パラメータは”パラメータ名=パラメータの値”でしたよね?このように、パラメータは名前と値の「ペア」になりますので、このペアを追加するわけですね。ということで、"+Add pair"をクリックします。

f:id:kun432:20190816222009p:plain

するとkeyとvalueという入力項目が出てくると思います。パラメータ名とパラメータ値のペアは、"key & value"とも言われます。したがって、ここにそれぞれを入力すればよいわけですね。keyにパラメータ名"zipcode"、valueに変数"{zipcode}"を指定します。変数指定の場合は最初に"{"を入力して、リストに表示されたものを選択することに注意してください。設定したら”Next”をクリックします。

f:id:kun432:20190816223129p:plain

次に、レスポンスと変数の紐付けです。TIPS #12 の Googleスプレッドシート連携の「検索」の時にも説明しましたが、リクエストの「結果」として受け取ったデータ(レスポンス)を変数に入れることで、以降の処理でそれを使えるようになりますので、ここで紐付けの設定が必要です。とりあえず、"Add Output Mapping"をクリックしてみましょう。

f:id:kun432:20190816224007p:plain

先ほどと同じように、入力する箇所が2つあります。右はVariableと書いてあり、レスポンスを保存しておくための変数をリストから選択して、ということはなんとなくわかっていただけると思います。左のObject Pathとはなんでしょうか?

これを理解するためには、実際にどういうレスポンスが返ってくるかを見てもらったほうが早いと思います。

まず、とりあえずレスポンスを受け取るための変数を作りましょう。変数のメニューから、今回は"res"というフロー変数を作成しました。

f:id:kun432:20190816230012p:plain

変数を作成したら、Integration Blockをクリックして、Mapping Outputをクリック、先程の設定画面を表示し、以下のように、Object Pathに"response"を入力、Variableは"{res}"を選択して、Nextをクリックしてください。

f:id:kun432:20190816230659p:plain

では、"Testing Integration" をクリックしてみてください。

f:id:kun432:20190816230900p:plain

画面中央にテスト実行を行うためのメニューが表示されます。zipcodeに適当な郵便番号を入力して、"RUN"をクリックしてみてください。

f:id:kun432:20190816231228p:plain

すると右下に結果が表示されます。

f:id:kun432:20190816231426p:plain

この中で結果として使いたいのは、真ん中にある"address"の1~3あたりですよね?これを取るにはどうすればよいか?試しにaddress1にカーソルを合わせてみてください。青いクリップボードのアイコン(小さいですけど)が表示されるのがわかりますか?これが表示されたらクリックしてみてください。

f:id:kun432:20190816232543p:plain

そして、再度、Mapping Outputのところをクリックして、"Add Output Mapping"をクリック、Object Pathのところでペーストしてみてください。すると、値が入るのがわかりますでしょうか?

f:id:kun432:20190816233245p:plain

ちょっと見えにくいので、以下に貼っときます。

response.results.0.address1

これ、よく見てみると、さっきのデータの先頭部分がだいたい順番に並んでる感じなのがわかるでしょうか?

f:id:kun432:20190816233515p:plain

一般的なAPIのレスポンスは、”JSON"というフォーマットで返ってくることが多いのですが、JSONというのは、データを「構造化」したものになっています。上記のデータでいうと、「responseという箱の中にresultsという箱があって、resultsは複数の箱に分かれていて、その中の0番目の箱にはさらにaddressという箱があり、中に"兵庫県"という値が入っている」という感じですね。したがって、特定の値を取り出す場合には、上記のような箱の名前を順番に"."(ドット)でつなぎ合わせることで、必要な値の「住所」というか「場所」を指定してあげるという形になります。

JSONについては、たくさん説明しているサイトが有るのですが、以下のサイトあたりがわかりやすいのではないかなと思います。

reffect.co.jp

これで、特定の値の取り出し方がわかったと思うので、address1〜3についても同じように設定してください。そして、それぞれを入れておくための変数も作る必要がありますね。

f:id:kun432:20190816234940p:plain

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

f:id:kun432:20190816235003p:plain

見にくいですね、、、こういう場合には、Integration Blockの右上にある、歯車アイコンをクリックして、Expandをクリックしてみてください。

f:id:kun432:20190816235653p:plain

画面中央にIntegration Blockと同じメニューが表示されるので、Mapping Outputをクリックしてみてください。

f:id:kun432:20190816235818p:plain

おお!見やすいですね!このように右のメニューで見にくい場合は、Expandを使うことで広く見れます。Expandメニューは他のブロックでも使えますので活用してください。

f:id:kun432:20190816235933p:plain

ただし、残念ながら、このExpandメニューは現在バグが有り、"+ Add Pair" とか "+ Add Output"などのボタンが効かない状態になってます・・・そのうち治ると思いますが、現時点では参照するだけにしか使えませんのでご注意下さい。

あと、最初に作った"response"と"res" のペアはもう不要なので削除しておいて下さい。

これでカスタムAPIの設定は終了です。

APIの結果データを使った発話とエラー時の発話

郵便番号をAPIにリクエストして、レスポンスとして住所を取得しましたので、あとは発話させるだけです。

f:id:kun432:20190817000641p:plain

あと、忘れてはいけないのは、API実行は失敗する場合もあるということです。その場合も踏まえて、エラーメッセージを発話するブロックを追加しておくのも大事ですね。

f:id:kun432:20190817000400p:plain

テスト

ではテストです。

f:id:kun432:20190817003852p:plain

うまく行ってますね。もう一つ。

f:id:kun432:20190817003912p:plain

あれ、だめですねぇ、、、確認してみたんですが、Alexa からはきちんと "0010000" で渡っているので、Voiceflow側で数値に変換しちゃってるみたいですね・・・ということで、少し修正。Code Blockで少しいじります。

f:id:kun432:20190817004746p:plain

こういうコードを差し込んであります。

zipcode = '0'.repeat(7 - (zipcode + '').length) + zipcode + '';

zipcodeが数値で渡されてくるので、文字列化して桁数をチェック。郵便番号は7桁なので足りない分だけ頭に"0"を文字列としてくっつけるという感じですね。さぁどうでしょうか?

f:id:kun432:20190817005025p:plain

一応ちゃんと渡ってますね。・・・が、最後がおかしいな・・・・Integration Blockでテストしてみます。

f:id:kun432:20190817005146p:plain

あー、なるほど、この郵便番号に紐付いた住所にはaddress3がないのですね・・・さらに修正。やっつけ感満載ですねw

f:id:kun432:20190817010401p:plain

f:id:kun432:20190817010427p:plain

f:id:kun432:20190817010412p:plain

addr_allという新しい変数を用意して、addr1〜3についてそれぞれなにか値が入っていればそこにどんどん連結していく、つまり、上のaddr3のように何も入ってない場合はスルーするって感じですね。最後に話すところも先程まではaddr1〜3を直接話させていましたが、連結させたaddr_allを話すようにしました。さぁ、これでどうだ?

f:id:kun432:20190817010617p:plain

はい、うまくいきましたねー、汗

今回の結論としては、数値のチェックやテストはしっかりやりましょう、ということで(違


いかがだったでしょうか?最後バタバタしちゃいましたが、APIとは関係ないところなので、そこは置いといて・・・

APIの設定は、連携したいAPI

  • リクエストの送り方
  • レスポンスの受け取り方

を、APIのドキュメントなどで確認した上で、指定されたとおりに(Voiceflow上で)設定するということが必要になります。最初のうちは難しいかもしれませんが、簡単そうなものから始めて、徐々に複雑なものに挑戦する、という形でいろいろやっていただければ、設定の勘所みたいなものがわかるようになると思いますので、トライアンドエラーで頑張ってみてください!

あと、最後に注意です。

  • APIの利用は、API提供元の利用規約を必ず確認、同意の上、利用するようにしてください。
  • API実行に失敗する場合もあります。きちんとエラー時の処理を忘れないようにご注意ください。
  • 場合によっては、スキル利用者にもAPI利用規約に同意して頂く必要があります。その場合には、スキル申請時に、利用規約やプライバシーポリシー等の準備も必要になる場合がありますのでご注意ください。

APIをうまく活用して、スキル開発の幅を広げていただければ幸いです。

Voiceflow TIPS #14 Googleスプレッドシート連携で作るゼロカロリースキルもどき 〜スプレッドシートへの登録〜

Voiceflow夏休みAdvent Calendarの14日目です。

Googleスプレッドシート連携シリーズ第3回です。前回、検索機能まで実装した「ゼロカロリースキル」に「登録」機能を追加していきたいと思います。

前回のあらすじはこちら。

ではまずスキルの実行イメージから。


アレクサ、「ゼロカロリースキル」をひらいて

ゼロカロリースキルです。カロリーを知りたい食べ物の名前を言ってください

カツカレーを登録

カツカレーを登録します。理由をお聞きます。最初に、理由、最後に、から、をつけていってください。例えば、理由、ほにゃほにゃだから、という感じで言ってください。

理由、カツとカレーのカロリーが相殺されるから

カツカレーは、カツとカレーのカロリーが相殺されるから、カロリーゼロ、で登録します。よろしいですか?

はい

カツカレーを登録しました。カロリーを知りたい食べ物の名前を言ってください・・・


オリジナルの方では、LINE BOTで実装されていましたが、今回はすべて音声でやってみたいと思います。果たしてうまくいくでしょうか?例によって、エラー処理は最低限の処理のみとします。

スキルの全体図

下のほうが今回新たに追加したブロックです。このブロックで登録処理を実装します。

f:id:kun432:20190814222313p:plain

前回作成したブロックのうち、Interaction Blockだけは、登録を受け付けるためのインテントを追加する必要がありますので、修正します。

では順に見ていきましょう。

Interaction Blockに登録用インテントを追加

前回作成したときには、検索用のインテントだけを追加しました。

f:id:kun432:20190814224220p:plain

f:id:kun432:20190814224228p:plain

f:id:kun432:20190814224240p:plain

今回は、同様にして、登録用のインテントを作成します。なお、手前のSpeak Blockのところで登録を促すメッセージを入れてもよいのですが、今回、登録機能はちょっと隠し機能的な意味合いとして、割愛します。Interaction Blockをクリックして、順に設定します。

まずSlotタブ。ここは変更の必要はありません。「○○○を登録」と発話した場合、○○○に入るのは食べ物名、すなわち、前回検索で登録したスロット "slot_recipe" がそのまま使えます。

f:id:kun432:20190814225545p:plain

次にIntentsタブです。検索用のインテントが見えていますね。ここに登録用インテントのサンプル発話とスロットを設定していきます。"Add Intent"をクリックします。

f:id:kun432:20190814225627p:plain

新しいインテント名を"add_intent"として、以下のようにサンプル発話を入力します。スロットの部分は、"["を入力してから表示されるスロット名を選択することに注意してください。

f:id:kun432:20190814225755p:plain

最後にChoicesタブです。インテントに合致したサンプル発話を受けてフローを分岐させます。"Add Choice"をクリックします。

f:id:kun432:20190814225907p:plain

Interaction Blockに新たな分岐(「2」)が追加されましたね。これで新しいインテントへのフローが作成されました。設定のほうにも新しい入力が追加されているので、先程作成した"add_intent"をドロップダウンから選択して、スロット"[slot_recipe]" と 変数 "{varRecipe}"を選択してください。

f:id:kun432:20190814225918p:plain

これでInteraction Blockの変更は完了です。

登録フローへのブロック追加

新しく追加された登録用の分岐にブロックを追加していきます。

1. Speak Blockを追加

登録の仕方を促すSpeak Blockを以下の通り追加して、先程のInteraction Blockの新しい分岐とつなぎます。

f:id:kun432:20190814231040p:plain

2. Interaction Blockで理由を受け取るインテントを追加

次にInteraction Blockを追加して、理由を受け取るためのインテントを設定します。このインテント、最初の例で「理由 ほにゃほにゃだから」みたいな形にしたのは理由があります。

通常、インテントの中に含めるスロットは、予め用意されたタイプから選択する必要があります。例えば最初の食べ物名で使用した「AMAZON.Food」とかがそうですね。これ以外にユーザがスロット候補を用意する「カスタムスロットタイプ」もあります。今回の「理由」のような「比較的自由な発話」を取る場合を想定した「AMAZON.SearchQuery」というスロットタイプも用意されていますのでこれを使うのですが、このスロットタイプは他のスロットタイプと違い、スロットだけでの発話を取ることができません。必ず、スロットの前か後に、何らかの表現を含める必要があります。

可否 サンプル発話例
[SearchQueryスロット]
[SearchQueryスロット] について検索して
キーワード [SearchQueryスロット]
キーワード [SearchQueryスロット] について検索して

なので今回は、最初に「理由」と言ってから、実際の理由を話すような感じでサンプル発話を設定していきます。

利用可能なスロットタイプについては以下も参照してみてください。

ではインテントを作りましょう。まずSlotタブ。「理由」をスロットとして受け取りますので、今回はスロットの登録が必要です。

と、おや、、、、前に登録した「slot_recipe」が見えていますね・・・?

f:id:kun432:20190814234452p:plain

これ、問題ではないので安心してください。Interaction Blockでは、スロットやサンプル発話、インテントなど、一度登録したものは他のInteraction Blcokでも共有されます。ただし、共有されているといっても、設定を変更しなければ影響はありませんし、あとで出てくるChoiceタブで対象のインテントを選択しない限りは呼び出されることはありません。

インテントは会話フローの特定の場面で1回だけで出てくるわけではありません。会話の流れによっては別の場所で同じようなインテントを使うこともありますよね。そういった場合に1回登録したものを再利用できる、というふうに考えてもらえればよいかと思います。

話が脱線しましたが、スロットを登録しましょう。"Add Slot”をクリックして、以下のように理由を受け取るための新しいスロット、"slot_reason"を設定します。ここで先程のSearchQueryタイプを選択します。

f:id:kun432:20190814235332p:plain

次にIntentsタブです。ここも以前登録した「検索用インテント(search_intent)」と、先程登録した「登録用インテント(add_intent)」が見えていますね。ここに理由を受け取るインテントのサンプル発話とスロットを設定していきます。"Add Intent"をクリックして、以下のように新しいインテント、"capture_reason"を設定します。

f:id:kun432:20190814235647p:plain

最後にChoicesタブです。先ほどとは異なり、ここにはこれまで登録したインテントへのフローが見えないですよね。"Add Choice"をクリックして、ドロップダウンを開いてみてください。

f:id:kun432:20190814235930p:plain

はい、先程のインテントが見えましたね。このようにここで呼び出すことによって初めてそのインテントが呼び出されるというわけです。なので、前に登録したものが見えても、ここで指定しない限りは使われないということがわかっていただけるかと思います。では気を取り直して、先程追加した"capture_reason"インテントを選択、そして、スロットと変数のマッピングです。スロットは"[slot_reason]"、変数は前回登録している"{varReason}"を選択します。

f:id:kun432:20190815000535p:plain

はい、これで理由が変数で取れるようになりました。

3. 食べ物名と理由の確認

受け取った理由で登録してもよいかを確認するために、Speak BlockとInteraction Blockを追加して、以下のように設定します。

f:id:kun432:20190815001110p:plain

f:id:kun432:20190815001123p:plain

「はい」の場合は「登録」すなわちGoogleスプレッドシートへのレコード追加に進むので後で説明するとして、先に「いいえ」のフローを作ります。「いいえ」の場合は、最初のカロリー検索のところまで戻してしまうのがよいでしょう。

f:id:kun432:20190815001812p:plain

4. Googleスプレッドシートへの登録

さあ、いよいよスプレッドシートへの登録をやります。Integration Blockを配置して、順に設定していきましょう。Google Sheetsを選択します。

f:id:kun432:20190815002845p:plain

登録なので"Create Data"を選択します。

f:id:kun432:20190815004426p:plain

スプレッドシート連携に使うアカウントを選択します。前回の検索時にアカウント連携は終わっているので、今回は選択するだけです。

f:id:kun432:20190815004441p:plain

連携するスプレッドシート名およびシート名を設定します。前回と同様に、スプレッドシート名「ゼロカロリースキル」、シート名「recipes」を選択します。

f:id:kun432:20190815013821p:plain

スプレッドシートの各カラムごとに登録する内容を設定します。recipeNameカラムにレシピ名、すなわち"varRecipe"、reasonカラムに理由、すなわち"varReason"を設定します。入力が終わったら"Next"をクリックします。

f:id:kun432:20190815013840p:plain

これで設定完了です。実際のテストの前にここで登録のテストをやってみましょう。"Test Integration"をクリックします。

f:id:kun432:20190815004719p:plain

テスト用のフォームが表示されます。変数部分が入力項目になりますので、食べ物名と理由を適当に入力して”Run”をクリックしてみてください

f:id:kun432:20190815005003p:plain

登録処理が終わると、Test Integraionに結果が表示されます。"Created row in Sheet ・・・"と表示されれば、登録が成功しています。

f:id:kun432:20190815005131p:plain

実際のGoogle スプレッドシートの方も見てみましょう。

f:id:kun432:20190815010907p:plain

おー、ちゃんと登録されていますね!これでGoogleスプレッドシートとの連携は完了です。

5. 成功時と失敗時のメッセージ

テストでは一応登録が成功していましたが、場合によっては失敗する可能性もありますし、成功した場合もその旨を伝える必要があります。失敗した場合はエラーを伝えてスキルを終了し、成功した場合は登録完了を伝えて、最初のカロリー検索のところまで戻してしまえば、登録した内容をすぐに確認できますね。

f:id:kun432:20190815013051p:plain

f:id:kun432:20190815013108p:plain

はい、これで完了です。お疲れさまでした!

テスト

では、テストしてみましょう。

f:id:kun432:20190815010255p:plain

ちゃんと登録ができて、それを検索で呼び出せていることがわかりましたね!もちろんGoogleスプレッドシートへの登録もちゃんとできています!

f:id:kun432:20190815010455p:plain


検索と登録ができると一気にスキルらしくなってきましたね!ただし、今の状態だと、同じ食べ物名があっても登録が可能で、かつ、最初に登録したものしか読み出されません。

ということで、次回は登録しようとした食べ物がすでに登録済みの場合を踏まえて「スプレッドシートの更新」をやってみたいと思います。またご覧いただければ幸いです。

Voiceflow TIPS #13 セッションを使ってユーザの利用回数・頻度に応じた回答を返す

Voiceflow夏休みAdvent Calendarの13日目です。

Googleスプレッドシート連携シリーズは明日に回すとして、今回はセッションを使った利用回数・頻度の話です。イメージで言うとこんな感じです。

初めての利用

アレクサ、「○○○スキル」をひらいて

はじめまして、○○○スキルです。このスキルを選んでいただきありがとうございます。・・・

2回め以降の利用

アレクサ、「○○○スキル」をひらいて

こんにちは、○○○スキルです。また呼んでくれてありがとう。最近の調子はどうかな?・・・

久しぶりの利用

アレクサ、「○○○スキル」をひらいて

お久しぶりです、○○○スキルです。ずっと呼んでくれなかったので、寂しかったです。・・・


どうですか?なんとなく親しみが持てませんか?流石に人間と同じ、というわけには行きませんが、こういった配慮や挨拶の仕方などにランダム性があると、ちょっと人間っぽさが出るし嬉しいですよね。今回はそういうのを説明します。


以前のTIPS #2, #3 でご紹介したとおり、Voiceflowには2つの変数があります。

  • プロジェクト変数
    • スコープはグローバル
    • 永続
  • フロー変数
    • スコープは対象のフロー内
    • 非永続

上記のような一定期間内の回数や頻度に応じて応答を変えるには、永続的なプロジェクト変数が必要になります。

Voiceflowではデフォルトで以下のプロジェクト変数が用意されています。

変数名 内容
sessions セッション回数。初回利用時は必ず1、スキル「起動」するたびに自動でインクリメントされる。 1
user_id AlexaのユーザID amzn1.ask.account.XXX・・・
timestamp スキル「実行」時の時刻、UNIX TIME表記 1565624078
platform スキル動作環境のプラットフォーム。現状alexaかgoogleらしい alexa
locale ロケール ja-JP
access_token アカウントリンキングで使用

これらはすべて永続になっているので、user_idをキーに、sessionsとtimestampをうまく使えばできそうですね。ちょっとやってみましょう。

スキルの全体図

結構ベタに作りました。メッセージなどは、うまく変数やGoogleスプレッドシートに外出しして分岐で処理すればもっときれいに作れると思います。まあ、あくまでも考え方のお話なので、ヨシとします。

f:id:kun432:20190813020944p:plain

ポイントだけ説明します。

起動回数に応じた分岐

sessionsにはデフォルトで起動回数が保存されます。なので、ここでIf Blockを使って分岐させます。この例では、1回目、10回目まで、それ以降、という3パターンに分岐させてますが、初回起動時か2回目以降か、みたいなよくあるチェックはこういう感じになります。

f:id:kun432:20190814012852p:plain

1回目

f:id:kun432:20190814013224p:plain

10回目まで

f:id:kun432:20190814013235p:plain

それ以降

f:id:kun432:20190814013249p:plain

前回起動時間からの差異

timestampには実行時の時間がUNIXTIME(1970/1/1からの秒数)で入っています。なので、前回実行時のtimestampをプロジェクト変数に保存しておいて、次の実行時のtimestampと比較すれば、何秒経過しているか?がわかるということですね。

まず、変数を用意します。

f:id:kun432:20190814014234p:plain

lastTimestampというのが前回実行時の時間を保存しておくための変数です。ここはセッションをまたぐのでプロジェクト変数にしておく必要があります。

curTimestampというのが起動時の時刻を保存しておくためのフロー変数です。別にtimestampをそのまま使えばいいのですが、timestampは呼び出したタイミングで時間が変わっていくので、ここでは「起動直後」の時間を保持しておくためにあえて用意してます。

あとは、この時間差異を入れるためのtimediffというフロー変数です。

フローに沿って見てみます。

まず起動直後に、timestampを取得して、curTimestampに保存しておきます。

f:id:kun432:20190814014656p:plain

そして最後にcurTimestampをlastTimestampに保存します。これで次回の起動時に前回起動した時間が取れるようになります。

f:id:kun432:20190814014931p:plain

スキルの中ではこんな感じです。

f:id:kun432:20190814015337p:plain

まず上で説明したとおり、起動回数で3分岐してますが、1回目はもちろん前回の実行時間を持っていないので、何もする必要はありません。2回目以降だけが対象になります。

次にCode Blockの中で前回実行時と今回起動時の差異を求めてます。Code Blockは2つありますが、どちらも同じです。ちょっと見にくいので、コード貼っときます。

timediff = Math.floor((curTimestamp - lastTimestamp) / 86400);

現在の時間(秒)から前回の時間(秒)を引いて、それを1日(86400秒)で割り、Math.floorで小数点以下は切り捨てます。これで「何日」経ったか?を算出します。

そして、If blockでそれをチェックして、ここでは「11日以降」だったら、という分岐にしてます。

f:id:kun432:20190814015903p:plain

で、その結果に応じて、メッセージを変えます。

f:id:kun432:20190814020254p:plain

f:id:kun432:20190814020307p:plain

f:id:kun432:20190814020319p:plain

f:id:kun432:20190814020348p:plain

はい、これで完了です。

テスト

実際に数日かけて試すわけにも行かないのでCode Blockを少し変えて秒で判定してます。なので前回実行時から10秒立ってないとスネるという、どんなデレやねんという感じになってますw

初回

初回はこんな感じで。結構よそよそしいですよね。

f:id:kun432:20190813021641p:plain

2回目以降

2回目以降は、少し丁寧に。でもまだまだよそよそしいです。しばらくスキルを使っていない場合は、下のメッセージになります。なんかこう真面目なキャラクターですね。(誤字は無視してください・・・)

f:id:kun432:20190813021657p:plain

10回目以降

10回目以降は、一気にフランクになります。しばらくスキルを使っていないと、こんな感じでちょっとスネたりします。

f:id:kun432:20190813021711p:plain

どうでしょうか?少し人間らしさが出るようにおもいませんか?


ユーザ側としても覚えてもらえるのは嬉しいし、回数に応じて親密な感じが出てくると、機械感がなくなってほんのちょっぴり人間ぽさが出ますよね。逆に、少しキャラクター性みたいなものも出てくるので、どういう性格で、どういう立ち位置なのか?みたいなのは、スキルの用途や想定している利用者にあわせて、きちんと事前に考える必要が出てくるかもしれません。

キャラクター性まで行くとちょっとやりすぎかもしれませんが、永続セッションを使った利用回数・頻度での分岐は、VUIのユーザビリティ向上に欠かせない要素なので、ぜひ活用してみてください。

Voiceflow TIPS #12 Googleスプレッドシート連携で作るゼロカロリースキルもどき 〜スプレッドシートの検索〜

Voiceflow夏休みAdvent Calendarの12日目です。

7日目でやったGoogleスプレッドシート連携、第1回ということで、スプレッドシートに登録された豆知識をランダムに呼び出すスキルを作りました。今回から複数回にわけて一つのスキルを作るハンズオン形式でGoogleスプレッドシート連携の使い方をご紹介していきたいと思います。

サンプルとなるスキルは、某所でバズっていた「ゼロカロリースキル」へのリスペクトを込めて、「ゼロカロリースキルもどき」を作ってみたいと思います。ただし、あっちは技術のオンパレードで、こっちは技術をほとんど使わない、かなり低グレードなものになる見込みなので、多大な期待はご遠慮頂ますようお願い致します・・・。

今回はまず「検索」からやってみたいと思います。

スキルはのイメージはこんな感じです。


アレクサ、「ゼロカロリースキル」をひらいて

ゼロカロリースキルです。カロリーを知りたい食べ物の名前を言ってください。

ドーナツ

ドーナツですね。ドーナツは、形がゼロを表しているから、ゼロカロリーです。他にも聞きますか?


登録は後から追加することとして最初はいくつかのサンプルを入れてやってみたいと思います。

Googleスプレッドシートの準備

こんな感じのスプレッドシートを用意してください。

  • スプレッドシート名は「ゼロカロリースキル」とします。
  • 1行目はヘッダです。"recipeName"が「食べ物の名前」、"reason"が「理由」になります。
  • 2行目以降に食べ物の名前を理由を入力していきます。
  • シート名は"recipes"にします。ここは日本語だとうまく行かないのでご注意ください。

f:id:kun432:20190812180359p:plain

しかし見れば見るほどすごい理論だな、これw

Voiceflowのフロー

全体像はこんな感じです。今回最低限のエラー処理しかしてないのでご注意ください。

f:id:kun432:20190812192902p:plain

順に見ていきましょう。

変数の作成

ユーザからのスロット兼スプレッドシートからの結果を受ける変数を用意します。"varRecipe"が「食べ物名」で、"varReason"が「理由」です。ホントはもっと分けたほうがいいんでしょうが、そこまで複雑でもないのでこれでいいでしょう。

f:id:kun432:20190812202604p:plain

① スキル開始

開始のメッセージを発話させます。

f:id:kun432:20190812202711p:plain

② Interaction Blockで検索を受けるインテントの設定

食べ物名を受け付けるインテントを作ります。まず、スロットは"slot_recipe"という名称で、AMAZON.Foodを使います。

f:id:kun432:20190812203105p:plain

インテントで、サンプル発話を入力していきます。「○○○のレシピ」「○○○のレシピを教えて」「○○○」って感じで、○○○で食べ物名を取るsearch_intentを作ります。

f:id:kun432:20190812203114p:plain

最後にインテントの分岐です。先程作ったsearch_intentのフローを作ります。ちょっと見えにくいですが、ここで受け取ったスロット"slot_recipe"を変数"varRecipe"に入れます。

f:id:kun432:20190812203123p:plain

③④ Integration BlockでGoogleスプレッドシートの検索

いよいよ、検索です。Integration Blockを使って、Googleスプレッドシートから食べ物名を検索、その理由を取得します。Integration Blockの設定を見ていきます。

まず、データの検索というか取得になりますので"Retrive Data"を選択、Googleアカウントの連携がまだであれば、ここで"Add User"して、Googleログインしてしてください。

f:id:kun432:20190812203846p:plain

次に、スプレッドシートの設定です。スプレッドシートは「ゼロカロリースキル」、シートは「recipes」を選びます。

f:id:kun432:20190812204233p:plain

次に、検索条件です。ランダムに抽出する場合は特に意識しませんでしたが、キーを元に検索する場合は個々で設定が必要です。スプレッドシートの"recipeName"カラムから"varRecipe"の値が該当するもの、という形になります。

ちなみに、Googleスプレッドシートの場合、この条件は一つ、かつ、合致するものしか設定できません。すなわち、複数の条件だったり、比較(>、<、>=、<=)みたいな、複雑な条件は設定できないということです。なので、本当のデータベースのような柔軟な使い方は難しいですね。(そのような用途に適した別のサービスとして、airtable というものがありますが、それはまた別の機会で)

f:id:kun432:20190812204358p:plain

返ってきた結果を取得します。"recipeName"カラムと"varRecipe"が合致した行から、"reason"カラムの値を取って、"varRecipe"に入れます。ここに「理由」が入っているので次のブロック以降で発話させればよいわけです。

f:id:kun432:20190812205244p:plain

あと、Googleスプレッドシートへのアクセスが何らかの理由でできなかった場合は、Failから④に流れるようにして、エラーを伝えるようにしておけばよいでしょう。

f:id:kun432:20190812205114p:plain

⑤⑥⑦ If Blockで検索結果の判定

Integration Blockでユーザが発話した「食べ物名」に基づいた「理由」が取得できている想定ですが、ユーザの発話した食べ物名がスプレッドシート上になかった場合、recipeNameはundefined(定義されていない)を返します。ここは少しプログラミング的になってしまうのですが、undefinedは空でもゼロでもない特殊な値で、If Blockの標準的な比較ではチェックができません。そこでIf BlockのAdvanced Expressionという機能を使って、かんたんなプログラムを入力してチェックします。

最初のIf Blockの状態はこんな感じです。

f:id:kun432:20190812211012p:plain

普段だと、ここで変数名を設定して、値を入力することになりますが、Advanced Expressionの場合はこれをすべて入力で行います。まず、=の行にカーソルを合わせるとゴミ箱アイコンが表示されるのでこれをクリックします。

f:id:kun432:20190812210701j:plain

入力欄が消えて1行だけになりました。次に右端の「」のアイコンをクリックします。

f:id:kun432:20190812210803j:plain

するとメニューが表示されるので、一番下のExpressionを選択します。

f:id:kun432:20190812210817j:plain

表示が変わったのがわかりますか?先程までは"Variable Name"となっていたところが"Enter Your Expression Here"となっていますね。これでAdvanced Expressionモードに切り替わったということです。

f:id:kun432:20190812210829j:plain

で、最後に、プログラムを入力します。

f:id:kun432:20190812210842j:plain

コードもこちらに貼っときます。変数のところはそのままでは使えませんので、必ず"{"を入力して表示される変数名を選択するようにして下さい。

equal({varReason},undefined)

要は"varReason"がundefined、すなわち、結果が返ってこなければ、その食べ物名が登録されていない、ということになるので、再度食べ物名を発話させるためにInteraction Blockに戻ります。

f:id:kun432:20190812212025p:plain

逆にvarReasonがundefinedでない、すなわち、結果が返ってきたということであれば、その食べ物名ならびに理由が登録されている、ということで、それらを発話する流れになります。

f:id:kun432:20190812212144p:plain

これでGoogleスプレッドシートを使った検索の大枠の流れはほぼ完了です。

あと、少し補足しておくと、If BlockのAdvanced Expression、なんとなく、JavaScriptっぽい感じはするけどけど、うまく動かないし、何だろうなーと思っていましたが、どうやら以下らしいです。

ドキュメントの下の方にチョロっと書いてありました・・・

ということで、undefinedかどうかのチェックはこんな感じになるみたいです。

equal({varReason},undefined)

普通の数値とか文字列の比較だと特に問題ないんですが、少し複雑な書き方とはになるとうまく動かなかった理由がやっとわかりました。今後はIf BlockのAdvanced Expressionを安心して使えそうです。

⑧⑨⑩ 繰り返しの確認

最後の部分はこれまでにも何度か説明してますが、「はい」「いいえ」を確認して、「はい」なら繰り返しなのでInteraction blockに戻って食べ物名を取得、「いいえ」なら終了させるというやつですね。詳細は割愛しますので、画面キャプチャを見ていただければと思います。

f:id:kun432:20190812202232p:plain

f:id:kun432:20190812202244p:plain

f:id:kun432:20190812202254p:plain

テスト

ではテストしてみましょう。

f:id:kun432:20190812212635p:plain

食べ物名が登録されているものは理由が説明され、登録されていないものは再度食べ物名を確認していますね。

まとめ

ということで、「Googleスプレッドシート連携で作るゼロカロリースキルもどき」の第1回は「検索」について説明しました。Googleスプレッドシート連携では複数条件の指定した検索などができないため、データベースのような凝ったことはできませんが、非常にお手軽に使えるという雰囲気が伝われば幸いです。

間に別のテーマを挟むかもしれませんが、次回は「スプレッドシートへの登録」をご紹介します。

しかし、ほんとあの「ゼロカロリースキル」はすごいなー。@gaomarさん、半端ない!