GoogleHome向けのActionを作ってみた
はじめに
先日、Google Home Mini を購入したので、せっかくだし自作のアプリでも作ってみるかと思い、季節を答える簡単なActionを作成してリリースしてみた。
一応リリースできたけど、何回かリジェクトされたりしたので、どんな感じに作ったかや、自分の理解を深めるために雑にまとめてみる。
世界観
色々なサイトを見れば詳しい解説が出ているが、一応ザックリとまとめてみる。
スマートスピーカー
GoogleHomeやHome mini などのような賢いスピーカー。我々人間がやりとりを行う時の相手になるもの。
Google Assistant
Googleが開発したAIアシスタント。スマートスピーカーに住んでいる妖精で、内容に応じたAgentを呼び出してくれる。
Actions on Google
Actionを開発するためのプラットフォーム。
Action
Google Assistantで使えるサードパーティ製のアプリ的なもの。
Dialogflow
自然言語理解(NLU)エンジンを積んでいて、GUIから簡単に会話型のインターフェイス(独自Agent)を構築できる機能。
例えば、天気を尋ねる時などに、言葉の微妙なニュアンスの違いがあったとしても、Dialogflow はそれを吸収して構造化された形式に変換してくれる。 ユーザからの「今日」「明日」「1月1日」などという発話は 「年/月/日/時間」の構造を持っているので、Dialogflowが曖昧な発話を「年/月/日/時間」の構造に変換して渡してくれるような理解。 他の例としては、「今日から明日」「7月」などは、「期間(from - to)」の構造を持っているので「年/月/日/時間 - 年/月/日/時間」に変換するなどなど。
プロジェクトを作成する
Googleで「Actions Console」で検索すると、一番上に Actions Consoleが出てくると思うので、そこから Actions on google のコンソールに入る。
「New Project」 から新規プロジェクトを作成する。
どんなものを開発しますか?的なことを聞かれるので、今回は会話型(Conversational)を選択する。
プロジェクトの概要ページに移る。
UI はかなり親切な設計になっており、上から
- Quick Setup
- Actionの起動方法決める
- Build your Action
- Actionの作成とテスト
- Get ready for deployment
- リリースするに当たってActionの説明(Directory)などを設定する
- Release
- Release申請する
という流れになっており、リリースするまでにやるべき事がパッとわかるようになっているので、迷う事なく進められる。
Actionの名前を決める
「Quick setup」を選択して、 声とActionの名前を設定する(後からも変更可能)
Actionの名前は、よくある単語や一般的に使われそうな名前ではダメというポリシーがあるらしいので、命名時は少し気にするといいかも。自分は「にじゅうしせっき」って付けたらリジェクトされた。
ちなみに、今回は女性の名前で作成したのだが、Female 2
の方が喋り方が自然だったので、Female 1
ではなくFemale 2
をオススメする。
Female 1
は、よくある機械が喋るような発声なので、聞くとガッカリする。
入力が完了したら、右上に出ている「Save」を忘れないようにする。
Actionの作成
サイドメニューに表示されている Actions を選択するか、概要ページの「Add Action(s)」を選択する。
組み込みやテンプレートみたいなのものもあるが、今回はカスタムインテントを選択する。選択すると Dialogflow のページに遷移する。
言語を選択してAgentを作成する。
AgentとはDialogflow のプロジェクト。管理単位。
会話を構築する
用語の説明
会話を構築するにあたり、いくつか用語があるのでまとめとく。
Intent
Intent とは、ユーザからの発話に対する処理の定義的なもので、インテント毎にどのような発話に反応(Training phrases)するかを決めて、それに対してどのような応答(Response)をするかなどを定義できる。いくつか項目があるので、ざっとまとめる。
Context
コンテキストにはin
とout
がある。使い分けとしては、このインテントが実行されるときに、別のインテントで発生したパラメーターなどを受け継ぐ時は、in
にそのコンテキスト名を指定すると使えるようになる。out
は、このインテントで発生したパラメータを別のインテントに引き継ぎたい場合に利用する。
会話の文脈的な意味合いから Cotext という名前がついたのかな?と覚えた。
Events
イベントはユーザからの発話ではなく、何か出来事が起こった時にインテントを起動するためのもの。トリガー。
Training phrases
トレーニングフレーズは、ユーザからどんな「発話」を受け取った時にインテントが起動するかを決める。
また、フレーズの内容は構造化されたパラメーターとして受け取る事ができる。
例えば「6月6日は」と定義した場合、「年/月/日」の部分を「sys.date」パラメータに設定することができるため、そのパラメータを Responseや Fulfillment で利用する事ができる。
Action and parameters
上のフレーズから抽出できるパラメータが自動的に設定される。
※ 手動で設定しなければいけない場合もある
Responses
このインテントの応答。
複数の応答を定義すると、その中からランダムに返してくれる。
Fulfillment
これを有効にすると、このインテントが呼ばれた場合に webhook する事ができる。 ちなみに、webhook は Actionにつき一つしか設定できないので、インテントでは使用有無しか設定できない。
構築
今回はこんな感じの会話を想定して構築した。
ちなみに、会話の構築で注意することは、ユーザがどのような発話の候補を持っているか を明確に教えてあげないと Release 時にリジェクトされるので、インテント毎に次にどんな発話を望んでいるか(想定)を丁寧に示すのがいいと思う。
[Action Start] > こんにちわ、季節をお答えします。 XX月YY日は? > XX月YY日の季節は・・・ もう一回 > XX月YY日の季節は・・・ おわり > ご利用ありがとうございました。 [Action End]
作成したインテントとしては、
- 接続直後に機能の説明をする「Welcome インテント」
- 日付に対応する季節を答える「Main インテント」
- もう一回答える「One More インテント」
- Actionを終了する「Exit インテント」
Welcomeインテント
Action起動時に機能の説明をして欲しいので、Eventの設定とResponseのみを設定している。
Mainインテント
ユーザからの発話から日付を取得して、該当する季節を答えるため、Training phrases と Parameter と Filfullment を設定している。
Filfullmentでは、Inline Editorを使ってサーバー側の処理を書いた。
'use strict'; function solar_term(now) { const SOLAR_TERM = [ {month: 1, date: 5, name:"小寒", name_kana: "しょうかん", description: "池や川の氷も厚みを増し、寒さが厳しくなる頃です。この日を「寒の入り」といい、寒さの始まりを意味します。そして、小寒と大寒を合わせたおよそ1か月を「寒中」「寒の内」といい、寒中見舞いを出す時期とされています"}, {month: 1, date:20, name:"大寒", name_kana: "だいかん", description: "冷え込みもはげしく、寒さが最も厳しい頃。二十四節気の最後の節気で、ここを乗り切れば春近しということです。寒気を利用した食物(凍り豆腐、寒天、酒、味噌など)を仕込む時期にもあたります"}, {month: 2, date: 4, name:"立春", name_kana: "りっしゅん", description: "二十四節気の最初の節気で、この日から暦の上では春となり、さまざまな決まりごとや節目の基準になっています。旧暦では立春近くに正月がめぐってきたので、立春は春の始まりであり、1年の始まりでもありました。まだまだ寒さは厳しいですが、立春を過ぎてから初めて吹く強い南風を「春一番」といいます"}, {month: 2, date:19, name:"雨水", name_kana: "うすい", description: "雪から雨へと変わり、降り積もった雪も溶けだす頃という意味です。実際にはまだ雪深いところも多く、これから雪が降り出す地域もありますが、ちろちろと流れ出す雪溶け水に、春の足音を感じます"}, {month: 3, date: 6, name:"啓蟄", name_kana: "けいちつ", description: "大地が温まって、冬ごもりから目覚めた虫が、穴をひらいて顔を出す頃。「啓」はひらく、「蟄」は土の中にとじこもっていた虫(蛙や蛇)という意味です。ひと雨ごとに暖かくなり、日差しも春めいて、生き物が再び活動し始めます"}, {month: 3, date:21, name:"春分", name_kana: "しゅんぶん", description: "昼夜の長さがほぼ同じになる日で、この日を境に陽が延びていきます。春分の日は彼岸の中日で前後3日間を春彼岸といい、先祖のお墓参りをする習慣があります。「自然をたたえ、生物をいつくしむ」として国民の祝日になっています"}, {month: 4, date: 5, name:"清明", name_kana: "せいめい", description: "清明は「清浄明潔」の略で、万物がけがれなく清らかで生き生きしているという意味です。花が咲き、鳥は歌い、空は青く澄み、爽やかな風が吹き、すべてのものが春の息吹を謳歌する頃。各地でお花見シーズンを迎えます"}, {month: 4, date:20, name:"穀雨", name_kana: "こくう", description: "春の柔らかな雨に農作物がうるおうという意味です。この時期に農作物の種をまくと、雨に恵まれ、よく成長するといわれています"}, {month: 5, date: 6, name:"立夏", name_kana: "りっか", description: "この日から立秋の前日までが暦の上では夏となります。新緑に彩られ、さわやかな晴天が続く頃です。ちょうどゴールデンウィークの時期にあたり、レジャーに出かけるにもよい気候です"}, {month: 5, date:21, name:"小満", name_kana: "しょうまん", description: "陽気がよくなり草木が成長して茂るという意味です。農家では田植えの準備を始める頃。動物や植物にも活気があふれます。また、秋にまいた麦の穂が付くころで安心する(少し満足する)という意味もあります"}, {month: 6, date: 6, name:"芒種", name_kana: "ぼうしゅ", description: "「芒」とはイネ科植物の穂先にある毛のような部分のことで、稲などの穀物の種をまく時期という意味です。田植えの目安とされ、農家が忙しくなる時期。梅雨入りも間近で少し蒸し暑くなってくる頃です"}, {month: 6, date:21, name:"夏至", name_kana: "げし", description: "北半球では、太陽が最も高く昇り、1年で最も昼が長い日です。ただ、日本では梅雨のシーズンでもあるので、日照時間が短く、あまりひの長さを実感できないかもしれません。暦の上では夏の折り返し地点にあたり、夏至を過ぎると暑さが増して本格的な夏がやってきます"}, {month: 7, date: 7, name:"小暑", name_kana: "しょうしょ", description: "だんだん暑さが増していくという意味で、梅雨明けも近くなり、湿っぽさの中にも夏の熱気が感じられるようになります。海や山に出かけるのにもいい時期です。また、小暑と大暑を合わせたおよそ1か月を「暑中」といい、「暑中見舞い」を出す期間とされています"}, {month: 7, date:23, name:"大暑", name_kana: "たいしょ", description: "夏の暑さが本格的になるという意味ですが、子どもたちは夏休みに入ってわくわく。農家にとっては田の草取り、害虫駆除など暑い中での農作業が続く大変な時期です。また、土用の丑の日が近く、夏バテ防止にうなぎを食べたりする頃です"}, {month: 8, date: 7, name:"立秋", name_kana: "りっしゅう", description: "厳しい残暑は続きますが、この日から暦の上では秋となります。これからは少しずつ涼しくなり、秋の気配が漂いだす頃です。また、立秋を過ぎたら「暑中見舞い」は「残暑見舞い」に変わります"}, {month: 8, date:23, name:"処暑", name_kana: "しょしょ", description: "さがおさまるという意味で、日中は暑いものの、朝晩の涼しさに初秋の息遣いを感じる頃です。夏休みもそろそろ終わり。秋の台風シーズンに入っていきます"}, {month: 9, date: 8, name:"白露", name_kana: "はくろ", description: "秋が深まり、草花に朝露がつきはじめる頃という意味です。空は高くなり、秋雲がたなびくようになり、本格的な秋の到来です。また、実りの秋を前に台風が心配な時期でもあります"}, {month: 9, date:23, name:"秋分", name_kana: "しゅうぶん", description: "昼夜の長さがほぼ同じになる日で、この日を境に日が短くなり、秋の夜長に向かいます。秋分の日は彼岸の中日で前後3日間を秋彼岸といい、先祖のお墓参りをする習慣があります。「祖先を敬い、亡くなった人をしのぶ日」として国民の祝日になっています"}, {month:10, date: 8, name:"寒露", name_kana: "かんろ", description: "草木に冷たい露が降りる頃という意味です。秋の長雨が終わり、ぐっと秋が深まります。稲刈りが終わるころで、その他の農作物の収穫もたけなわとなります。また、北の方から紅葉の便りが届きはじめます"}, {month:10, date:23, name:"霜降", name_kana: "そうこう", description: "早朝に霜が降りはじめる頃という意味です。晩秋を迎え、北の方では朝霜が降り、山々は紅葉に染まります"}, {month:11, date: 7, name:"立冬", name_kana: "りっとう", description: "この日から立春の前日までが暦の上では冬となります。木枯らしが吹き、冬の訪れを感じる頃。太陽の光が弱まって日も短くなり、木立ちの冬枯れが目立つようになります。木枯らしが吹くのは、冬型の気圧配置になった証拠です"}, {month:11, date:22, name:"小雪", name_kana: "しょうせつ", description: "木々の葉が落ち、山には初雪が舞い始める頃です。「小雪」とは、冬とは言えまだ雪はさほど多くないという意味で、冬の入口にあたります"}, {month:12, date: 7, name:"大雪", name_kana: "たいせつ", description: "山の峰々は雪をかぶり、平地にも雪が降る頃です。本格的な冬の到来で、動物たちも冬ごもりを始めます。年末に向け、お正月の準備も始まって、何かとあわただしい時期でもあります"}, {month:12, date:22, name:"冬至", name_kana: "とうじ", description: "太陽が最も低い位置にあり、1年で最も夜が長く、昼が短い日です。太陽の力が一番弱まる日ですが、翌日からは再び強まるということから、運が向いてくるとされています。また、冬至かぼちゃ、冬至がゆ、柚子湯などで、厄払いや無病息災を願う風習があります"}, ]; var index = SOLAR_TERM.findIndex(season => { if (new Date(now.getFullYear(), season.month -1, season.date) > now) { return season; } }); return (index <= 0) ? SOLAR_TERM[SOLAR_TERM.length -1] : SOLAR_TERM[index - 1]; } function say(date, season) { var say_season = `${date.getMonth() + 1}月${date.getDate()}日は、${season.name_kana}の季節です。${season.description}。。`; var announcement = "別の日について知りたい場合は、希望する日付を指定してください。終わりにする場合は、「終わり」と言って下さい。"; return say_season + announcement; } const functions = require('firebase-functions'); const { dialogflow } = require('actions-on-google'); const app = dialogflow(); app.intent('One More', conv => { const season = conv.contexts.get("season"); if (typeof season === 'undefined') { conv.ask("希望する日付を指定してください。"); } else { conv.ask(season.parameters.say); } }); app.intent('Another Date', (conv, params) => { const date = new Date(params.date); const answer = say(date, solar_term(date)); conv.contexts.set("season", 10, {say: answer}); conv.ask(answer); }); exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
One Moreインテント
Mainインテントで設定されたseason
コンテキスト(conv.contexts.set("season", 10, {say: answer});
)をもらい、同じ内容で応答するために、 Training phrases と Parameter と Response を設定している。
Exitインテント
Actionを終了するために、Training phrases と Response と「set this intent as end of conversation」 を設定している。
リリースするにあたってディレクトリを設定する
概要の「Enter information required for listing your Action in the Actions directory」を選択する
内容はこのくらいあるが、注意すべきところだけここでは取り上げる
Description
Actionが何をできるかを簡潔に書く。曖昧だとリジェクトされる。
Sample Invocations
Actionを起動するときのフレーズを設定する。最初に設定したAction名からかけ離れているとリジェクトされる。
ちなみに、同じフレーズでも「ひらがな」と「漢字」版など、想定されそうなフレーズは複数設定しといたほうがいい。自分は最初、ひらがなだけのフレーズを設定していたが全然Actionが起動されず、スマホの Google Assistant で試してみたら、発話の一部分が漢字に変換されていてフレーズに一致しない事象が起きていた。
Images
好きなアイコンと背景画像をどうぞ。
Contact details
連絡先情報と開発者名(任意)をどうぞ。
Privacy and concent
プライバシーポリシーを明記したリンクを設定する。最初はよく分からなかったので、他のActionのプライバシーポリシーを参考にした。
ちなみに、プライバシーポリシーはGoogleサイトで作成した。
リリース
リリースボタンを押して、アプローブされるのを待とう。
レスポンスが結構早いのと、あちら側でも簡単なテストをやってくれるのでありがたい。
曖昧な説明や動作がおかしかったりするとリジェクトされる。
リリース後
ちなみにリリースから数日経つと、リリース記念に「Tシャツ」と「毎月200ドルのGoogle Cloudクレジット」がプレゼントされる。これは嬉しい。