Watson Assistantのスロット機能の使い方

(2018/6/19 Conversation -> Assistant の変更他、最新の情報にUpdate)

# スロット機能とは
チャットボットのよくある使い方として、ユーザーとの対話を通して複数の情報を収集し、必要な情報が集まったら次の処理に進むというものがあります。必要な各々の情報のことを「スロット」と呼び、このスロットを埋めていく（フィリング）ということで、このようなパターンは「スロット・フィリング」と言われます。例えば、レンタカーの予約をチャットボットで自動化する場合、予約の処理をするためには「貸出/返却日時」「店舗」「車両クラス」「オプション」等の情報が必要で、足りない情報をチャットボットがユーザーから聞き出して収集することになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/cf3a053d-3e77-9419-1f4b-ab9740aaf987.png)

Watson Assistant(以降Assistant)のスロット機能は、このようなスロット・フィリングの処理をシンプルに実装できる機能になります。スロット機能を使うと前述のレンタカー予約のユースケースでは例えば以下のような対話が想定されますが、このような複雑なケースでも比較的シンプルに作ることができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/0e2446d2-f0e4-b068-bd29-202951a527ac.png)

ここでは、レンタカー予約を題材にAssistantのスロット機能の使い方を紹介します。

# 事前準備
以下からダウンロードしたJSONファイルをAssistantにインポートしてワークスペースを作成します。
[https://raw.githubusercontent.com/schiyoda/wcs_slot_test/master/wcs_slot_test.json](https://raw.githubusercontent.com/schiyoda/wcs_slot_test/master/wcs_slot_test.json)

インポートする際には<font color="Red">「Intents and Entities」</font>を選択してください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/7399f93b-00b4-95a1-dbf5-4bd60dd6440f.png)

ワークスペースにはテスト用にインテントとエンティティーが作成されています。

- インテント
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/58847732-1527-3029-698c-2ebf0a196347.png)


- エンティティー
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/234eeaca-32b2-9054-a41e-cff0725dca1f.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/11fa74f9-556e-b12f-90fa-a4c864bb59a4.png)


# 基本的なスロット機能の設定
まず、基本的なスロットの動きを理解するために、レンタカー予約をする際に「貸出日付」「貸出時間」「返却日付」「返却時間」「店舗」「車両クラス」の情報を収集するスロットを作成します。

DialogフローにDialogノードを追加し、条件(If bot recognizes)をインテント #予約 に設定します。Dialogノードの設定の右上の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/d2e45cef-3aa1-eac7-a73b-cbaf06d8e352.png)をクリックし、「Slots」を「On」にして「Apply」すると以下のようにスロットの設定項目が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/512ba955-90c9-b5ab-201a-354d53ddce41.png)

次に1つ目のスロットに以下のような設定をします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/b32a28e3-ca4d-7352-7547-c5e589ff6e9b.png)

これはユーザーからの入力文に日付（@sys-date）が含まれていたらコンテキスト変数 $dateFrom に格納する、もし日付に該当するものが含まれていなかったら、「貸出の日付を教えてください。」と返答し、日付の入力を促す、という設定になります。いくつかのスロットを以下のように設定してみます。

| |Check for |Save it as |If not present, ask |
|---|---|---|---|
|1  |@sys-date|$dateFrom|貸出の日付を教えてください。|
|2  |@sys-time|$timeFrom|貸出の時間を教えてください。|
|3  |@sys-date|$dateTo|返却の日付を教えてください。|
|4  |@sys-time|$timeTo|返却の時間を教えてください。|
|5  |@店舗|$shop|貸出の店舗はどちらになりますか？|
|6  |@車両クラス|$class|どの車両クラスにしますか？|

応答文(Then respond with)は以下のように設定します。

```
$dateFrom $timeFrom から $dateTo $timeTo まで $shop で $class のお車を用意いたします。ご予約ありがとうございました。
```

