Azure BOT Service とCognitive Service QnA MakerでFAQのBOTを作る

#概要
こんにちは！吉田です。今回は、マイクロソフトが12月13日にリリースした、質問に対して回答を返してくれる新しいCognitive Service「QnA Maker」と11月にリリースされたAzure BOT Serviceを組み合わせて、とある企業のFAQに対応するチャットボットを作ってみたいと思います。

#事前に準備するもの
* 時間：30分ほど
* Azure サブスクリプション
* 質問・回答が同じページに掲載されているFAQのサイト（例：https://www.microsoft.com/ja-jp/software-download/faq 等）
* Skype アカウント（Microsoft Account）　※もしSkypeで試したい場合のみ必要

#設定手順
##Azure Bot Serviceの作成
1. [Azureポータル](https://portal.azure.com/)へアクセスし、新規(+)から、「Intelligence + analytics」をクリックし、「Bot Service」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/7931f53c-3e84-88b0-6d64-ae3d63755cb3.png)


2. 画面下から新規作成画面が表示されます。サイト名を記入し、場所を選択し、「作成」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/6c3ccbfa-7c8d-ce6e-be5f-9d3669f9316c.png)

3. デプロイが開始されます。１分ほどで完了します。デプロイが完了すると、以下のように表示されます。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/0792de59-2ed3-195a-15de-d0356908a807.png)

4. 新しく作成したBot Serviceをすべてのリソース（または設定したリソースグループ）から選択します。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/d5bf2d4a-7fd7-1cfa-7019-832d4d54d40a.png)

5. 開くと、Microsoft App IDの作成が求められます。「Create Microsoft App ID and Password」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/2811bb96-f121-c9fb-b8f9-603fde008c72.png)

6. アプリ名は(1)で設定したアプリ名、アプリIDは自動で生成されます。「アプリパスワードを生成して続行」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/6a3c216d-6807-ad5c-593e-cbc763051ead.png)

7. パスワードが表示されます。表示されたパスワードをメモ帳等のテキストエディタへ貼り付けて、「OK」をクリックします。パスワードは後程利用します。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/7eb6da2d-ce0f-9ae1-00e6-0502e6decc20.png)
8. 「終了してボットのフレームワークに戻る」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/533c9f0d-283e-e4cb-b48e-bdd9a0d9c680.png)

9. 先ほど表示されていた、App IDとメモ帳へ貼り付けていたパスワードを入力します。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/027f526a-1949-c091-3af8-334de3e723ca.png)

10. 次に言語を選びます。今回はC#で作りますので、「C#」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/38ff3821-96e8-40af-7618-197702250dda.png)

11. [前回の投稿](http://qiita.com/taiki_yoshida/items/0be69dfa1db272383bf9)にはなかった、「Question and Answer」が選択できるようになっています！これを選択し、「Create bot」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/af8fe3ea-89b2-f7ac-ac63-341f845f6f2e.png)

12. ウィザードが立ち上がります。QnA MakerはBot Serviceとは別のサービスなのですが、ここから作成すると、ウィザードが自動的にアカウントを作成してくれます。「Sign In」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/8a67422f-f2e1-43e2-0507-af4e81cf8d60.png)

13. サインインが求められます。Microsoft Accountのものを選択しましょう。別のにすることも可能です。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/9f40c856-d00c-dc4b-75c9-7bc6c64b79f0.png)

14. 利用許可の確認画面が出ます。「はい」をクリックしましょう。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/88130287-3038-8fc1-e11d-ee5c2788101b.png)

15. 「I agree...」のところにチェックを入れて、「OK」をクリックします。OKをクリックしてから次の画面に移るまで10秒ぐらいあり、「あれ？反応しない？」っと最初は焦りましたが、待っていれば大丈夫（のはず）です。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/c67556a1-e9aa-f088-b8be-ce50da03cd72.png)

16. BOTの作成がはじまります。2分ぐらいこの画面が表示されるので、待ちましょう。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/3d770ceb-7a28-8e23-d8a9-6a2c9197559e.png)

