FAQs Answering Bot Using Bot Framework And QnA Maker service

In previous article, I have explained how to educate QnA maker for questions and answers. In this article we will discuss about how to consume QnA maker service in Bot application created using Microsoft Bot Framework.

I am going to build a bot which will answer FAQs related to LUIS with the help of QnA maker service we have created in previous article.

Prerequisite

  • Download Visual Studio 2015/2017 and update all extensions.
  • Download and install bot application template from here.
  • Download Bot Framework Emulator. The emulator is a desktop application that lets you test a bot application on localhost or running remotely.

Create bot application

Open Visual Studio and create a new C# project. Choose the Bot Application template. If required, also update NuGet Packages installed in project.

Microsoft Bot Framework

Microsoft Bot Framework

Template contains all components required to build a bot as well as initial code setup.

Code

In solution explorer, you will see WebApiConfig.cs file, Controllers and Dialogs folders. 'MessageController' is inherited from 'System.Web.Http.ApiController'. Bot application is nothing but webapis which will be called from chat window to get response.

Microsoft Bot Framework

When user is going to enter query in chat window, 'post( )' method within controller will get called. Then controller will invoke specified dialog, which will process the query and return the result.

For our chat bot, I have added AnswerDialog.cs (see image above) to process questions from user. You can pull code from GitHub repository.

MessageController.cs

Updated code for invoking AnswerDialog instead of default RootDialog. 

  1. public async Task<HttpResponseMessage> Post([FromBody]Activity activity)  
  2. {  
  3.     if (activity.Type == ActivityTypes.Message)  
  4.     {  
  5.         await Conversation.SendAsync(activity, () => new Dialogs.AnswerDialog());  
  6.     }  
  7.     else  
  8.     {  
  9.         HandleSystemMessage(activity);  
  10.     }  
  11.     var response = Request.CreateResponse(HttpStatusCode.OK);  
  12.     return response;  
  13. }   

AnswerDialog.cs 

  1. public class QnAMakerResult  
  2. {  
  3.     [JsonProperty(PropertyName = "answers")]  
  4.     public List<Result> Answers { get; set; }  
  5. }  
  6.   
  7. public class Result  
  8. {  
  9.         [JsonProperty(PropertyName = "answer")]  
  10.         public string Answer { get; set; }  
  11.   
  12.         [JsonProperty(PropertyName = "questions")]  
  13.         public List<string> Questions { get; set; }  
  14.   
  15.         [JsonProperty(PropertyName = "score")]  
  16.         public double Score { get; set; }  
  17. }   
  18.   
  19.   
  20. public Task StartAsync(IDialogContext context)  
  21. {  
  22.     context.Wait(QuestionReceivedAsync);  
  23.   
  24.     return Task.CompletedTask;  
  25. }  
  26.   
  27. private async Task QuestionReceivedAsync(IDialogContext context, IAwaitable<object> result)  
  28. {  
  29.     var activity = await result as Activity;  
  30.   
  31.     await context.PostAsync(GetAnswer(activity.Text));  
  32. }  
  33.   
  34. private string GetAnswer(string query)  
  35. {  
  36.     string responseString = string.Empty;  
  37.   
  38.     var knowledgebaseId = Convert.ToString(ConfigurationManager.AppSettings["KNOWLEDGE_BASE_ID"], CultureInfo.InvariantCulture);  
  39.   
  40.     //Build the URI  
  41.     var builder = new UriBuilder(string.Format(Convert.ToString(ConfigurationManager.AppSettings["QNA_SERVICE_URL"], CultureInfo.InvariantCulture), knowledgebaseId));  
  42.   
  43.     //Add the question as part of the body  
  44.     var postBody = string.Format("{{\"question\": \"{0}\"}}", query);  
  45.   
  46.     //Send the POST request  
  47.     using (WebClient client = new WebClient())  
  48.     {  
  49.         //Set the encoding to UTF8  
  50.         client.Encoding = System.Text.Encoding.UTF8;  
  51.   
  52.         //Add the subscription key header  
  53.         var qnamakerSubscriptionKey = Convert.ToString(ConfigurationManager.AppSettings["SUBSCRIPTION_KEY"], CultureInfo.InvariantCulture);  
  54.         client.Headers.Add("Ocp-Apim-Subscription-Key", qnamakerSubscriptionKey);  
  55.         client.Headers.Add("Content-Type""application/json");  
  56.         responseString = client.UploadString(builder.Uri, postBody);  
  57.     }  
  58.     QnAMakerResult result = JsonConvert.DeserializeObject<QnAMakerResult>(responseString);  
  59.     return result.Answers[0].Answer;  
  60. }   

When controller invokes AnswerDialog, control will come to 'StartAsync( )' method of dialog. It will invoke 'QuestionReceivedAsync( )' method which in sequence calls 'GetAnswer( )' method with user query as parameter and waits for result.

In GetAnswer( ) method, QnA service URL will be consumed to request an answer. We can copy service URL from sample HTTP request given by QnA maker while publishing the service. 

  1. POST /knowledgebases/{{knowledge base id}}/generateAnswer  
  2. Host: https://westus.api.cognitive.microsoft.com/qnamaker/v2.0  
  3. Ocp-Apim-Subscription-Key: {{your subscription key}}  
  4. Content-Type: application/json  
  5. {"question":"hi"}   

I have kept QnA service URL and knowledge based id as well as subscription key which is required to make post call to QnA service, in web.config 

  1. <add key="QNA_SERVICE_URL" value="https://westus.api.cognitive.microsoft.com/qnamaker/v2.0/knowledgebases/{0}/generateAnswer"/>  
  2. <add key="KNOWLEDGE_BASE_ID" value="{{your knowledge base id}}"/>  
  3. <add key="SUBSCRIPTION_KEY" value="{{your subscription key}}"/>   

Service will return result in responseString variable. We will serialize json response to get object of type QnAMakerResult and will return answer property to user.

Test

Hit F5 to test a bot application. It will host application with IIS Express and open browser. To understand the control flow, insert breakpoints in Post( ) method of MessageController and StartAsync( ) method of AnswerDialog.

Microsoft Bot Framework

Now launch bot emulator app. Put URL as 'http://localhost:{{port no. from browser url}}/api/{{controller name}}' to connect emulator with bot application. Keep App ID and App Password blank, click on connect.

Microsoft Bot Framework

Start asking questions to bot and bot will answer you with the help of QnA maker service. Happy chatting! :)

Microsoft Bot Framework

In next post, we will look into how to publish this bot application to different channels like Skype, Web Chat, Bing etc.

Read more on Microsoft Bot Framework