はじめに

スマートスピーカーの流行にのって、Google Home Miniを購入したので色々遊んでみているのですが、Herokuと繋げた何かが出来ればアドベントカレンダーのネタになるのでは、ということで、Google HomeからHerokuのアプリを作ってみました。

結果

で、出来上がったものがこちらです。
(クリックでYoutubeに飛びます)

Google HomeからHerokuアプリを作るデモ

(夜中2時くらいにひっそり撮影したのもあって、ぼそぼそしゃべっててすみません…)

内容としては、PHP7でHeroku Postgresqlアドオンを追加したアプリを作成する、というもの。
この結果、出来上がったアプリが以下。

deploied_voice.jpg

ブラウザからphpinfoを表示させた状態。

web_site_voice.jpg

ララベルのアプリを作ったり、PHPのバージョンが古いものも作りたかったけれど時間がなくて断念しました…。
(ちなみに、つい先日リリースになった7.2はすでにHerokuで作成出来るようになっていました。)

というわけでかなり長くなってしまいましたが、以下解説です。

構成

Google HomeアプリはDialogflowを使用して作成しました。
動画の通り、テスト用アプリとして動作確認した状態です。そこから、フック先のURLとしてHeroku上に作成したアプリのURLを指定し、そのアプリがHerokuのアプリを作成している、という構成です。
Herokuのアプリ作成には、Platform APIを使用しました。

Heroku

Herokuのアドベントカレンダーなので、Heroku側の説明から。

Platform API

HerokuにはPlatform APIという、Herokuアプリの作成や編集などを行えるAPIが用意されています。

今回のHerokuアプリの作成にはこちらを利用しました。

利用準備

まずは操作対象のHerokuアカウントのAPIキーを取得します。
これはアカウント設定から作成、確認することが可能です。

右上の自分のアイコンから、Account Settingsをクリック。Accountタブの下のほうにAPI Keyのセクションがあります。

api_key.jpg

これを使用するHerokuアプリの環境変数などに入れておきます。

PHPライブラリ

今回はHeroku側のアプリはPHPで作成しました。
PlatformAPI用のPHPライブラリがあるのでそちらを利用します。

利用

インストールや設定などはGithubを参照して下さい。Composer経由で簡単に行えます。
利用方法も簡単で、先ほど確認したAPIキーを使用して接続すれば、エンドポイントを指定するだけで使えます。

例えば、認証後、作成しているアプリの一覧を取得するには以下のようにします。

$heroku = new HerokuClient([
    'apiKey' => env('HEROKU_API_KEY')
]);

$app_list = $heroku->get('apps');
return $app_list;
PUT

このライブラリでは、GET POST PATCH DELETE は用意されているのですが、PUT は用意されていません。
PlatformAPIのドキュメントにも使用メソッドにはPUTが記載されていないのですが、実際には使用されています。

https://devcenter.heroku.com/articles/platform-api-reference#methods

PUTメソッドを使用しているビルドパックの追加

https://devcenter.heroku.com/articles/platform-api-reference#buildpack-installations-update

そこで上記ライブラリのClientクラスにPUTメソッドでの送信を追加してみました。
コントローラーに適当なクラスを作成し、その中で、HerokuClient\Clientを継承し、putメソッドを追加します。

<?php

namespace App\Http\Controllers;

use HerokuClient\Client;

class ClientPut extends Client
{
    public function put($path, $body = null, array $headers = [])
    {
        return $this->execute('PUT', $path, $body, $headers);
    }
}

以下のように利用します。

use App\Http\Controllers\ClientPut as HerokuClient;

// 中略

$buildpacks['updates'][] = ['buildpack' => 'heroku/php'];
$heroku->put('apps/'.$app_name.'/buildpack-installations',$buildpacks);

app.json

しかしAPIとして用紙されているものだけでは、PHPのビルドパックを追加したアプリは作成出来ても、PHPのバージョンを指定したり、ドキュメントルートの設定などが出来ません。
そこまでカスタマイズした状態のアプリを作る方法として、Heroku ButtonやReview Appでも使用されているapp.jsonを利用した方法が用意されています。

手順としては、予め作成したい状態のアプリをGitHubのリポジトリで作成し、そのリポジトリ内にアプリの設定ファイルとなるapp.jsonを含めておきます。
そしてそのリポジトリを指定して作成するようにAPIから呼び出してやれば自動でアプリを作成する事が出来ます。

今回は時間の都合もあって、PHP7.1と7.0用のリポジトリのみを作成しました。
(その他のバージョンやLaravelのアプリなんかも同じように作成しておけば可能なはずです)

余談ですが、

