GoogleHome 開発をやってみた 〜Dialogflow編〜

事務所にある GoogleHome で何かできないか、ということで、すこし調べたのでメモしておきます。

## 概要
GoogleHomeで音声を使ったアプリケーションを開発するときの、データの流れは以下の通りです。

　[GoogleHome] ---- [Actions on Google] ---- [ \<action\> ] ---- [backend web-service]
 
左から３番目の \<action\> は開発する規模？種類？によって次の４つから選べます。
　1) Dialogflow
　2) Smart home
　3) Actions SDK
　4) Converse.AI

今回は 1 の Dialogflow と 3 の ActionSDK の場合をやってみました。

## Actions on Google
とりあえず、Actions on Google (https://developers.google.com/actions/) のページから、右上の ACTIONS CONSOLE に入ります。

Add/Import project で テスト用プロジェクトを作成します。
![screenshot001.png](https://qiita-image-store.s3.amazonaws.com/0/59523/c91390ef-6852-5451-e431-c1a09ce4f48b.png)

次に、Add action to your app の ADD ACTIONS を押して、使用するアクションを選択します。

![screenshot002.png](https://qiita-image-store.s3.amazonaws.com/0/59523/897dfae2-6c81-a416-f38a-ef56ad8ff941.png)
![screenshot003.png](https://qiita-image-store.s3.amazonaws.com/0/59523/d94c9165-7d14-a640-025e-671bdbfe388f.png)
＃最初は３つしか見えてませんが、すぐ下に more とあるのでそれを押すと全部見えるようになります。
各項目右下の BUILD を押すと作成されます。

## Dialogflow
まずは、Dialogflow を使ってみます。
Dialogflow は簡単な音声アプリを簡単に作成するときに使用します。
キーとなる音声（文字列）を登録し、その言葉が見つかったら処理（返事）をする、というルールを一つずつ登録していくスタイルです。
何らかの処理が Dialogflow だけで完了しない場合、例えばキーワードに関する情報を加工したりする場合には、backend の web-service へ処理を投げる（webhookする）ことができます。

![screenshot004.png](https://qiita-image-store.s3.amazonaws.com/0/59523/13881333-cd89-92e2-9bca-dfeb56bb52f4.png)

DEFAULT LANGUAGE を ja にして、右上の CREATE ボタンで作成します。

### Intents の作成
処理のきっかけとなる文章とその処理を定義します。
標準で作成されているのは Default Welcome Intent です。これは GoogleHome が、今回作成するアプリケーションに制御を渡すきっかけとなるものです。

![screenshot005.png](https://qiita-image-store.s3.amazonaws.com/0/59523/0f3f934e-7d62-3c9f-85c3-dfd38e50c3e5.png)

クリックして中を見てみましょう。

![screenshot006.png](https://qiita-image-store.s3.amazonaws.com/0/59523/17a3eeec-8104-2e13-1751-706bab02436c.png)

大きい項目には
　Contexts
　Events
　Training phrases
　Action and parameters
　Responses
　Fulfillment
があり、Events に 「Welcome」が設定されています。これが制御を移すイベントになります。

下段の Training phrases には、そのきっかけの文章を定義します。
Training とあるように、ここに書かれた文章は学習され、汎用的に丸められます。
「アプリに繋いで」とか「アプリと話す」的な言葉を登録します。

次に、Response に、その結果、応答する言葉を設定します。
「ようこそアプリへ！」とか「こんにちは、アプリです。」的な言葉を登録します。

Googleさんのガイドには、そのアプリが次の動作へスムーズに移行できるようなフレーズを登録すると良いと書かれていますので、そういうふうにした方が親切かと思います :p

### Exit Intent を作る

そしてこのままだとアプリへ制御が移動したまま終われないので、終わりのきっかけを作ります。
CREATE INTENT で Intent name を Exit Intent として、Events に Exit を追加、Training phrases に「さようなら」とか「バイバイ」とか登録して、Response に 「ありがとー」を追加して、その下の Set this intent as end of conversation のチェックを ON にして保存します。

![screenshot007.png](https://qiita-image-store.s3.amazonaws.com/0/59523/c4091661-5265-69a3-b9b8-0afb53997aca.png)

![screenshot008.png](https://qiita-image-store.s3.amazonaws.com/0/59523/b852fc70-b44f-c192-163c-8cd25586a8c6.png)

これで、「OK Google」→「アプリに繋いで」→ （応答）→「さようなら」→ （応答）で 自作アプリに処理を渡して終了する部分が完成しました。
＃実際には「アプリに繋いで」では繋がらず、「テスト用アプリに繋いで」と言わなければいけませんでした。なにか設定が足りないようです。。。

### テストしてみる
Actions on Google のページに戻り、左上の 三 からメニューを開いて、Actions を選択します。

![screenshot009.png](https://qiita-image-store.s3.amazonaws.com/0/59523/f9f9b590-11ac-e079-d2b5-fc536d0c4fc2.png)

![screenshot010.png](https://qiita-image-store.s3.amazonaws.com/0/59523/a29c6c76-2dbf-122e-d9d5-a9255453bb81.png)

Actions が開いたら右側の TEST ボタンでテストします。
シミュレーターが起動するので、テキストを入力するか音声でテストをします。

![screenshot011.png](https://qiita-image-store.s3.amazonaws.com/0/59523/4ccb42e2-a84e-cd1e-ab29-b0beffa7e341.png)

GoogleHome が繋がっていれば、音声でテストできます。
「OK Google」「テスト用アプリにつないで」と喋ると、上で登録した、「こんにちは！」が返ってきます。
さらに「さようなら」と喋ると、「ありがとー」と返ってアプリを終了します。

テキスト入力でのテストもできるので、同様に入力してみます。
リクエストや返事の実データが右側でみれるようになっています。

### Fulfillment を使ってみる
簡単な応答アプリの場合は前項までで作ることができますが、ちょっと複雑な処理とかしようと思ったときにこの Fulfillment を設定します。

![screenshot012.png](https://qiita-image-store.s3.amazonaws.com/0/59523/8fc70a16-343d-9248-04e2-2a21c840f6ee.png)

左メニューから Fulfillment を選択して Webhook を有効にします。
URL に backend の web-service の url を登録します。https でないといけないので注意します。
認証とかなければ他は空白で大丈夫です。
下段の Inline Editor は Firebase という グーグルさん関連のクラウドサービス上で処理をするよ、その時のコードだよ、というものです。今回は使用しません。＃Node.js を使っているみたい。。。

保存したら Intent を追加します。ポイントは一番下の Fulfillment の Enable webhook call for this intent を有効にするところです。＃その下は無効で。
Response は追加しません。web-service からの返事が Response になります。

![screenshot013.png](https://qiita-image-store.s3.amazonaws.com/0/59523/01f82f0f-06f1-3b3a-7d05-7f5293952423.png)

これで、登録した Intent のフレーズを認識したら URL の web-service へデータが飛んで行くようになりました。
データは HTTPS の POST で、body 部分が JSON形式の文字列になっています。

## 受け側をつくるよ
というわけで、受け側を作ります。今回は sinatra で作りました。

```ruby
post “/hogeactiontest” do
  content_type :json
  response = { :speech => “こんにちは、世界！”,
               :displayText => “Hello, world!” }
  response.to_json
end
```

JSON形式なのでJSONで返します。
:speech と :displayText は必須のようです。
とりあえずこれで返事を返すようになりましたので、同じようにテストしてみましょう。

Dialogflow 経由でやってくる JSON は以下な感じです。
あとは、色々できそうですね :p

あ、ちなみに body をハッシュにするのは

```ruby
params = JSON.parse(request.body.read)
```
です。あとはよしなに。

```json
{
  "originalRequest": {
    "source": "google",
    "version": "2",
    "data": { 
      "isInSandbox": true,
      "surface": {
        "capabilities": [
          { "name": "actions.capability.WEB_BROWSER" },
          { "name": "actions.capability.MEDIA_RESPONSE_AUDIO" },
          { "name": "actions.capability.SCREEN_OUTPUT" },
          { "name": "actions.capability.AUDIO_OUTPUT" }
        ]
      },
      "inputs": [
        { 
          "rawInputs": [ { "query": "ハロー",
                           "inputType": "VOICE" } ],
          "arguments": [ { "rawText": "ハロー",
                           "textValue": "ハロー",
                           "name": "text" } ],
          "intent": "actions.intent.TEXT"
        }
      ],
      "user": {
        "lastSeen": "2018-03-14T13:14:09Z",
        "locale": "ja-JP",
        "userId":"ABwpp....ru-3Q....RUI"
      },
      "conversation": {
        "conversationId": "1521033330197",
        "type": "ACTIVE",
        "conversationToken": "[]"
      },
      "availableSurfaces":[
          {
            "capabilities":[
              { "name": "actions.capability.SCREEN_OUTPUT" },
              { "name": "actions.capability.AUDIO_OUTPUT" }
            ]
          }
      ]
    }
  },
  "id": "6a34b33e-8d99-479b-aac8-9b0d3c392719",
  "timestamp": "2018-03-14T13:15:37.745Z",
  "lang": "ja",
  "result": {
    "source": "agent",
    "resolvedQuery": "ハロー",
    "speech": "",
    "action": "",
    "actionIncomplete": false,
    "parameters": {},
    "contexts": [
      { "name": "actions_capability_screen_output",
        "parameters": {},
        "lifespan": 0 },
      { "name": "google_assistant_input_type_voice",
        "parameters": {},
        "lifespan": 0 },
      { "name": "actions_capability_audio_output",
        "parameters": {},
        "lifespan": 0 }, 
      { "name": "actions_capability_media_response_audio",
        "parameters": {},
        "lifespan": 0 }, 
      { "name": "actions_capability_web_browser",
        "parameters": {},
        "lifespan": 0 }
    ],
    "metadata": {
      "intentName": "Hello Intent",
      "isResponseToSlotfilling": false,
      "intentId": "6e68c027-4c00-4ec7-a379-04a8229162f7",
      "webhookUsed": "true",
      "webhookForSlotFillingUsed": "false",
      "nluResponseTime": 3
    },
    "fulfillment": {
      "speech": "",
      "messages": [
        { "type": 0,
          "speech": "" }
      ]
    },
    "score":1.0
  },
  "status":{
    "code":200,
    "errorType":"success",
    "webhookTimedOut":false
  },
  "sessionId":"1521033330197"
}
```

Intents を作成するときに Action にタグのようなものが付けれます。
そこで登録した名称は [result"]["action"] の中に入ってくるので、これをみて処理を変えることもできたりします。もちろん ["result"]["metadata"]["intentName"] で処理を分けても大丈夫です。

## まとめ
Dialogflow を使うと簡単に音声アプリが作れるのはすごいですね。
GoogleHome のような音声認識を使ったものはインパクトありますし、これからますます多くなってくると思うので、簡単にできるのはいいことです。

次は Actions SDK 編 予定。。。
→ https://qiita.com/ysomei/items/9c38dc76cd698e59026a