全くの初心者がスマートスピーカーのアプリを2週間で作りました!

cacapon \ 2019/06/05

プログラミング

<はじめに>

はじめましてこんにちは! cacaponと申します~

今回Amazing engineの技術ブログに
記載させてもらうことになりました~
それで、何を載せるかという話になるのですが…​

今回は私が作成した「Google Home」用アプリ
RPGの道具屋 」について書いていきたいと思います♪

今回アプリは、
リリースに必要なActions on Googleから始まり、
自然言語処理を行うDialogflow
webフレームワークのFlaskなど
など、色々なツールを組み合わせて作成しました!

といっても、cacaponはそれらのツールは使ったことはありませんし、
プログラミングもちょっとかじった程度です。

もちろん、アプリのリリースもやったことありません…

ほとんど無に等しい状態で
そんなアプリリリースまで出来るものなのか?
と内心ひやひやだったのですが…

なんと、二週間 で出来てしまいました♪

そんな「 RPGの道具屋 」アプリがどんなものかというと…

レトロゲームでおなじみの道具屋の店員さん♪
それをGoogle Homeを使ってアイテムを買う…

という流れを再現してみてます♪

どんなもんだと知りたい方は、
是非こちらのRPGの道具屋のリンクをご確認ください♪
↓↓
RPGの道具屋

今回のブログは「 RPGの道具屋 」の作り方を通して どんな感じでツールを使ったのか?
どんな感じで作ったのかを紹介していきたいと思います! このブログを通してアプリリリースの行う際の
開発の一助になれば良いなと思っています!

それでは、そろそろ作り方を見ていきましょうか~
少し長くなるかと思いますが、お付き合いいただけると嬉しいです♪

<必要なもの>

Google アカウント (Actions on GoogleとDialogflowで使います~)
Heroku アカウント (本アプリはHeroku環境で動作させています~)
ngrok (デプロイ前の動作確認で使います~)
Git (本ブログ内ではあまり触れていませんが、ソースコードのバージョン管理で利用しました~)
プログラミングの知識 (本アプリではPythonを使います~)

<使用するアプリの準備>

今回主に使っていく、Actions on Googleと
Dialogflowの準備の仕方を説明していきます~

=Actions on Googleでプロジェクトを作る=

最初にGoogle Home を動かすアプリのプロジェクトを作ってみましょう~
Actions on Google のページに行き、

右上あたりにこんなボタンがあるので押してください~
(Googleのアカウントが必要になりますので、作っておいてください~)

0

1.まず、Add/import projectを選びます~

1
  • 初回だと下の画面が出ます~

2
  • 翻訳内容するとこんな感じになるので、
    赤枠の通りに設定してください~

2 1 ja

2.続いてプロジェクト作成画面になります~
プロジェクト名を入力し、
赤枠の設定をしたら[CREATE PROJECT]を押しましょ~
※初回作成時の画像を取り忘れたため、
 以降「hello-world」のプロジェクトと差し替えて説明しています~  ご了承下さい~

3

3.右下のConversationalを選択をします~

4

4.左のメニューのInvocationを選択して、
Display nameを入力したあと、[SAVE]ボタンを押します~
※メニューが隠れている場合は≡マークを選択すると現れます~

5

5.次に左メニューから[Build]→[Actions]を選択して、
[ADD YOUR FIRST ACTION]ボタンを押します~

7

6.Custum intentになっているのを確認し、
[BUILD]を押しましょう~

8

=Dialogflowの初期設定=

7.初回だとDialogflowからGoogleアカウントへの
アクセスが求められるので、許可します~

9

8.次に初期設定画面が表示されます~

10
  • 翻訳すると下記になりますので、
    国の設定と利用規約にチェックを入れ、
    ACCEPT(翻訳後だと受け入れる)ボタンを押しましょう~

10 ja

9.時間経過のせいかもう一度同じ画面が出ました~
許可して先に進みます~

※ 通常時は二回も出るかはわかりません~

11

