Dialogflowで「燃やせるゴミは月水金」を認識する（composite entity）

[スマートスピーカー Advent Calendar 2018](https://qiita.com/advent-calendar/2018/smart-speaker) 3日目は、Google Home / Google アシスタントのお話です。

Dialogflow をうまく使うと、ユーザーの複雑な発話を上手に認識してくれます。ゴミを出す日を教えてくれる「[ゴミ捨て](https://assistant.google.com/services/a/uid/000000d50e9952cf?hl=ja)」は、ゴミ収集日のルールをユーザーに発話してもらうのですが、composite entity (developer composite) を使うことで「燃やせるゴミは月水金」のような複雑なパターンに対応させることができたのでご紹介します。

# 認識できた発話パターン

「ゴミ捨て」は、ユーザーが自分でゴミ収集日のルールを GoogleHome に登録します。自然に話しかけて登録できないと、使ってもらえません。そこで、次の様な色々な話し方に対応しています。

- 「資源ごみは木曜日」「資源ごみは木」（曜日には別名がある）
- 「燃やせるゴミは月曜日と水曜日と金曜日」（「と」を使って、複数の曜日を指定）
- 「燃やせるゴミは月水金」（別名が3つ連続）
- 「燃やせないゴミは第一第三火曜日」（序数付きの曜日がある。序数部分が2つ連続）
- 「複雑ゴミは第二水曜日と第一第三金曜」（これらの組み合わせ）

# Dialogflow を使いこなす

うまく使いこなせば、割と少ない設定で対応できます。どう認識しているかは下のデモで試せます。PCでご覧ください。

<p data-height="560" data-theme-id="0" data-slug-hash="NEWVOP" data-default-tab="result" data-user="takatama" data-pen-title="Dialogflowで燃やせるゴミは月水金" class="codepen">See the Pen <a href="https://codepen.io/takatama/pen/NEWVOP/">Dialogflowで燃やせるゴミは月水金</a> by Hirokazu Takatama (<a href="https://codepen.io/takatama">@takatama</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

> [Dialogflow Web Demo | Dialogflow](https://dialogflow.com/docs/integrations/web-demo) で web demo を有効にして、web demo の iframe のコードを [CodePen](https://codepen.io) に貼り付けると、Qiita でも表示できるようになります。詳しくは [Qiitaで記事にCodePenが埋め込めるようになりました](https://qiita.com/Qiita/items/edae7417214c8e957f54) をどうぞ。

では、Dialogflow での設定を順を追ってみていきます。

## 別名をつける entity

曜日の別名をそれぞれ定義していきます。
Dialogflow の左ペインから Entities を選び、```day``` entity を作ります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/f9fda4c0-17a8-1e18-b2a9-aa80b78fa518.png)

- Define synonyms をチェックします。別名（同義語）を定義できます。
- Allow automated expansion はチェックしません。チェックすると Dialogflow がここに定義されていない同義語を推測して補ってくれるそうです。今回は、登録した曜日だけを認識させたいのでチェックしません。

次に、登録した ```day``` entity で曜日の別名を認識してくれるかを試してみます。
まず、左ペイン Intents を選び、```add-rule``` intent を作ります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/a34587ff-425b-1853-284f-6899bb01c1b7.png)

Training phrases に ```燃やせるゴミ は 月曜日``` と記述して Enter。※1
```燃やせるゴミ``` をドラッグして、```@sys.any```、
```月曜日``` をドラッグして、先ほど作った entity ```@day``` をそれぞれ選択します。

次に、左上の Try it now の枠に、「燃やせるゴミは月曜日」と入力すると、認識されていることが分かります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/f317fc06-b137-4b47-8bb5-a26c1a608905.png)

※1 Training phrase のパラメーターは、スペースを入れておくといいことがあります。

Actions on Google 利用率向上のための施策 - 高度なアプリ呼び出し方法 と Dialogflow のトレーニング機能
https://developers-jp.googleblog.com/2018/03/exp-imp-invocations.html

```
追記：
Dialogflow の Entity や @sys.* 、Actions SDK の  Entity や $SchemaOrg_* を使用する場合には、それらと固定文字列の間に半角スペースを挿入してください。
（例）　"$Vegetable:Vegetable を使ったレシピを教えて"
```

## 複数の entity を認識する IS LIST

次に、「燃やせるゴミは月曜日と水曜日」のように、複数の entity を認識させてみます。

「燃やせるゴミは月曜日と水曜日」と入力しても、誤認識してしまいます。
「燃やせるゴミは月曜日と」までが any、「水曜日」だけを day として認識しています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/c2cc0ef9-bf4f-ac11-2dbe-9af87a9f80b9.png)

ここで、add-rule intent の Action and parameters セクションで、day の IS LIST にチェックを入れてみます。これで、曜日が複数あっても認識できるようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/d8379665-b2cb-5e92-ce24-97fe85479b7b.png)

実際にやってみると、正しく認識できています。dayは複数認識されているので ```["月曜日","水曜日"]``` のように配列で表現されました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/13bb7575-30ad-369c-ce84-6d80a99eb9c3.png)