では、テストしてみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/0da204aa-f55f-f1e9-2507-a9dc8fa1c8b6.png)

# 出力のフォーマット
@sys-date、@sys-time は形式を変えてコンテキスト変数に保存することができます。スロットの右側の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/18b4c7b0-7f42-a505-9913-f750a89c5253.png)をクリックし、右上の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/4995c552-1f0d-f4fb-06b3-07cee1dd0558.png)から「Open JSON editor」を選択します。JSON editorで以下のようにフォーマットを変更可能です。

```JavaScript
{
  "context": {
    "dateFrom": "<? @sys-date.reformatDateTime('M月d日') ?>"
  }
}
```

```JavaScript
{
  "context": {
    "timeFrom": "<? @sys-time.reformatDateTime('k:mm') ?>"
  }
}
```


# １度の返答で複数のスロットの値を確認する
先のやり方では個々のスロットを1つずつ順番に聞いているため、対話としては不自然です。このような時のために、スロットが1つも埋まっていない時に複数のスロットの値を１度に聞くことができるオプションがあります。Dialogノードの![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/d2e45cef-3aa1-eac7-a73b-cbaf06d8e352.png)をクリックし、「Prompt for everything」をチェックして「Apply」すると「If no slots are pre-filled, ask this first:」が表示されるので、以下のように設定します。

```
ご希望の貸出日時、返却日時、貸出店舗、車両クラスを教えてください。
```

では、テストしてみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/2529ec74-da56-a503-f937-52514d02d2a6.png)

#1つのスロットで複数の値を保持する
例えば、レンタカーに付けるオプションはユーザーが複数指定する可能性がありますが、この複数の値を1つのスロットで収集することができます。複数の値をコンテキスト変数に配列として保持するためには、スロットを以下のように作成します。

| |Check for |Save it as |If not present, ask |
|---|---|---|---|
|7  |@オプション.values|$options|追加オプションはございますか？|

ユーザーの発話にエンティティー「@オプション」にマッチする文言（例えば「カーナビ」と「チャイルドシート」）が含まれていた場合、これらの値がコンテキスト変数 \$options に配列としてセットされます。配列の値は例えば <? $options.join('、') ?> のように参照可能です。

#スロットからの追加応答
ユーザー入力に必要な情報が含まれていなかった場合、Assistantはスロットからの返答で必要な情報の入力を促します。次のユーザー入力で必要な情報が含まれていた、あるいは含まれていなかった各々の場合について追加応答を設定することができます。スロットの右側の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/18b4c7b0-7f42-a505-9913-f750a89c5253.png)をクリックしてスロットの詳細設定画面を開きます。「Found」「Not Found」に追加応答文を入力します。ここではより複雑な条件を指定するために画面右上の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/4995c552-1f0d-f4fb-06b3-07cee1dd0558.png)から「Enable conditional responses」を選択します。

### 「Found」の設定（ユーザー入力に追加オプションが含まれていた場合）
右側の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/18b4c7b0-7f42-a505-9913-f750a89c5253.png)をクリックして追加応答の詳細設定画面を開きます。条件（If bot recognizes）を「true」、応答（Then respond with）は JSON editor で以下のように設定します。