10.Dialogflowのプロジェクト開始時の画面になります~
japaneseに設定しましょう~

12

<アプリケーションを作成していく>

ここから、道具屋のアプリケーションを作成して行きます~
まずはDialogflowの設定を見てきましょう~

Dialogflow編

<Entitiesを開き、Entitieを作成していく>

  • Entitieは、会話に含まれる特定の単語を
    変数として扱うことができる機能です~
    作成するEntitieは下記の通りです~

    • exit_of_in_the_middle(途中退出感知用)

    • ItemList(道具屋のアイテムを扱う値)

    • YES(はいを判定する値)

    • NO(いいえ判定の値)

    • Pieces(何個購入するかを保持する)

  • 新規で作成する場合は、[CREATE ENTITY]を押すと作成します~

13 re
  • こちらの図は、ItemListを開いた時の図です~

  • 左は値、右がどの単語が値として扱われるかを記載していきます。
    例:左が薬草、右が薬草・やくそうの場合、 「やくそうください」と話しかけたら「やくそう」という単語から「薬草」が含まれていると判断します~
    「薬草」ももちろん同じ♪
    でも、「ヤクソウ」は左にないため「薬草」と判断されません。

14 re

ItemList以外のEntitieは下記のように設定しました。

14 1
14 2
14 3
  • 数字を扱う場合は @sys.number:number を使います~

14 4

<Intentsを作成する>
Intentはあるトリガーをきっかけに呼ばれるイベントといえばよいでしょうか?
例えば、「令」と言ったら「和」と返す…
「何の言葉」に「どう返すか」を設定するのがIntentになります~

  • 今回のアプリケーションでは下記Intentを設定しました。

    • Default Fallback Intent (聞き取りミスの時~)

    • Default Welcome Intent (起動する時~)

    • MAIN (上記以外のもろもろの対応はこちら~)

    • end (退出が必要な場合はこちらが呼ばれます~)

15

なお、Intentsを作成してから必要な値が分かる時もありますので、
作成した時はEntityの作成と行き来する事もありました~

次に、Intentsの各項目を紹介します~
下記図はMAINを開いた時の図がこちら。

15 1
  • Contexts
    yes/no分岐を作るときに使用。
    今回はPython側でフローを管理するので使いません~

  • Events
    イベントを呼び出すときに使います~
    終了処理を書く時には設定しますが、
    MAINでは設定しません~

  • Training phrases
    Intentsが呼ばれる会話を設定します~

  • Action and parameters
    Entityを設定していると該当単語が現れた際に
    自動的に値が設定されます~
    手動ではいじってません~

  • Responses
    MAINが呼ばれた際に、何とメッセージを返すか設定します♪
    webhook(後述)を使わない場合はここを設定する必要がありますが、
    今回はメッセージをPythonが返すため設定してません~

  • Fulfillment
    そのIntentsがwebhookを利用するかしないか設定します~

それでは各項目を見ていきましょう~
下図はMAINのTraining phrasesです~

15 2

Add user expressionに記載し、Enterすると、
Training phrasesが追加されます~
その時、Entitieを設定していますと、
図のように色分けされるようになるのです♪
色分けされたところがEntitieと対応するようになります~
ここに入力されたフレーズで判断していきますので、
考えられるフレーズをどんどん入力していきましょう~

次に、下図はAction on parametersである。

15 3

Training phrasesにEntitieが含まれている場合、
自動的に設定されます~

次の下図は、MainのFulfillmentになります~

15 4

[Enable webhook call for this intent]を有効にすると、
そのIntentがwebhookを通して
json形式で外部アプリとやり取りすることができます~
MAINはjsonでやり取りしますので、有効にしましょう~


こんな感じで設定していきます~
他の項目も見ていきましょう~

Default fallback intent の場合

16 1

Pythonからjsonデータを受け取るため
Fullfilmentを有効にする必要があります~
それ以外は未入力でOKです~

Default welcome intent の場合