17. 次に「run.csx」のプログラムを以下のコードで上書きしてしまいます。ナレッジベースIDとキーの取得は、事前にQnA Makerで「Publish」をした後に表示されたものを利用します。
※本来はコードを書き換えなくてもそのまま利用できるはずなのですが、試したところ、日本語だと返ってくるメッセージが文字化けしてしまったので、暫定的にこの方法にしています。（修正されたら、この記事にも反映します）

```CSharp:run.csx
#r "Newtonsoft.Json"
#load "BasicQnAMakerDialog.csx"

using System;
using System.Net;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using Microsoft.Bot.Builder.Azure;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

public static async Task<object> Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info($"Webhook was triggered!");

    // Initialize the azure bot
    using (BotService.Initialize())
    {
        // Deserialize the incoming activity
        string jsonContent = await req.Content.ReadAsStringAsync();
        var activity = JsonConvert.DeserializeObject<Activity>(jsonContent);

        // authenticate incoming request and add activity.ServiceUrl to MicrosoftAppCredentials.TrustedHostNames
        // if request is authenticated
        if (!await BotService.Authenticator.TryAuthenticateAsync(req, new[] { activity }, CancellationToken.None))
        {
            return BotAuthenticator.GenerateUnauthorizedResponse(req);
        }

        if (activity != null)
        {
            // one of these will have an interface and process it
            switch (activity.GetActivityType())
            {
                case ActivityTypes.Message:
                    //await Conversation.SendAsync(activity, () => new BasicQnAMakerDialog());
                    
                    //ナレッジベースIDとキーの取得は、事前にQnA Makerで「Publish」をした後に表示されたものを利用します。
                    var knowledgebaseId = "ここにナレッジベースIDを入力してください"; //Utils.GetAppSetting("QnASubscriptionKey"); // Use knowledge base id created.
                    var qnamakerSubscriptionKey = "ここにサブスクリプションキーを入力してください"; //Utils.GetAppSetting("QnAKnowledgebaseId"); //Use subscription key assigned to you.
 
                    //Build the URI
                    Uri qnamakerUriBase = new Uri("https://westus.api.cognitive.microsoft.com/qnamaker/v1.0");
                    var builder = new UriBuilder($"{qnamakerUriBase}/knowledgebases/{knowledgebaseId}/generateAnswer");

                    //Add the question as part of the body
                    var postBody = $"{{\"question\": \"{activity.Text}\"}}";
                    //QnAMakerResult qnaresponse = new QnAMakerResult();
                    String qnaresponse = null;
                    //Send the POST request
                    using (WebClient webclient = new WebClient())
                
                    {
                        //Set the encoding to UTF8
                        webclient.Encoding = System.Text.Encoding.UTF8;

                        //Add the subscription key header
                        webclient.Headers.Add("Ocp-Apim-Subscription-Key", qnamakerSubscriptionKey);
                        webclient.Headers.Add("Content-Type", "application/json");
                        log.Info($"Requesting QnA...");
                        var qnaresponseString = webclient.UploadString(builder.Uri, postBody);
                        //qnaresponse = JsonConvert.DeserializeObject< QnAMakerResult >(qnaresponseString);
                        qnaresponse = JObject.Parse(qnaresponseString)["answer"].ToString();
                    }
                    
                    var qnaclient = new ConnectorClient(new Uri(activity.ServiceUrl));
                    var qnareply = activity.CreateReply();
                    //qnareply.Text = qnaresponse.Answer.ToString();
                    qnareply.Text = qnaresponse;
                    await qnaclient.Conversations.ReplyToActivityAsync(qnareply);
                    break;
                    
                case ActivityTypes.ConversationUpdate:
                    var client = new ConnectorClient(new Uri(activity.ServiceUrl));
                    IConversationUpdateActivity update = activity;
                    if (update.MembersAdded.Any())
                    {
                        var reply = activity.CreateReply();
                        var newMembers = update.MembersAdded?.Where(t => t.Id != activity.Recipient.Id);
                        foreach (var newMember in newMembers)
                        {
                            reply.Text = "Welcome";
                            if (!string.IsNullOrEmpty(newMember.Name))
                            {
                                reply.Text += $" {newMember.Name}";
                            }
                            reply.Text += "!";
                            await client.Conversations.ReplyToActivityAsync(reply);
                        }
                    }
                    break;
                case ActivityTypes.ContactRelationUpdate:
                case ActivityTypes.Typing:
                case ActivityTypes.DeleteUserData:
                case ActivityTypes.Ping:
                default:
                    log.Error($"Unknown activity type ignored: {activity.GetActivityType()}");
                    break;
            }
        }
        return req.CreateResponse(HttpStatusCode.Accepted);
    }
}
```