```JavaScript
{
  "output": {
    "text": {
      "values": []
    }
  },
  "context": {
    "optionsPhrase": "オプションで<? $options.join('、') ?>を追加します。"
  }
}
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/211968b8-383e-7e08-b338-429d87061b76.png)

>ここでは応答文をコンテキスト変数 $optionsPhrase に保持し、この値をDialogノードの応答文に設定します。

### 「Not Found」の設定（ユーザー入力に追加オプションが含まれていなかった場合）
同じように追加応答の詳細設定画面を開き、条件（If bot recognizes）を「true」、応答（Then respond with）は以下のように入力します。

```
追加オプション無しで承ります。
```

このスロットを無視するために「And finally」で「Skip this slot」を選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/83058c12-e6bc-e819-6d1a-29582cb4be95.png)

Dialogノードの応答文に $optionsPhrase を追加し、以下のように設定します。

```
$dateFrom $timeFrom から $dateTo $timeTo まで $shop で $class のお車を用意いたします。 $optionsPhrase ご予約ありがとうございました。
```

テストしてみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/c2649fca-fe38-1ce0-4041-dcec0362e4ab.png)


#オプショナルのスロットを追加する
これまで設定したスロットは「Type」が「Required」となっており、スロットの値が埋まらないと、先の処理に進むことができませんでした。スロットからの返答（If not present, ask）を以下のようにブランクにするとそのスロットは「Optional」となります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/29a914b2-da45-6bdd-0f49-d698fc66d219.png)


このケースではユーザーの発話にエンティティー「@返却方法」にマッチする文言（例えば「乗り捨て」）が含まれていた場合のみスロットが有効になり、コンテキスト変数 $returnType に値がセットされます。

#条件付きスロットを作成する
特定の条件を満たした場合のみ有効になるように、条件付きのスロットを作成することもできます。ここでは先のスロットで $returnType に値がセットされた場合のみ「返却店舗」を確認するスロットを作成します。

まず、新規スロットを以下のように作成します。

| |Check for |Save it as |If not present, ask |
|---|---|---|---|
|9  |@店舗|$returnShop|返却の店舗はどちらになりますか？|

スロットの右側の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/18b4c7b0-7f42-a505-9913-f750a89c5253.png)をクリックし、右上の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/4995c552-1f0d-f4fb-06b3-07cee1dd0558.png)から「Enable condition」を選択します。「Enable this slot if:」が表示されるので、「$returnType」と入力します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/9344429f-e145-26e2-402d-cde01bb2f046.png)

>必須ではないですが、ここで応答用のメッセージを作成します。右上の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/4995c552-1f0d-f4fb-06b3-07cee1dd0558.png)から「Open JSON editor」を選択し、以下のようにコンテキスト変数 $returnPhrase に応答用のメッセージをセットします。

```JavaScript
{
  "context": {
    "returnShop": "@店舗",
    "returnPhrase": "返却は<? $returnShop ?>で<? $returnType ?>で承ります。"
  }
}
```

「Save」をクリックすると、スロットが「Conditional」となっていることが確認できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/b96fbe5f-7e51-8ecc-ef80-1a5fcb95537c.png)]

Dialogノードの応答文に $returnPhrase を追加し、以下のように設定します。

```
$dateFrom $timeFrom から $dateTo $timeTo まで $shop で $class のお車を用意いたします。 $optionsPhrase $returnPhrase ご予約ありがとうございました。
```

#最終確認用のスロットを作成する
全てのスロットの値が収集できたら、収集した値が正しいか最後にユーザーに確認するためのスロットを追加することができます。

まず、新規スロットを以下のように作成します。

| |Check for |Save it as |If not present, ask |
|---|---|---|---|
|10  |(#はい &#124;&#124; #いいえ) && slot_in_focus|$confirmation|\$dateFrom \$timeFrom から \$dateTo \$timeTo まで \$shop で \$class のお車を用意いたします。\$optionsPhrase \$returnPhrase よろしいですか？|

> 「slot_in_focus」を指定すると、最終確認用のスロットからの応答（今回の場合は「〜〜〜。よろしいですか？」）があった場合のみこのスロットの条件を評価します。最終確認用のスロットに到達する前に、例えば「<font color="Red">いいえ</font>、追加オプションは必要ありません。」等の発言をこのスロットで評価しないようにするためです。

スロットの右側の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/18b4c7b0-7f42-a505-9913-f750a89c5253.png)をクリックし詳細設定画面を開きます。

### 「Not Found」の設定（ユーザー入力が「#はい」「#いいえ」以外の場合）
応答文に以下を入力します。

```
「はい」か「いいえ」でお答えください。
```

### 「Found」の設定（ユーザー入力が「#はい」「#いいえ」の場合）
右上の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/4995c552-1f0d-f4fb-06b3-07cee1dd0558.png)から「Enable conditional responses」を選択します。条件（If bot recognizes）を「#いいえ」とし、応答文（Respond with）に以下を入力することで、処理を初めから行ってもらいます。

```
初めからご予約をお願いします。
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/d0f33c70-f391-f82b-d694-48bccb0e3b16.png)

