Messengers show constant growth of active users. WhatsApp has 1.5 billion active users every month. Facebook – 1.3 billion. Skype – 300 million. Messaging apps are getting bigger and they have surpassed social apps long time ago. Also the increase in time spent on messaging apps is massive. As a result, bot is a very attractive form of communication for end users. They naturally interact on a website, app or your favorite messaging application. In this article I will show how you can create a bot which understands the syntax of your sentence and gives you the reply from the database or from the Internet. With a bit of setup you can easily convert your static FAQ page into a bot which will answer your questions.
Bot Architecture
The number of bot platforms is now very large. For me, as Microsoft specialist, the clear choice is Microsoft Bot Framework. I am also going to use a bit of Artificial Intelligence using Microsoft Cognitive Services:
- LUIS – A machine learning-based service to build natural language into apps, bots, and IoT devices.
- QnA Maker – a question and answer service.
- Bing Web Search – a search service to retrieve the results from the Internet.
This frameworks allow you to easily create a bot in a very short time.
In short, user types a phrase in a chat window. Next, the phrase goes to LUIS which tries to figure out whether it is a greeting or an actual question. If it is a question, LUIS tries to understand the intent and replies with the keyword. Then, the QnA Maker gets the keywords and tries to match it with the answer. If no answer found, then the keyword is routed to Bing Web Search and the public results are returned. Finally, the bot replies to the user – either with the response from the QnA Maker or with the Bing Search results.
How to create a bot
Now, let me go through all the necessary configuration.
1. Configuring LUIS
In LUIS I am going to configure the following elements:
- Intents – An intent represents actions the user wants to perform. In our case we are going to have two intents: Greeting and Search.
- Utterances – An utterance is text input from the user that your app needs to understand. It may be a sentence, like “What is Digital Experience”, “Tell me more about Digital Experience”, “I want to know more about Digital Experience”, etc.
Entities – An entity represents detailed information that is relevant in the utterance. For example, in the utterance “What is Digital Experience”, “Digital Experience” is a keyword. By recognizing and labeling the entities that are mentioned in the user’s utterance, LUIS helps you choose the specific action to take to answer a user’s request.
Here are my LUIS intents:
In the greeting configuration I added the following utterances: “hi hello yo greetings hey”. This will allow LUIS to recognize the following words as a greeting and as a result it will reply appropriately.
For search I considered the following utterances:
- what is Keyword
- i want to know more about Keyword
- tell me more Keyword
- who is Keyword
- i want to learn more about Keyword
- tell me more Keyword
Here the Keyword is my simple Entity. I am sure you can think of dozens of different phrases for asking bot a question but a beauty of LUIS is that you can easily train it so it can get better and better.
2. Configuring QnA Maker
The QnA Maker configuration is pretty straightforward. It can be easily populated by adding a link to the FAQ page or uploading a Word or PDF document with question and answers.
For the purpose of the demo I only added 3 items to QnA Maker service:
3. Configuring other services
I created a Bing Web Search on my Azure Portal. There is no project specific configuration for this service.
Additionally, I created the Bot in Azure Portal. You just need to create the bot and grab the keys and ids.
4. Coding the bot
The whole project is written in C#. I used the standard Visual Studio bot template as a starting point.
First of all, let’s modify the Post activity:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public async Task<HttpResponseMessage> Post([FromBody]Activity activity) { if (activity.Type == ActivityTypes.Message) { await Conversation.SendAsync(activity, () => new Dialogs.RootDialog()); } else { HandleSystemMessage(activity); } var response = Request.CreateResponse(HttpStatusCode.OK); return response; } |
Next, let’s look at the RootDialog class:
1 2 3 4 5 6 7 8 9 10 11 |
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result) { // Show visual clue to the user await context.ShowBotBusy(); var activity = await result as Activity; // A class for getting ModelId and SubsriptionKey from the config file var luisModelInfo = ConfigurationService.GetLUISModelInfo(); var luisDialog = new Dialogs.RootLuisDialog(luisModelInfo.ModelId, luisModelInfo.SubscriptionKey); if (!(luisDialog is null)) await context.Forward(luisDialog, AfterMessageReceivedLUISAsync, activity); } |
Here I am getting the LUIS configuration from the config file and forwarding to the LuisDialog.
The LuisDialog class looks as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
[Serializable] public class RootLuisDialog : LuisDialog<object> { public RootLuisDialog(string ModelId, string SubscriptionKey) : base(GetLuisService(ModelId, SubscriptionKey)) { } private static ILuisService GetLuisService(string ModelId, string SubscriptionKey) { var luisModel = new LuisModelAttribute(ModelId, SubscriptionKey, LuisApiVersion.V2, "westus.api.cognitive.microsoft.com"); ILuisService luisService = new LuisService(luisModel); return luisService; } [LuisIntent("")] [LuisIntent("None")] [LuisIntent("Search")] public async Task Search(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result) { try { var messageActivity = await activity; //Hand over to QnAMaker Service, based on the user topic/ language var qnaModelInfo = ConfigurationService.GetQnAModelInfo("Atos", "en-us"); var qnadialog = new QnADialog( qnaModelInfo.Topic, qnaModelInfo.AppSubscriptionKey, qnaModelInfo.AppId, string.Format("Sorry, I couldn't find any specifc answer related to the topic {0}. Please try asking another question", qnaModelInfo.Topic), 0, 3 ); // Forward the request to the QnA Dialog if (!(qnadialog is null)) await context.Forward(qnadialog, AfterMessageReceivedQnAAsync, messageActivity, CancellationToken.None); } catch (Exception ex) { } } [LuisIntent("Greeting")] public async Task Hello(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result) { try { var messageActivity = await activity; var returnMessage = context.MakeMessage(); returnMessage.Text = "Hi there. What would you like to know about Atos Consulting?"; returnMessage.Speak = "Hi there. What would you like to know about Atos Consulting?"; await context.PostAsync(returnMessage); // Remove the Dialog from the stack //context.Done<object>(null); } catch (Exception ex) { } } } |
I created two main methods: Search and Greeting which will be called based on the intent recognized by LUIS.
The Greeting is very straightforward – it just replies to you with the hard-coded welcome message. You could add here some sample queries and describe the general bot idea and usage.
In the Search method I am configuring the QnA Maker service and forwarding to the QnADialog.
The QnADialog looks as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
[Serializable] public class QnADialog : QnAMakerDialog { private string _topic; public QnADialog(string topic, string subscriptionKey, string knowledgeBaseId, string defaultMessage = null, double scoreThreshhold = 0.3, int top = 1) : base(CreateQnAService(subscriptionKey, knowledgeBaseId, defaultMessage, scoreThreshhold, top)) { this._topic = topic; } private static IQnAService CreateQnAService(string subscriptionKey, string knowledgeBaseId, string defaultMessage = null, double scoreThreshhold = 0.3, int top = 1) { var qnaAttribute = new QnAMakerAttribute(subscriptionKey, knowledgeBaseId, defaultMessage, scoreThreshhold, top); IQnAService qnAService = new QnAMakerService(qnaAttribute); return qnAService; } //// Override to also include the knowledgebase question with the answer on confident matches protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults qnaMakerResults) { string language = "en-us"; if (qnaMakerResults.Answers.Count > 0 && qnaMakerResults.Answers.FirstOrDefault().Score >= 0.5) { await PostReplyToUser(context, qnaMakerResults); } else { var userFeedbackMessage = string.Format("I know nothing about {0}. Let me goog... err, bing it.", message.Text); // Post the No answer found message to the user await context.SayAsync(userFeedbackMessage, userFeedbackMessage); // Show generic Help (Bing Search) await BingSearchService.DisplaySearchResults(((Activity)context.Activity).Text, language, context); } } private async Task PostReplyToUser(IDialogContext context, QnAMakerResults qnaMakerResults) { var returnMessage = context.MakeMessage(); var qnaReply = HttpUtility.HtmlDecode(qnaMakerResults.Answers.FirstOrDefault().Answer); // Check to see if any video content exists, if so, add as attachement returnMessage.Text = qnaReply; returnMessage.Speak = qnaReply; await context.PostAsync(returnMessage); } } |
I added a reference to Microsoft.Bot.Builder.CognitiveServices.QnAMaker. Therefore, I can override the ‘RespondFromQnAMakerResultAsync’ method (line 20) and customize that as needed. If the response with the score high enough (0.8) is found, then post it to user. If not, use Bing Web Search Service.
Here is how I created Bing Web Search Service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
public class BingSearchService { private const string BingSearchURL = "https://api.cognitive.microsoft.com/bing/v7.0/search?q='{0}'&mkt='{1}'&count=5&safesearch=Moderate"; public static async Task DisplaySearchResults(string searchQuery, string language, IDialogContext context) { List<GenericSearchResult> bingSearchResults = new List<GenericSearchResult>(); // Create the Search URL string searchURL = string.Format(BingSearchURL, searchQuery, language); try { // Execute the HTTP Request to get the results var searchResults = await WebAPIBaseService.ExecuteWebAPIRequest(HttpMethod.Get, searchURL, "Ocp-Apim-Subscription-Key", "[Paste your bing service key here]"); // Retrieve the reults if ((searchResults != null) && !string.IsNullOrEmpty(searchResults.ReponseContent)) { JArray searchDetails = JObject.Parse(searchResults.ReponseContent.ToString())["webPages"]["value"] as JArray; if (searchDetails != null) bingSearchResults = searchDetails.ToObject<List<GenericSearchResult>>(); } if (bingSearchResults.Count() > 0) // Check to see if there are any records { var resultMessage = context.MakeMessage(); resultMessage.AttachmentLayout = AttachmentLayoutTypes.Carousel; resultMessage.Attachments = new List<Attachment>(); // Loop through the resultset and create the cards for (int indx = 0; indx < bingSearchResults.Count; indx++) { var bingSearchResult = bingSearchResults[indx]; HeroCard heroCard = new HeroCard() { Title = bingSearchResult.name, Subtitle = bingSearchResult.displayUrl, Text = bingSearchResult.snippet, Buttons = new List<CardAction>() { new CardAction() { Title = "Bing results", Type = ActionTypes.OpenUrl, Value = bingSearchResult.url } } }; resultMessage.Attachments.Add(heroCard.ToAttachment()); } await context.SayAsync("Maybe you'll find it useful:", "Maybe these results will help you"); await context.PostAsync(resultMessage); } } catch (Exception ex) { } } } |
In line 14 I am executing the GET request to the service. Then, I display Hero Cards within a Carousel (lines 26-49) back to the user.
Bot in action
Greeting the bot results in the expected answer:
Next, asking a specific question with different syntax returns the answer from QnA Maker:
The cool thing about the Microsoft.Bot.Builder.CognitiveServices.QnAMaker library is that it handles multiple responses. If more than one result from QnA Maker has good score, then the service suggests more specific questions:
Finally, if we ask a question which does not exist in the QnA Maker database, we get Bing results:
Summary
Bots will get more and more popular. Thanks to the Microsoft Bot Framework and Cognitive services you can easily create a bot and a great conversationalist. There are great ideas for Bots in the internet and the list goes on and on.
For further reading about AI I recommend this good article on our blog post: AI and I – Human Artificial Intelligence Relationships.
Happy botting!


Mateusz Orlowski
I have all the SharePoint/Office 365 certificates which gives me super powers.
I read ULS logs in notepad.
I would be an MVP but I have added too many hateful comments on MSDN forum.
Latest posts by Mateusz Orlowski (see all)
- How to create a bot for FAQ in 4 steps - January 18, 2018
- Getting started with SPFx with AngularJS - July 21, 2017