16 2 A
16 2 B
16 2 C
  • Events Action and parametersは自動で設定されます。

  • Training phrasesは無くても大丈夫ですが、
    Dialogflowからテスト確認したいときに設定していると便利です~
    今回は「道具屋」というワードを設定しています~

  • Responsesにもテスト用に入れておきましょう~
    jsonファイルが返されなかったときはこちらが呼ばれますので、
    エラーチェック用に設定しましょう~

※ 「デフォルト」は適していないかも?
 「JSONが返されませんでした」の方が分かりやすいかもです。

  • こちらもjsonデータのやり取りが発生するので、
    fulfillmentは有効にしましょう~

end の場合

17 A
17 B
  • Eventsには actions_intent_CANCEL を設定します。

actions_intent_CANCEL はActions on Googleへ
 終了を伝えるためのイベントになります~

  • Responseのワードは設定しませんが、
    Set this intent as end of conversation を有効にします~
    これは、このintentが来たら終わりですよ~というのを
    伝えるためのものになります~

  • こちらも他のintent同様jsonデータのやり取りがあるので、
    fulfillmentは有効に~

ここまで出来れば、Dialogflowで設定する項目は一旦一休みになります~
この後は、Pythonでの実装部分に取り掛かりましょう~

Python編

さて、Pythonでの実装についてですが、
主に行いたいのは下記のことになります~

  1. Dialogflowからjsonデータを受け取る事

  2. 受け取ったjsonデータからどのユーザーか判断すること

  3. 会話のフローを管理すること

  4. 利用中のユーザーがどの会話のフローか判断して、
    道具屋が返すべきメッセージを選択すること

  5. 選択したメッセージをjson形式でDialogflowへ渡すこと

また、アプリ審査を通すために、
プライバシーポリシーの作成も必要になりますので、
プライバシーポリシーもPythonで作っていきましょう~

以上を踏まえて、大まかな設計は下記のようにしていきます~
Flaskとmvcモデルを組み合わせた形ですね~

shop dataflow

それでは、実装内容を見ていきましょう~

…と言いたいところですが、コードが長すぎるので
各コードがどんな役割かを中心に紹介したいと思います~

全体の階層構造

|--.gitignore ※1
|--Procfile
|--requirements.txt
|
|--www
|  |--app.py
|  |--factory_app.py
|  |--setting.py
|  |
|  |--controllers
|  |  |--__init__.py
|  |  |--posts.py
|  |
|  |--instance
|  |  |--sample_application.cfg※2
|  |
|  |--model_instance
|  |  |--__init__.py
|  |  |--i_flow_maneger.py
|  |  |--i_flow_status.py
|  |  |--i_output_data_adjust.py
|  |  |--i_Select_next_status.py
|  |  |--i_Select_ret_mes.py
|  |
|  |--models
|  |  |--__init__.py
|  |  |--flow_maneger.py
|  |  |--flow_status.py
|  |  |--output_data_adjust.py
|  |  |--Select_next_status.py
|  |  |--Select_ret_mes.py
|  |
|  |--templates
|  |  |--posts
|  |  |  |--privacy-policy.html
|  |
|  |--views
|  |  |--__init__.py
|  |  |--posts.py
|
|--test
|  |--conftest.py ※1
|  |--flasktest
|  |  |--test_app.py ※1
|  |--modeltest
|  |  |--tes_data.json
|  |  |--test_flow_maneger.py
|  |  |--test_flow_status.py
|  |  |--test_output_data_adjust.py
|  |  |--test_select_next_status.py
|  |  |--test_select_ret_mes.py

※1:社内用プライベートリポジトリ[flask-mvc]から流用してます~
※2:流用したものが残ってました~ 今回使いません~

rootディレクトリのファイル

|--.gitignore
|--Procfile
|--requirements.txt
gitignore

・Gitにプッシュしないファイルやフォルダを書きます~

Procfile

・Herokuにプッシュ(後述)する際に実行される
 コマンドを記載します~
 ここを設定していないと、
 後でプッシュした時にエラーになるので設定しましょう~

↓中身

web: cd www &&  gunicorn app:app --log-file -
requirements.txt