処理状態を初期化するために、「Found」の![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/18b4c7b0-7f42-a505-9913-f750a89c5253.png)をクリックし、JSON editor で全てのコンテキスト変数を null に設定します。

```JavaScript
{
  "output": {
    "text": {
      "values": [
        "初めからご予約をお願いします。"
      ]
    }
  },
  "context": {
    "shop": null,
    "class": null,
    "dateTo": null,
    "timeTo": null,
    "options": null,
    "dateFrom": null,
    "timeFrom": null,
    "returnShop": null,
    "returnType": null,
    "confirmation": null
  }
}
```

では、テストしてみます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/7b2a5ce0-efcd-23d7-677d-8e550be531c1.png)

#スコープ外の発言への対応
スロットで収集すべき内容以外の発言に対しては、スロット・ハンドラーで処理することができます。例えば対話の途中でユーザーが「そういえば、キャンセル料金ってどうなってるんだっけ？」のような質問をした場合に、この質問に返答した上で、引き続きスロットの処理を進めることができます。

スロット・ハンドラーを設定するには、Dialogノードの設定画面で ![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/2afbe378-31cf-5c48-864c-4e758e7f0fd9.png) をクリックします。条件（If bot recognizes）に「#キャンセルポリシー確認」、応答（Respond with）を以下のように設定します。

```
ご予約の2日前からキャンセル料金が発生します。
```
ここまでの設定で記事の冒頭に記載した対話フローが実現できたことが確認できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/674a66be-3709-e490-70e2-3ac222a1ef36.png)


#スロットの処理を全てキャンセルする
対話の途中でユーザーが「やっぱり今回はキャンセルします。」というリクエストをしてきた場合も、以下のようにスロット・ハンドラーを追加して対処することができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/225e7730-bd87-bdc1-4507-ea76e1c893e5.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/18b4c7b0-7f42-a505-9913-f750a89c5253.png)をクリックし、JSON editor で全てのコンテキスト変数を null に設定、「And finally」は「Skip to response」に設定します。

```JavaScript
{
  "output": {
    "text": {
      "values": [
        "予約のキャンセルを承りました。"
      ]
    }
  },
  "context": {
    "shop": null,
    "class": null,
    "dateTo": null,
    "timeTo": null,
    "options": null,
    "dateFrom": null,
    "timeFrom": null,
    "returnShop": null,
    "returnType": null,
    "confirmation": null
  }
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/208f1731-cca8-e309-a1c8-7176bd405556.png)


Dialogノードの設定画面で![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/d2e45cef-3aa1-eac7-a73b-cbaf06d8e352.png)をクリックし、「Multiple responses」を「On」にします。Dialogノードの応答を追加し、キャンセルの場合のメッセージを設定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/2391ff6b-0cd6-bdae-87cb-8a927f48d2d6.png)

以下のように対話の途中でキャンセル処理ができるようになりました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/1e0a4fe5-47f7-1ee1-caba-786a2c5c7b13.png)

#コンテキスト変数のリセット
スロットで全ての情報を収集し必要な処理を実行した後は、次のスロットの処理を行うためにコンテキスト変数をクリア（null にする）する必要があります。コンテキスト変数はスロットが定義されたDialogノードの子ノード、あるいは親ノードでクリアする他、アプリケーション側で実装する等、いくつかの方法が考えれます。例えば以下のようにジャンプ先の子ノードのJSON editorでコンテキスト変数をnullにすることができます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/235649/a613166b-d331-9324-36bd-dc96ff947f9b.png)