PHPのバージョンを変えるには、composer.json内でPHPのバージョンを指定する必要があるのですが、deploy時にはcomposer.lockが必要になるため、ローカル環境などでcomposer installしておく必要があります。
そうなると、installを実行する環境と指定するPHPのバージョンが異なるとエラーとなってしまうため、作成したいバージョンの環境をVagrantなどで準備してやる必要があります。
そんなわけで今回は手元にあった7.1と7.0のバージョンのみで作成しています…。

app.json内では追加するアドオンや環境変数の設定などを記述しておきます。
環境変数のみ、APIからアプリを作成する際にapp.jsonに記載がないものを追加してやることが可能です。
今回はアドオンはGoogle Homeで選択されたものを追加するためここでは指定していませんん。

https://github.com/k-usk/heroku-php-7.1/blob/master/app.json

API

APIから作成する際には、作成したいリポジトリのURL末尾に/tarball/master/をつけて、圧縮されたデータとして読み込ませてやることが出来ます。

$created_app = $heroku->post('app-setups', [
    'source_blob' => [
        'url' => 'https://github.com/k-usk/heroku-php-7.1/tarball/master/'
    ]
]);

レスポンスは以下のように帰ってきます。

{
    "id": "3caddefc-9d93-4bb2-b6b2-f124a230af94",
    "failure_message": null,
    "status": "pending",
    "app": {
        "id": "5ffd5e4c-b583-45cd-9cc8-b417e6628bfe",
        "name": "pacific-meadow-58700"
    },
    "build": null,
    "manifest_errors": [],
    "postdeploy": null,
    "resolved_success_url": null,
    "created_at": "2017-11-28T06:33:45+00:00",
    "updated_at": "2017-11-28T06:33:45+00:00"
}
確認

作成は非同期で行われるため、作成されたことを確認するためのAPIも用意されています。
先ほど作成時のレスポンスのidを使用して、以下のようにすると確認することが出来ます。

$created_app = $heroku->get('app-setups/fd35478e-bde6-4369-828f-910d7835705d');
return json_encode($created_app);

作成が完了していると以下のように返却があります。

{
    "id": "3caddefc-9d93-4bb2-b6b2-f124a230af94",
    "failure_message": null,
    "status": "succeeded",
    "app": {
        "id": "5ffd5e4c-b583-45cd-9cc8-b417e6628bfe",
        "name": "pacific-meadow-58700"
    },
    "build": {
        "id": "87925806-05df-4e67-99b8-b8e45797fe94",
        "output_stream_url": "https:\/\/build-output.heroku.com\/streams\/5f\/5ffd5e4c-b583-45cd-9cc8-b417e6628bfe\/logs\/87\/87925806-05df-4e67-99b8-b8e45797fe94.log?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAJQUUZPBDLMDG7K7Q%2F20171128%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20171128T063348Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=b796f85a7e709bc759769b3fd40acd5ecf3d13bfb28fba6941000375e56770ea",
        "status": "succeeded"
    },
    "manifest_errors": [],
    "postdeploy": null,
    "resolved_success_url": "https:\/\/pacific-meadow-58700.herokuapp.com\/",
    "created_at": "2017-11-28T06:33:45+00:00",
    "updated_at": "2017-11-28T06:33:56+00:00"
}

今回はアプリ作成後に、アドオンの追加と、アプリ名の変更を行うため、確実にアプリが作成されたのを確認してから実行するようにしました。

//app.jsonを使用したアプリの作成
$created_app = $heroku->post('app-setups', [
    'source_blob' => [
        'url' => 'https://github.com/k-usk/heroku-php-'.$this->version.'/tarball/master/'
    ]
]);
$app_name = $created_app->app->name;
$build_id = $created_app->id;

//終わるまで待つ
do{
    sleep(5); //deploy時間で5秒待つ
    $created_app = $heroku->get('app-setups/'.$build_id);
    $build_status = $created_app->build->status;
    echo 'Build status : ' . $build_status."\n";
}while($build_status !== 'succeeded');

//以下、アドオンの追加と名前の変更など

キュー

GoogleHomeでは、レスポンスに5秒以上かかった場合にはタイムアウトになってしまうという制限があるようです。
なので、Herokuのアプリ作成完了まで待ってから返していたのでは間に合いません。
そんなわけでGoogleHomeからのリクエストはとりあえずキューに入れておき、後からWorker Dynoによるバックグラウンド処理によってアプリの作成をさせるようにしました。
Laravelには標準でキューの処理が実装されているのでそちらを利用しています。

Worker Dynoによるジョブ処理の指定

resources.jpg