実装に使ったパッケージを記載していきます~
今回使ったのは
Flask・pytest・Gunicornの三つです。
バージョンはpip freezeで確認しておきましょう~


wwwのファイル

www
|--app.py
|--factory_app.py
|--setting.py
shop dataflow

上図でいうFlaskにあたる部分です~

app.py

Flaskを立ち上げます~

factory_app.py

Flaskの設定やcontrollerの情報を読み込んで
Flaskのインスタンスを生成します~

setting.py

各モード(testやdev,prodなど)毎に設定します~


www/controllersのファイル

controllers
|--__init__.py
|--posts.py

設定したURLにアクセスしたとき、どのviewに飛ばすかを設定します~
今回はmodelとプライバシーポリシーへ送る指示を作ります~


www/viewsのファイル

views
|--__init__.py
|--posts.py

本アプリのviewsではプライバシーポリシーのページ呼び出しと、
models内のクラスを呼び出す設定を行っています~


www/templatesのファイル

templates
|--posts
|  |--privacy-policy.html

Googleのアプリにはプライバシーポリシーが必要です。
そのhtmlファイルをこちらで作ります~

ただし、作成時は以下に注意してください~

1アプリケーション名を入れること
2個人情報の扱いを記載すること
3アナリティクスを使うならそのあたりを記載すること

このあたりを守れていればおそらく大丈夫かと思います~


www/modelsのファイル

www/model_instanceのファイル

models
|--__init__.py
|--flow_maneger.py
|--flow_status.py
|--output_data_adjust.py
|--Select_next_status.py
|--Select_ret_mes.py

model_instance
|--__init__.py
|--i_flow_maneger.py
|--i_flow_status.py
|--i_output_data_adjust.py
|--i_Select_next_status.py
|--i_Select_ret_mes.py

今回の実装ではインスタンスを生成する部分と
クラス部分を分けて実装しました~

やりたいことは受け取った会話から
それに合った返事を返す。ということをしています~

Select_ret_mes.py

大元のプログラムですね~
受け取った会話から、適した返事を返すということをしています~

flow_maneger.py

会話フローの管理をしています~
・そのユーザーは今どこのフローか返したり ・そのユーザーの次のフローを設定したりしています~

flow_status.py

今のフローだとどの会話を受け取った時、
どんなフローになってどんな会話を返すか…
そんなデータ一覧を管理しているのがflow_status.pyです~

Select_next_status.py

flow_status.pyの会話フローの一覧と
受け取った会話データを使って、
次のフローが何かを決めるプログラムです~

output_data_adjust.py

Dialogflowに会話データを返すとき、

通常時は

{
    "fulfillmentText": "何かしらのメッセージ",
    "source": "何かしらのメッセージ"
}

会話を終わらせるときは

{
	"payload": {
		"google": {
			"expectUserResponse": False,
			"richResponse": {
				"items": [
					{
						"simpleResponse": {
							"textToSpeech": "何かしらのメッセージ"
						}
					}
				]
			}
		}
	}
}

という形式にしないといけません~
その形に整形するのがこちらのプログラムになります~

…​

因みに、model_instance内のプログラムは
各クラスのインスタンスを生成するプログラムになります~


testのファイル

test
|--conftest.py
|--flasktest
|  |--test_app.py
|--modeltest
|  |--tes_data.json
|  |--test_flow_maneger.py
|  |--test_flow_status.py
|  |--test_output_data_adjust.py
|  |--test_select_next_status.py
|  |--test_select_ret_mes.py

pytestで実行できるテストコードになります~

ルートディレクトリでpytestと打つとテストしてくれます~

ただ… テストコードを作っていない箇所、
テストコードが完成していない箇所があります~

これはcacaponの開発の仕方のミスです~
途中までテストコード作っておらず
不完全なテストコードになってしまいました~


と、ここまででPythonの実装は以上になります~
次は、作ったコードで動作確認をしてみましょう~

動作確認編

Dialogflowを使って確認してみる

