Now that summer vacation is over I am starting a new series focused on Microsoft Cognitive Services. Together we will explore all available APIs and how to use them. Today we are checking out the QnA Maker. The QnA Maker is in the Knowledge category and can be used in two ways. You can use it to extract Questions and Answers from unstructured text or you can use it to power a question and answer service from your semi-structured content like FAQ documents or URLs and product manuals. In this tutorial we are going to focus on creating a knowledge base. The knowledge base we are creating can be used with the Microsoft Bot Framework to create bot dialogue or Azure Search.
Prerequisites
You must have a Cognitive Services API account with QnA Maker chosen as your resource. You’ll need a paid subscription key from your new API account in your Azure dashboard. If you do not have an Azure Account you can open a trial. To retrieve your key, select Keys under Resource Management in your dashboard.
Create knowledge base
- Create a new .NET Framework C# console application using Visual Studio 2017
- Install the Newtonsoft.Json NuGet package.
> Install-Package Newtonsoft.Json
- Add the code provided below.
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; namespace QnAMaker { class Program { // Represents the various elements used to create HTTP request URIs // for QnA Maker operations. static string host = "https://westus.api.cognitive.microsoft.com"; static string service = "/qnamaker/v4.0"; static string method = "/knowledgebases/create"; // NOTE: Replace this value with a valid QnA Maker subscription key. static string key = "YOUR SUBSCRIPTION KEY HERE"; /// <summary> /// Defines the data source used to create the knowledge base. /// The data source includes a QnA pair, with metadata, /// the URL for the QnA Maker FAQ article, and /// the URL for the Azure Bot Service FAQ article. /// </summary> static string kb = @" { 'name': 'QnA Maker FAQ', 'qnaList': [ { 'id': 0, 'answer': 'Use the bot framework. Follow this link for more information: https://dev.botframework.com', 'source': 'Custom Editorial', 'questions': [ 'How do I build a bot?' ], 'metadata': [ { 'name': 'category', 'value': 'api' } ] } ], 'urls': [ 'https://docs.microsoft.com/en-in/azure/cognitive-services/qnamaker/faqs', 'https://docs.microsoft.com/en-us/bot-framework/resources-bot-framework-faq' ], 'files': [] } "; /// <summary> /// Represents the HTTP response returned by an HTTP request. /// </summary> public struct Response { public HttpResponseHeaders headers; public string response; public Response(HttpResponseHeaders headers, string response) { this.headers = headers; this.response = response; } } /// <summary> /// Formats and indents JSON for display. /// </summary> /// <param name="s">The JSON to format and indent.</param> /// <returns>A string containing formatted and indented JSON.</returns> static string PrettyPrint(string s) { return JsonConvert.SerializeObject(JsonConvert.DeserializeObject(s), Formatting.Indented); } /// <summary> /// Asynchronously sends a POST HTTP request. /// </summary> /// <param name="uri">The URI of the HTTP request.</param> /// <param name="body">The body of the HTTP request.</param> /// <returns>A <see cref="System.Threading.Tasks.Task{TResult}(QnAMaker.Program.Response)"/> /// object that represents the HTTP response."</returns> async static Task<Response> Post(string uri, string body) { using (var client = new HttpClient()) using (var request = new HttpRequestMessage()) { request.Method = HttpMethod.Post; request.RequestUri = new Uri(uri); request.Content = new StringContent(body, Encoding.UTF8, "application/json"); request.Headers.Add("Ocp-Apim-Subscription-Key", key); var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); return new Response(response.Headers, responseBody); } } /// <summary> /// Asynchronously sends a GET HTTP request. /// </summary> /// <param name="uri">The URI of the HTTP request.</param> /// <returns>A <see cref="System.Threading.Tasks.Task{TResult}(QnAMaker.Program.Response)"/> /// object that represents the HTTP response."</returns> async static Task<Response> Get(string uri) { using (var client = new HttpClient()) using (var request = new HttpRequestMessage()) { request.Method = HttpMethod.Get; request.RequestUri = new Uri(uri); request.Headers.Add("Ocp-Apim-Subscription-Key", key); var response = await client.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); return new Response(response.Headers, responseBody); } } /// <summary> /// Creates a knowledge base. /// </summary> /// <param name="kb">The data source for the knowledge base.</param> /// <returns>A <see cref="System.Threading.Tasks.Task{TResult}(QnAMaker.Program.Response)"/> /// object that represents the HTTP response."</returns> /// <remarks>The method constructs the URI to create a knowledge base in QnA Maker, and then /// asynchronously invokes the <see cref="QnAMaker.Program.Post(string, string)"/> method /// to send the HTTP request.</remarks> async static Task<Response> PostCreateKB(string kb) { // Builds the HTTP request URI. string uri = host + service + method; // Writes the HTTP request URI to the console, for display purposes. Console.WriteLine("Calling " + uri + "."); // Asynchronously invokes the Post(string, string) method, using the // HTTP request URI and the specified data source. return await Post(uri, kb); } /// <summary> /// Gets the status of the specified QnA Maker operation. /// </summary> /// <param name="operation">The QnA Maker operation to check.</param> /// <returns>A <see cref="System.Threading.Tasks.Task{TResult}(QnAMaker.Program.Response)"/> /// object that represents the HTTP response."</returns> /// <remarks>The method constructs the URI to get the status of a QnA Maker operation, and /// then asynchronously invokes the <see cref="QnAMaker.Program.Get(string)"/> method /// to send the HTTP request.</remarks> async static Task<Response> GetStatus(string operation) { // Builds the HTTP request URI. string uri = host + service + operation; // Writes the HTTP request URI to the console, for display purposes. Console.WriteLine("Calling " + uri + "."); // Asynchronously invokes the Get(string) method, using the // HTTP request URI. return await Get(uri); } /// <summary> /// Creates a knowledge base, periodically checking status /// until the knowledge base is created. /// </summary> async static void CreateKB() { try { // Starts the QnA Maker operation to create the knowledge base. var response = await PostCreateKB(kb); // Retrieves the operation ID, so the operation's status can be // checked periodically. var operation = response.headers.GetValues("Location").First(); // Displays the JSON in the HTTP response returned by the // PostCreateKB(string) method. Console.WriteLine(PrettyPrint(response.response)); // Iteratively gets the state of the operation creating the // knowledge base. Once the operation state is set to something other // than "Running" or "NotStarted", the loop ends. var done = false; while (true != done) { // Gets the status of the operation. response = await GetStatus(operation); // Displays the JSON in the HTTP response returned by the // GetStatus(string) method. Console.WriteLine(PrettyPrint(response.response)); // Deserialize the JSON into key-value pairs, to retrieve the // state of the operation. var fields = JsonConvert.DeserializeObject<Dictionary<string, string>>(response.response); // Gets and checks the state of the operation. String state = fields["operationState"]; if (state.CompareTo("Running") == 0 || state.CompareTo("NotStarted") == 0) { // QnA Maker is still creating the knowledge base. The thread is // paused for a number of seconds equal to the Retry-After header value, // and then the loop continues. var wait = response.headers.GetValues("Retry-After").First(); Console.WriteLine("Waiting " + wait + " seconds..."); Thread.Sleep(Int32.Parse(wait) * 1000); } else { // QnA Maker has completed creating the knowledge base. done = true; } } } catch { // An error occurred while creating the knowledge base. Ensure that // you included your QnA Maker subscription key where directed in the sample. Console.WriteLine("An error occurred while creating the knowledge base."); } finally { Console.WriteLine("Press any key to continue."); } } static void Main(string[] args) { // Invoke the CreateKB() method to create a knowledge base, periodically // checking the status of the QnA Maker operation until the // knowledge base is created. CreateKB(); // The console waits for a key to be pressed before closing. Console.ReadLine(); } } }
- Replace the key value with your valid subscription key.
- Run the code
A successful response is returned in JSON. If the final call returns a “Succeeded” state… your knowledge base was created successfully!! To troubleshoot refer to the Get Operation Details of the QnA Maker API.