Slackbotが可愛いすぎると社内で評判になった件(Watson Assistantの育て方)

# SlackBotも社員の一員！
## 弊社で愛されるSlackbot
弊社には、社員に愛されるSlackbotがおります。その名も「加湿さん！」。そもそもの由来は、オフィス環境が乾かない様に、社員に定期的な加湿器への給水をリマインドするものだったのですが、現在は社員の心も潤す存在に進化しています。

どれくらい愛でられているかというと、、、独身男性社員にナンパされてしまうくらいの人気ぶりですw
<img width="681" alt="スクリーンショット 2020-04-21 15.55.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/c11bdc23-ed7c-1a11-b5f8-97be195abc83.png">

また、女性社員からも、それなりに好評の様です。
<img width="380" alt="スクリーンショット 2020-04-21 16.00.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/a6ad3ba7-08a8-db7d-ca00-998a815d6b27.png">

さて、このSlack上での会話ですが、IBM社のWatsonを使って会話を成立させています。
システムのイメージとしてはこの様な感じです。

![スクリーンショット 2020-04-21 16.04.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/8bcffe1b-dc78-a20c-7bd3-0dc02171a6e2.png)


## WatsonとSlackの連携はノンコーディングでできる！
一見、どうやってやるの？となりそうですが、連携自体はノンコーディングでOKです。
@ayatokura さんが、[【Watson×Slack】Watson Assistantを連携させたSlack botを開発する方法 (ノンコーディング編)](https://qiita.com/ayatokura/items/476139e76c73e6c88362)で紹介しているので参考にしてみてください。

今回は、SlackとWatson Assistantが連携している前提で、このbotの**育て方**をご紹介します。

※ここから先の記述は、IBM Cloudのコンソール→Watson Assistantの起動がされている前提です。

## 手始めの一般会話ができるまで
### Content Catalogを使って一般会話の意図を理解できる様にする。
Watson Assistantを起動して、該当のbotを開き、Content Catalogを選択します。ここでは、「よくある一般的なトピック」として一般を選択(Add to Skill)します。
![スクリーンショット 2020-04-21 16.14.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/77e795e7-d82b-73d2-612d-af89aa030607.png)

内容はこんな感じですね。挨拶や、あなたは誰？というIntent[^1]が色々と登録されています。
![スクリーンショット 2020-04-21 16.15.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/be72e8b6-4e99-4a84-7730-c0dec3551dd0.png)

### Dialogで応答を定義する
上記で定義されたIntentに対して、どの様な返答を返すかはあなた次第です。愛されそうな返事を定義しましょう。
Dialogを選択して、Add to Nodeを押します。
![スクリーンショット 2020-04-21 16.21.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/9f353b9d-c9ea-178d-bd52-b4cd15f2abd8.png)
そうすると下記の画面が出てきますので、良い返事を考えていれましょう。ここでは、「こんにちは」など、
挨拶がされたときの返事を定義しています。
![スクリーンショット 2020-04-21 16.24.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/4530e43c-1013-2d48-8839-b8deb593578c.png)

はい。これだけで、挨拶されたら挨拶を返すbotの完成です。同じ要領で、既にContent Catalogで取り込んだIntentへの返事を定義していきましょう。

## ユーザーの目的を汲み取って返事をしてみる
### 「〜をしたよ！だから、褒めて欲しい」という要求に答える(Entityを使う)
ある程度、Intentを使ったやりとりができてきたそんなある日、とある社員が「掃除をしたよ！(だから褒めて)」とbotに要求してきました。この場合、通り一辺倒な褒め言葉では「愛されるbot」にはなれません。「掃除をしたこと」に対する「褒め」を用意しましょう。イメージとしては、こんな感じです。
![スクリーンショット 2020-04-21 16.34.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/b8b40eec-88fd-a22e-cdf7-b3826e38c890.png)

さて、この様に「〜を」というユーザーの「目的語」(この場合は掃除)を捉えるために、Watson AssistantにはEntityという概念があります。

#### Entityを定義する
Entity → My entities から Create entityを押して Entityの定義をします。

![スクリーンショット 2020-04-21 16.37.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/87c0486a-fffe-c173-3a91-90355d5a5874.png)

内容は至って簡単で、今回の「掃除」がキーワードであれば、それを表しそうな言葉を入れていきましょう。

![スクリーンショット 2020-04-21 16.39.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/6819cb8c-4416-beec-d8cd-b75dc777b987.png)

#### Dialogで返答を定義する
これは少し、コツが必要です。

##### 「褒めて欲しい」という意図をまずは判定する。
まずは、「褒めて欲しい」という意図(Inent)を判定します。ただ、この時点では「何に対して」という情報が不足しているため、回答が確定できません。この場合は、回答を定義せずに、次の処理(Skip User Input and evaluate child nodes)とします。下記の様なイメージですね。
![スクリーンショット 2020-04-21 16.42.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/513d7095-d745-d7b4-233b-c5f65f24c4e5.png)

##### Child NodeでEntityを判定して適切な答えを返す
上記のNodeを選択した状態で、Add Child Nodeを押します。
![スクリーンショット 2020-04-21 16.48.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/a4524982-c03b-041e-8a7a-f86e0a05a353.png)
ここでは、既に「褒めて欲しい」というIntentは判定済みですので、Entityだけを判定します。この様なイメージです。
![スクリーンショット 2020-04-21 16.50.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/f9d578c4-86c1-b485-fd58-92bc7dd7bcbf.png)
これで、ユーザが褒めて欲しい対象によって、返答を切り替えることができる様になりました。

## 答えられなかった質問を答えられる様にする。
### IntentやEntityの判定を調整する。
愛されてくる様になると、本当に様々な~~タチの悪い~~呼びかけがbotにされる様になります。こうすると、Intentで判定できなかったり、誤った返答をしてしまうケースが出てきます。これが頻発すると、愛され度も台無しです。
<img width="300" alt="スクリーンショット 2020-04-21 16.56.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/71c6db73-c563-bb5d-3c67-c7946875faa4.png">
ということで、回答をメンテナンスしていきます！

Analytics → User Conversationsで今まで話しかけられた内容に対して、どの様なIntentやEntityと判定したのか見ることができます。
![スクリーンショット 2020-04-21 16.58.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/3952e296-48dc-4d22-82cb-d84453469086.png)

修正するときは、右側の鉛筆マークをクリックして正しいIntentやEntityを定義してあげましょう。ちなみに、どのIntentにも該当しなった場合は「Irrelevant」なります。必要に応じて、新しいIntentを用意してあげましょう。

修正画面はこの様な感じです。非常にシンプルですね。修正後は、Saveをクリックしてください。
![スクリーンショット 2020-04-21 17.03.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/f5b0a81a-032b-0fbf-3884-cb5f10c6d4e7.png)

これを繰り返すことで、どんどんと、Watson Assistantは学習を重ね、ユーザーが喜ぶ返事をどんどんと返すことができる様になります。

### 答えられなかった時のリアクションも意外と大事
ちょっと困った風の返事をしたりすると、人間(というか二次元っぽさ)が出る様で、(弊社では)喜ばれます。
<img width="450" alt="スクリーンショット 2020-04-21 17.06.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/dc038d7a-5891-a1bd-3102-4f39067c120d.png">

この様にするためには、`anything_else`という判定を活用しましょう。これは、どれにも当てはまらなかったら、という意図です。何も定義しないと、IntentやEntityに該当しない質問が来た時に、botが黙った様に感じられてしまうので、判定とセットにして忘れずに定義しましょう。
![スクリーンショット 2020-04-21 17.08.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/155f1872-f84f-23fd-73ad-f50438b2889e.png)

## FaaSを使って、外部とのやりとりをする(天気予報)
また、Watson Assistantは外部サービスとの連携も非常に簡単になりました。天気予報など、予め、返答を定義できない問いかけに対して、非常に有効です。また、この機能を使えばDBへ話しかけられた内容を保管する、なんてこともできちゃいます！
今回は、天気予報を例に書いていきます。アーキテクチャはこの様なイメージです。
![スクリーンショット 2020-04-21 17.14.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/9a3f4239-c90c-c436-6434-c1993a4c3899.png)

### Webhooksを設定する
Options→Webhooksを選択します。ここに呼び出すAPIのURLやAuthorizationヘッダ情報(IDやパスワード)を定義するだけです。

![スクリーンショット 2020-04-21 17.18.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/60a6faf6-a33f-63c5-4266-5a9e7bf6cc19.png)

### 定義したWebhookをDialogから呼び出す
DialogのNodeの右上の歯車マークをクリックして、WebhooksをONにする
![スクリーンショット 2020-04-21 17.21.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/be270a64-f027-6feb-58a8-115d02b9e689.png)

下記の要領で定義をしていきます。

* APIに渡すパラメータを定義します。(今回は天気予報なので、Parameterにはlocationを設定しています。)
* Return variable(戻り値の名前)を設定します。
* Return variableがあった時の返事を定義します。今回は下記の様に定義しました
    * そうですね。。。今日の "@location" の天気は "$webhook_result.response.result.forecast" みたいですよ！
* `anything_else`を使って、呼び出しに失敗した時の定義をします。

![スクリーンショット 2020-04-21 17.23.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/e06c5469-3b10-f08c-9cee-5ae27c98f793.png)

こうすると、呼び出しに成功した時には以下の様な返答が返ってきます。
<img width="817" alt="スクリーンショット 2020-04-21 17.26.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154110/81c62199-73f8-8d67-91d4-28991125fcfe.png">

良い感じですね。以前は、Webhookの呼び出しはJSONで定義しなければ行けなかったのですが、、、本当に簡単になりました。

ちなみに、`Weather Company Data`で天気予報を取得するときは、IBM Cloud Functionsを利用しています。
取得コードは下記の通りです。(Node.js)

```javascript
/**
  *
  * main() このアクションを呼び出すときに実行されます
  *
  * @param Cloud Functions アクションは 1 つのパラメーターを受け入れます。このパラメーターは JSON オブジェクトでなければなりません。
  *
  * @return このアクションの出力。この出力は、JSON オブジェクトでなければなりません。
  *
  */
var request = require('request');

var main = function(params){
    var options = {
        json:true,
        auth:{
            'user':'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
            'password':'XXXXXXXX'
        }
    }

    return new Promise(function(resolve, reject) {
        //名称から緯度と経度を取得。
        options.url = 'https://twcservice.au-syd.mybluemix.net/api/weather/v3/location/search?query=' + encodeURI(params.location) + '&language=ja-JP';
        request.get(options, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                var lat = body.location.latitude[0];
                var lng = body.location.longitude[0];
                //天気予報をゲット！
                options.url = 'https://twcservice.au-syd.mybluemix.net/api/weather/v1/geocode/' + lat + '/' + lng + '/forecast/daily/3day.json?language=ja-JP';
                request.get(options, function (error, response, body){
                    if (!error && response.statusCode == 200) {
                        // narrativeは天気予報を文章で教えてくれる
                        var forecast = body.forecasts[1].narrative;
                        // 音声出力(TextToSpeech)に合わせて、記号をひらがなに変換
                        forecast = forecast.replace(/～/g,"から");
                        forecast = forecast.replace(/-/g,"マイナス");
                        resolve({"forecast":forecast,"statusCode":response.statusCode});
                    }else{
                        reject();
                    }
                })
            } else {
                reject();    
            }
        });
    })
}
```

# まとめ
リモートワークをしていると、ちょっとした雑談相手が欲しい時ってありますよね。無駄に話しかけることができて、成長を感じられるSlackbotは、社内のコミュニケーションの手助けにもなりますし、何よりも機械学習に慣れる良い機会になります！
IBM CloudはLite Planという永年無料のプランがあり、このプランでも十分にSlackbotは楽しめるので、ぜひ、トライしてみてくださいね！


[^1]: Intentとは、その名の通り「意図」を表します。メッセージの送り主がどの様な質問をしてきているのかを判定する材料となります。