動作確認を行うためには、DialogflowとFlaskを接続する必要があります~
今回の接続方法は、webhookを使ったやり方で行うのですが、
その時に必要になるのが、https形式のurlです~

本番だとHerokuにデプロイし、そこで発行されるurlを使うのですが、
ちょっとした修正だとHerokuへデプロイはちょっとめんどくさい…

そんな時に使うのが、ngrokです~

ngrokというのは、ローカルホストのURLを一時的にhttpやhttpsとして扱えるツールで、
一時的な確認でしたら、ngrokで作成したhttpsのurlを使って行うことができるのです~

というわけで設定していきましょう~

まずはこちらでアカウント登録、
及び設定をお願いします~ (英語で書かれていますが、分かりやすいと思います~)

設定が終わりましたら、ngrokを起動してみましょう~

24

CLIな画面が出てくると思いますので、
ngrok http 5000 とコマンドを打ち、Enterしましょう~

そうすると下のような画面が出ると思います~

25

そしたら、Forwardingというところにhttpsのurlが出ますので、
図のように選択してコピーしましょう~

次に、DialogflowのFullfilmentに設定します~
写真のようにWebhookのEnabledを有効にし、
先ほどコピーしたurlを貼り付けましょう~

なお、今回のアプリではcallbackにアクセスしたら
modelが呼ばれるようになってますので、
"https://<ngrokのurl>/callback"
という形式になります~

27

設定が終わったら保存して、app.pyを立ち上げましょう~

カレントディレクトリから
python www/app.py を実行します~

23

これで確認する準備が整いました!
さっそくDialogflowを使って確認してみましょう~

右上にあるTry it now に入力してみてください~

31

こちらの写真はお試しで作ったHelloworldのものになりますが、
おはようございます と入力したら、
Welcome to Flask. Hello World!
と返っているのがわかるかと思います~

こんな感じで、返事が正しく返ってきてるか確認してみて下さい~

より詳しい情報を見たいときは
DIAGNOSTIC INFOを確認するとjsonの内容が見れます~


Actions on Googleを使って確認してみる

Dialogflowの確認が大丈夫そうでしたら、
実際の環境に近い、Actions on Googleの
シミュレータを使って確認しましょう~

まずは、DialogflowのIntegrationsを開いて、
Google Assistantを押します~

17

下の方にあるTESTを押しましょう~

18

CONTINUEを押します~

19

しばらく待つと、シミュレータの画面に移動します~

21

※ 下の画面が出た場合、Actions on Googleでプロジェクトを作成した時のアカウントと今のアカウントが異なる可能性があります~
 違ってなかったとしても、デフォルトのアカウントが違うという場合もありますので、そのあたりを確認してみてください~

20 error

では、動かしてみましょう~
メッセージを入れてどう動くか、
アプリがどうしゃべるか確認していきます。

22

もし、応答がないと言われましたら、次の事を確認してください~
・app.pyがちゃんと起動しているか?
・Python側で何かエラーが起きてないか?

ここまでの確認で問題がない!
後は、リリースするだけ!
となりましたら、アプリリリースの作業に入りましょう~

アプリリリース作業

Herokuにデプロイする。

確認作業ではngrokを使用していましたが、
ngrokはあくまで「一時的」に確認するものですので、
本番には向いていません。

そこで、恒久的に動作する環境を用意するため、
Herokuを使っていきたいと思います~

インストールに関してはこちらを参考にしています~

インストールできましたら、cliを立ち上げて進めていきましょう~

まずは、heroku login
logeed in as <登録したメールアドレス> がでたらOKです~

28

次に、heroku create app名 でHerokuのアプリを作成します。
(今回は写真用に、test-rpg-shopという名前で作成しています)

29

そして、git push heroku master でアプリにソースコードをプッシュします。

※ ビルドがうまくいかない場合は、
Herokuのビルドパックが設定されていないのかもしれません。
ビルドパックの変更については以下ページがわかりやすいです~
   http://info-i.net/heroku-buildpacks