別名 x 3連続の「燃やせるゴミは月水金」にも対応しています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/287d7e86-6f92-efb4-874f-05c08e1f0915.png)

やったー！

## もし上手く認識しないときは

左ペイン Training から、上手く認識できなかった会話のゴミ箱を押してから、右上のAPPROVEボタンを押してください。Dialogflow の学習がクリアされるので、次は正しく認識されるはずです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/dc05727a-371d-c910-eedc-6da22b3c397e.png)

## 2種類の entity を認識させる

続いて、第一火曜日、のように序数付きの曜日を扱えるようにしていきます。

- 曜日の序数は第一から第五
- 同じ序数でも、第一（漢数字）、第1（英数字）、第１（全角数字）の別名がある

先程定義した曜日 ```day``` を拡張するやり方だと、5つ序数 x 3つの別名 x 7つ曜日 = 105 パターンを定義するはめになります。
さすがにそれは辛いので、曜日の他に、序数を entity として定義し、それを組み合わせて使うことにします。
そこで、序数を表す order entity を定義しておきます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/fdf1854b-f721-720f-54be-74dffd574ec9.png)

### intent に複数の entity を書く

「燃やせないゴミは第一第三火曜日」を認識する intent は次のようになります。

![20181101125201top-1.png](https://qiita-image-store.s3.amazonaws.com/0/6760/71b91334-1329-34c1-1322-a55ab87da0a9.png)

Action and parameters で ```order``` の IS LIST にチェックを入れることで、連続する「第一第三」をうまく認識しています。

![20181101124521top-1.png](https://qiita-image-store.s3.amazonaws.com/0/6760/4c79e275-856f-7556-455c-68dc2a0f1045.png)

しかし、「複雑ゴミは第二水曜日と第一第三金曜」はうまく認識できません。

### composite entity で複数の entity を組み合わせる

そこで、序数 ```order``` と、曜日 ```day``` を直接 intent に書く方法をやめて、それぞれを組み合わせた [composite entity](https://dialogflow.com/docs/entities/developer-entities#developer_composite) （複合エンティティ）を作って対応することにします。

新しく作る entity は、その月の n 番目の曜日、ということで、```nthDay``` とします。
「第一火曜日」のような序数がある場合だけでなく、序数がない「火曜日」も受け付けられるように定義します。

composite entity を定義するときには、Define synonyms のチェックは外します。

「火曜日」は ```@day:day```
「第一火曜日」は ```@order:order @day:day```
「第一第二火曜日」は ```@order:order @order:order @day:day```
...
がそれぞれ相当します。

![20181101011040top-1.png](https://qiita-image-store.s3.amazonaws.com/0/6760/38a97b16-b368-ede3-0137-801b6c21c5f5.png)

nthDay entity を定義できたら、add-rule intent が nthDay を使うように変更します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/5bd63e0f-4a13-6b9a-2ab4-cebecd9104f6.png)

nthDay は IS LIST にしているので、「燃やせないゴミは第一火曜日」だけでなく、「燃やせないゴミは第一火曜日と第三火曜日」にも対応できるようになりました。

![20181101010420top-1.png](https://qiita-image-store.s3.amazonaws.com/0/6760/a7e8c05b-9b01-89b0-7e52-942ff6991d1a.png)

```[{"day":"水曜日",order:"第二"},{"day":"金曜日",order:["第一","第三"}]``` と認識していますね。

もちろん、今まで認識していた「燃やせるゴミは月水金」も認識します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6760/326fa47d-a3ff-54b3-09f6-f4bb3cf49ed2.png)

```[{"day":"月曜日"},{"day":"水曜日"},{"day":"金曜日"}]``` となっていて、order プロパティがなくなっていることに注意してください。

認識結果は次のように、order が無い、order があるが値、order が配列、と異なるものになります。

発話 | 認識結果
---+---
火曜日 | [{"day":"火曜日"}]
第一火曜日 | [{"day":"火曜日","order":"第一"}]
第一第三火曜日 | [{"day":"火曜日","order":["第一","第三"]}]

プログラムで利用するときは、整えてから使うのがおすすめです。

```javascript
const makeDistinct = nthDay => {
  let result = [];
  for (let d of nthDay) {
    if (!d.order) {
      result.push({day: d.day, order: '毎週'});
    } else if (Array.isArray(d.order)) {
      d.order.forEach(o => {
        result.push({d.day, order: o});
      });
    } else {
      result.push(d);
    }
  }
  return result;
};
```

# まとめ
- 1つの intent と、3つの entity、```day```、```order```、```nthDay``` を作りました。
- intent の parameters で IS LIST をチェックすれば、パラメーターをリストとして扱えるので、連続したパラメーターを認識できます。
- ```nthDay``` は、```day``` と ```order```を組み合わせた [composite entity](https://dialogflow.com/docs/entities/developer-entities#developer_composite) です。composit entity をリストとして扱えば、複雑なパラメーターの連続を認識できます。

去年の冬のボーナスで GoogleHome を買ってから一年。いつの間にか10個以上のアクションを作っていました。作るの楽しいですね！使える技術はうまく使いながら、これからも便利な / 面白いアクションをたくさん作っていきたいです。