17. この画面が表示されたら、Bot Serviceの作成は完了です。次に質問集を登録していきます。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/3b91a880-4824-7e62-3d9c-0f059a5d6ee9.png)

##質問と回答を登録する
Bot Serviceは出来上がりましたが、このままでは質問と回答が登録されていません。このステップでは、事前に用意しておいた質問と回答を登録し、Botが答えられるようにします。

1. QnA Makerのサイト(https://qnamaker.ai) へアクセスし、「My services」を選択します。ログインされていない場合は、先ほどの13の手順で利用していたMicrosoft Accountでログインしてください。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/ea670625-47d5-7056-b220-94a4ffe74741.png)

2. 「I agree...」のところにチェックを入れて、「OK」をクリックします。（2回目作成時からは表示されません）
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/f9b63e8d-1726-c9b9-9ad2-46da9b9de507.png)

3. 先ほどAzure Bot Serviceで登録したQnA Makerが表示されています。鉛筆マークをクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/69a33a37-777b-a930-abd8-d1c140527c0b.png)

4. 質問・回答集が表示されます。既定では「hi」という質問に対し、「hello」と返答することだけが登録されているので、これから編集します。「Settings」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/12836ad7-a877-bf58-47f6-fb360c8a6db2.png)

5. 手動で登録する方法もありますが、QnA Makerには、URLを指定するだけで自動で質問回答集を作成してくれる、非常に便利な機能が用意されています。今回はStarbucks Coffee JapanさんのFAQ（http://www.starbucks.co.jp/faq/products/） を試してみたいと思います。（スタバさん、勝手に使ってすみません…）
このように、質問と回答が同じページでないと、QnA Makerは自動で認識できないので、要注意です。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/80f9cf04-51b7-e690-71fa-6f1efef77f05.png)

6. URLを入力したら、「Save and retrain」をクリックし、「Knowledge Base」へ戻ります。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/77b2d722-268e-3043-812b-b999bf9238fc.png)

7. FAQの内容が登録されたことが確認できました。「Test」をクリックして、試してみましょう。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/f9680b90-22b2-0cff-fa6d-90d6a7eda469.png)

8. 試しに質問を送信してみます。回答がでましたね。次に「Publish」をクリックし、この内容を発行します。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/23867cad-02f5-1f53-2241-2453409528a4.png)

9. 確認画面が表示されるので、「Publish」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/f036f8ba-cf41-541d-63bf-f65a1b803309.png)

10. この画面が表示されたらQnA Makerの設定は完了です。Azure Portalから作成したAzure Bot Serviceへ戻ります。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/a4cfac45-3430-239d-8737-010971162dec.png)

##Skype BOTを作成する
FAQの登録が完了したので、Skypeで試してみます。Skypeでのテストは、Azure Bot Serviceの画面からBOTの連絡先が追加できるようになっています。

1. 「Channels」タブから「Add to Skype」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/9f54ece4-9e59-5346-6a2a-626859abcafb.png)

2. 「Add to Contacts」をクリックします。
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/7d2b8e06-b082-24f2-7e52-cc9be8ffcab1.png)

3. Skypeが立ち上がりますので、試してみましょう！
![image](https://qiita-image-store.s3.amazonaws.com/0/136660/9644c91a-1a4f-cc6d-ef45-88b6b5066829.png)