プッシュが正常に完了したら、DialogflowのwebhookのURLを
HerokuのURL/callback に変更して動かしてみましょう~

これで正常に返事が返ってくるようでしたら
Herokuの環境はできたといってよいでしょう。

※上手くいかなかったら、
 CLI上で heroku logs -t を打ち、
 Herokuのログを確認してみてください~
 何かしらの解決のためのヒントが出ているはずです~

Actions on Googleにアプリの情報を記載していく。

Herokuの環境ができましたら、いよいよ最後の行程、
リリースに必要な情報を記載していきましょう~

Description

Actions on GoogleのDirectory informationを開き
Descriptionを開いてみましょう~

37

ここは、アプリの説明を記載します~
short の方は概要を
Full の方は詳細を書いていく感じですね~

次は、Sample invocationsです。
こちらは、どんなワードでアプリが呼び出されるか設定します~

続いて、Imagesですね~

38

こちらはアプリのページで使われるアイコンと画像を設定します~
画像は任意ですが、アイコンは必須なので合ったアイコンを設定しましょう~

Contact detailsは表示するアプリ作成者を記載します~

39

Privacy and consentにはプライバシーポリシーのurlを記載します。
今回だと、https://amazingengine-rpg-shop.herokuapp.com/privacy-policy
というURLを設定しています~

一番下にある、Additional Information には下記のように設定しました。 Category:Games & fun
For Families:No
Alcohol and Tobacco:No
Testing Instructions (optional)
薬草を2個ください
はい
いいえ
残りはすべてチェック無

42
43

Location targeting

続いて、Location targetingを開きます~

こちらはどこの国をターゲットにするかを設定するところですね~

今回は214ヵ国を対象にしています~

44

Surface capabilities

Surface capabilitiesでは、必要なデバイス機能を設定していきます~

今回はスピーカーアプリなので、Do your Actions require audio output?のみyesにします~

45

ここまで設定したら、あと一歩です! リリース申請をしましょう~

Releaseを選択して、SUBMIT FOR PRODUCTION を押します。
Googleさんが審査してくれますので、通るまで待ちましょう~

通らなかった場合も、メールでどこがダメだったか教えてくれるので、
通らなかった原因を直して申請…というのを繰り返して
審査を通しましょう~

これで通りましたら、晴れて RPGの道具屋 がリリースされたことになります! Google Homeに「RPGの道具屋につないで」っていえば、
cacaponの作った RPGの道具屋 を遊ぶことができますよ~♪

※もし、Google Homeを持っていない…という方も、
 スマフォアプリで Google Assistant をインストールして、
 「RPGの道具屋につないで」って言えば遊べますよ~

<終わりに>

ここまでお疲れさまでした!

今回のブログを見て
「アプリ作ってみたいけど、ちょっと難しそうだな…」と思っていた方が
「お!意外と簡単じゃん!」って思ってくれたら嬉しいなぁとも思っています~♪

私がここまで出来たのはAmazing engineの皆さまがフォローしてくれたおかげだと思います!
本当にありがとうございました~

そんな弊社の募集がありますので、最後ですがご覧ください♪


Amazing engineでは、代表の「必要なものを先に作ろう」という考えのもと、
botやシステムなどを皆で考えながら、より良い会社にして行っている最中です!

私は働き始めてまだ間もないのですが、そのための時間はしっかりあり、
手厚いフォローもあってとても充実した仕事を送れています!
ここでしたら、いずれ私がやりたいゲーム作りもできるなぁという実感を持っています!

そんな弊社ですが、只今社員募集中です!
よかったら一緒にAmazing engineで働いてみませんか?

私たちと一緒に会社を大きくしたい!
じっくり考える環境で企画・設計・実装したい!

そんな貴方と一緒に働けることを、心よりお待ちしています!

もし↑のリンクが終わってしまっていても大丈夫です!
その際は、お手数ですが下記メールアドレスにご連絡ください!


それでは今回のブログはここまで♪
最後まで読んでくれてありがとうございました!
また次のブログで会いましょ~