Actions on Google(Dialogflow)での構築知見を少し

## **初めに**
   
   こんにちは。CYBIRDエンジニア [Advent Calendar](http://qiita.com/advent-calendar/2017/cybird)
   14日目の[坂本](https://qiita.com/sakamoto_koji)です。13日目は@daisuke-senmyouさんの「AWS WAFのマネージドルールを試す」でした。WAFを使って多重にアプリケーションのセキュリティを固めるのは今後さらに一般的に使われると思いますので参考にしたいです。

## **内容について**
今回はActions on Google（Dialogflow）でサービス公開した際に得た知見を少し記載します。
DialogflowでFulfillmentを利用しサーバ側と連携する場合の参考になると思います。
[Dialogflowの仕様はこちら](https://dialogflow.com/docs/fulfillment)

IntentsとEntitiesの各項目とサンプルJSONデータを載せます。


## **Intents、Entitiesのどちらを先に作成すべきか**
Dialogflowの画面をみるとIntentsの設定項目が上にあるので、ついIntentsから設定しようと思ってしまいますが
まずはEntitiesのほうを仮で良いので考えれるパターンを登録しておくのをおすすめします。

関わったサービスの場合だと都道府県から特定の場所を選択するフローのため
次の３種類登録しました。

- 都道府県
- エリア（千葉北、千葉南など）
- 地点

地点数などデータ量の多いEntitiesは
最初から全てを網羅できれば良いですが、難しい場合はまずは少数のサンプルで登録しておき、
構築が進んできた段階でサービス用の完全なものに整備し直すのがおすすめです。
なぜかというと、
IntentsのUser Saysに
Entitiesで登録したのと同じ単語で発話パターンを入れた際、
Dialogflowの機能で自動で紐付けされるからです。

たとえば、Entitiesで「神奈川県」と登録した状態で
IntentsのUser Saysで「神奈川県の情報」と入力すると「神奈川県」の単語が自動でEntitiesと結び付けられます。


## **Intents、Entitiesの変更が反映されるタイミング**
ドキュメントを一通り読みましたが正式な情報は見つけられませんでした。
現在のところ検証環境が用意されてないので、Dialogflow上で変更したものはそのまま本番側に反映されます。 
変更後すぐの反映ではなく５分～１０分ほどしてから反映されるように感じました。

以下にIntentsとEntitiesそれぞれの項目について記載します。


## **Intentsについて**

### **Contexts項目**
会話の順序が決まっているときに直前の会話内容をパラメータとして引き回すときに利用します。
今回の場合、最大で次のように直前の選択肢を維持していく必要があります。
それぞれにContextsを設定して、直前に発話されたパラメータを維持させたまま会話を進められるようにしました。

都道府県
　↓↓↓
エリア（千葉北、千葉南など）
　↓↓↓
地点
　↓↓↓
お気に入りに設定するか否か

データの持ち方によりますが、プログラムの作りによっては都道府県、エリア、地点の全てが無いと
お気に入りの地点登録はできないケースもありますよね。
そんなときに使います。

下記キャプチャにように直前の会話から受け取るContextsと次の会話に受け渡すContextsを定義できます。

|  ![Contexts.png](https://qiita-image-store.s3.amazonaws.com/0/124856/583cabc1-c5f8-8593-b6da-38e4abc3c440.png)

|:-:|



一つのみではなく複数定義できて、保持させる回数も数字で指定できます。


#### **Contextsをサーバ側プログラムで制御する**
サーバからのレスポンスにcontextOutのパラメータを含められます。
（Dialogflowの仕様ページに記載あり）
これを使うとサーバ側で自由にcontextを制御できます。
直前のAction名や発話により受け取ったパラメータ値をレスポンスに含むようにすれば
直前の発話内容をリピートさせたり、エラー制御に活用できます。
サーバにアクセスを受ける都度データベース等にセッション情報を入れて更新する方法もありますが
データベースに余計な負荷をかける必要もないので
contextOutでパラメータを引き回すのが良いかと思います。
lifespanについてもDialogflowの画面上から定義できますが、全てサーバ側で制御してしまったほうが扱いやすいと感じました。


### **User says項目**
ユーザの発話パターンを記載します。
Entitiesの登録が済んでいれば入力すると自動で紐付けされます。

キャプチャは都道府県の入力サンプルです。この例ではパラメータ１つですが、複数登録させることも出来ます。
Entitiesに都道府県が登録されていれば自動で紐づけされます。（キャプチャの黄色いところ）

| ![Entities_Prefectures2.png](https://qiita-image-store.s3.amazonaws.com/0/124856/369f88da-abf7-efe2-7e29-00bcf041bc00.png)
|:-:|



### **Action項目**
定義したAction名はサーバ側にパラメータとして通知されます。
なので、Action名を定義しておき、Action名で動作を別けるのが作りやすいです。


### **Response項目**
Fulfillmentでサーバ連携する場合は記載不要の認識です。
サーバ連携しないIntentsについてだけ、記載します。

設定画面例はこんな感じです。

| ![Response_Fulfillment_GA.png](https://qiita-image-store.s3.amazonaws.com/0/124856/f5c0cb70-2cf5-9208-7c23-9d7ee9aee502.png)
|:-:|

### **Fulfillment項目**
サーバ側で制御したほうが作りやすかったのでほとんどのIntentsでUse webhookをオンにしています。
初期設定されている「Default Welcome Intent」もUse webhookをオンにしてサーバと連携させること可能です。

### **Google Assistant項目**
End conversationのチェックボックスがあるのみです。
Intentsが呼ばれたあと会話を終了させる場合にオンにします。
「会話終了」など終了させる主旨の発話がされた際に使います。

## **Entitiesについて**
サーバに値を通知する場合、「Define synonyms」をオンにします。
その下の入力欄では
「Enter reference value」
「Enter synonym」
の２種類を入力します。

画面例はこんな感じです。

| ![Entities_Prefectures.png](https://qiita-image-store.s3.amazonaws.com/0/124856/aa462b7c-34b0-b6be-1314-66ca984e9781.png)
|:-:|


「Enter reference value」はサーバに通知される値、
「Enter synonym」は発話されるパターンです。
たとえば、
「Enter reference value」を「1」
「Enter synonym」を「北海道、ほっかいどう」
とした場合、「北海道、ほっかいどう」のどれかが発話された場合
「1」というパラメータ値がサーバに伝わります。
※ここで設定したEntitiesはIntentsの「Action」項目で関連付けされます。

SAVEボタンの右側にプルダウンがあって「Switch to raw mode」を選ぶと
JSON、CSVの２通りでも編集、データ投入ができるので
手作業でポチポチ入力しなくてもフォーマットそろえておけば纏めて投入できます。



## **JSONサンプル**
Dialogflow → サーバのリクエストとレスポンスのJSONサンプルです。
伏せ字は「******」にしてあります。

### **Dialogflow → サーバ**
都道府県の発話箇所で「神奈川」と発話したケースです。
Intentsで定義したアクション名が"action"項目、
認識されたパラメータ値が"parameters"項目、
直前の会話で受信したcontextOutが"contexts"項目内の"lastcontext"に入っています。


```
{
    "originalRequest": {
      * 中略 *
    },
    "id": "******",
    "timestamp": "2017-12-13T01:36:51.557Z",
    "lang": "ja",
    "result": {
        "source": "agent",
        "resolvedQuery": "神奈川",
        "speech": "",
        "action": "ActPrefectures",
        "actionIncomplete": false,
        "parameters": {
            "Prefectures": "14"
        },
        "contexts": [{
            "name": "lastcontext",
            "parameters": {
                "Spot": "",
                "Prefectures.original": "神奈川",
                "Prefectures": "14",
                "lastaction": "input.welcome"
            },
            "lifespan": 1
        }, {
      * 中略 *
        }, {
            "name": "contextsprefectures",
            "parameters": {
                "Prefectures.original": "神奈川",
                "Prefectures": "14"
            },
            "lifespan": 5
        }],
        "metadata": {
      * 中略 *
        },
        "fulfillment": {
            "speech": "",
            "messages": [{
                "type": 0,
                "speech": ""
            }]
        },
        "score": 1.0
    },
    "status": {
        "code": 200,
        "errorType": "success",
        "webhookTimedOut": false
    },
    "sessionId": "******"
}

```



### **サーバ → Dialogflow**
[suggestins tip](https://developers.google.com/actions/assistant/responses#suggestion-chip)をレスポンスした例です。前述のcontextOutも出力しています。
構築時にサンプルとなる前例が少なかったので苦労しました。。。


```
{
    "contextOut": [{
        "name": "lastcontext",
        "lifespan": 2,
        "parameters": {
            "lastaction": "ActPrefectures",
            "Spot": ""
        }
    }],
    "messages": [{
        "platform": "google",
        "textToSpeech": "次にエリアを選択してください。鎌倉、藤沢、茅ヶ崎、西湘のどちらですか？",
        "displayText": "次にエリアを選択してください。鎌倉、藤沢、茅ヶ崎、西湘のどちらですか？",
        "type": "simple_response"
    }, {
        "platform": "google",
        "suggestions": [{
            "title": "鎌倉"
        }, {
            "title": "藤沢"
        }, {
            "title": "茅ヶ崎"
        }, {
            "title": "西湘"
        }],
        "type": "suggestion_chips"
    }],
    "responsedate": "2017-12-13 10:36:52",
    "speech": "次にエリアを選択してください。鎌倉、藤沢、茅ヶ崎、西湘のどちらですか？",
    "displayText": "次にエリアを選択してください。鎌倉、藤沢、茅ヶ崎、西湘のどちらですか？"
}

```


## **最後に**
   
   技術情報が全て英語で日本語の情報が少なかったのが苦労しました。サーバ側はPHPでの実装でしたNode.jsで構築しても良かったかなと思いました。
   スマートスピーカー関連は今後面白い分野のひとつになると思いますので動向が楽しみです。

   CYBIRD エンジニア [Advent Calendar](http://qiita.com/advent-calendar/2017/cybird) 明日は、[@keitarou](https://qiita.com/keitarou)さんの「Protocol Buffers動かしてみる」です！
  どのような内容か！！！ 楽しみです!!!