Herokuの無料枠は(クレジットカードを登録している場合)1ヶ月 1000時間(Dyno)まで利用出来ます。
1Dynoを丸々動かしている場合、720時間消費される計算になります。
よってWeb DynoとWorker Dynoの両方をfreeプランでは1ヶ月丸々動かし続けることは出来ませんので注意して下さい。
(実際はweb dynoは30分アクセスがなければ落ちるので丸々使われることはないかもしれないけれど)

Heroku側まとめ

  • GoogleHomeからリクエストがあると、作成内容がキューに追加されます
  • その後、バックグランドの処理によってPlatformAPIを利用してアプリが作成されます
  • アプリの作成には予め作成されたGitHubのリポジトリを利用して行われます

ちなみにアプリの名前はHerokuの標準っぽく、寿司のネタ+自然系の単語+ランダムな数字、とかにしています。

Google Home

GoogleHome側の説明はあまり深くわかっていないこともあるのでさらっと目に。

Google Homeアプリの開発、というのはイコール、Googleアシスタントアプリの開発、となります。
というのも、Google Homeというのは結局のところ、Googleアシスタントの音声デバイスであって、スマホのアプリからテキストでチャットしても基本的に同じことが出来るようです。

その作成方法として現在、2種類提供されているようで、今回はGUIでの作成が可能であるという、Dialogflowで作成してみました。

Dialogflow

作成方法に関しては以下の記事がすごく参考になりましたのでそちらを見てもらう方が早いと思います。

この記事を見て、Entityの使い方が理解できました。

分岐

まだあまりよくわかってなかったりするのですが、会話によって会話の流れを分岐させるには、IntentsのUser Saysの箇所にユーザが応答するであろう内容を記述しておくことで、それぞれのIntentsに処理を遷移させることが出来るようです。
(Contextの使用方法がイマイチわからない)

今回は最初に起動される、Welcome intentでPHP、Laravelどちらのアプリを作成するかを訪ね、PHPと言われた場合にはバージョンなどを尋ねるIntentが実行されるようにしています。

intents_welcome.jpg

PHPが選択された場合に遷移するIntent

intents_php.jpg

ララベルが選択された場合には準備中であることを返しているだけなので、User Saysにララベルと書いておき、Responseに直接テキストを記述しているだけ。

このように簡単に作らたりするのだが、ユーザーが入力に失敗したり、正しく認識されなかった場合の復帰方法がよくわからなかったりする。
welcome intentに戻す方法があったりするのでしょうか…。

Entity

Entityは入力の選択肢項目の設定となるわけだけど、一つの選択肢に対して、色々な言い方を設定事が可能。
PostgreSQLをポスグレでもポストグレスでも受け付けられるようにしたり出来る。

entities_addons.jpg

Fulfillment

外部のAPIに投げる用のFulfillmentはこんな感じ。ベーシック認証を設定しているので、内容を入力している。

fulfillment.jpg

Simulator

作成中にDialogflow上からでもテスト可能なので都度のテストはそちらでしておくとよい。ちゃんとFulfillmentを使った外部のAPIへのPOSTも実行してくれる。作成後、ちゃんとしたテストはSimulatorを使ってテストしてやることが可能。

ここでのやり取り内容が、まんま、GoogleHome上でも再現されることになります。

simulator.jpg

不明点など

  • DialogflowからDRAFTをアップデートすると修正後に再度アップデートした際に全然反映されない気がする(キャッシュとか時間がかかったりする?)
  • エラーの際のデバッグ方法がよくわからない(JSONのレスポンスなどから見るしかない?)
  • contextとintentの流れの制御が難しい
  • 利用者のGoogleアカウントと連携した処理なんかはDialogflowでも出来る?
  • 声ってどこかで変更できたりするの?

GoogleHome側まとめ

  • Dialogflowを使い、PHPのバージョンとアドオンを選択させます
  • 選択された内容を含むJSONを作成したHerokuのAPIへ投げるようにします

感想

とりあえずこれでGoogle Homeの入力からHerokuアプリの作成が出来るように作成できました。
入力ミスの対応などが出来ていないので自分しか使えないですが…。(そもそもAPIキーが固定なので他の人に渡せない)

Google Homeの音声入力はかなり優秀な印象ですごいと思うのですが、アドオンの名所など、一般的でない言葉の認識はやはり難しいよう。レディス、と言っても、レディース、と認識されてしまったり。その辺の揺れの許容とかどうやって入力させるか、とかは考えてやらないといけないのかもしれません。
とりあえずこんなアプリでも作ってみることでわかることが多かったので、VUIの難しさを感じることが出来てよかったと思います。