diff --git a/samples/csharp_dotnetcore/11a.qnamaker/AdapterWithErrorHandler.cs b/samples/csharp_dotnetcore/11a.qnamaker/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..da9206a655 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/AdapterWithErrorHandler.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples +{ + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger, ConversationState conversationState = null) + : base(configuration, logger) + { + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Send a message to the user + await turnContext.SendActivityAsync("The bot encounted an error or bug."); + await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); + + if (conversationState != null) + { + try + { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" in a Web pages. + await conversationState.DeleteAsync(turnContext); + } + catch (Exception e) + { + logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); + } + } + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/samples/csharp_dotnetcore/11a.qnamaker/Bots/QnABot.cs b/samples/csharp_dotnetcore/11a.qnamaker/Bots/QnABot.cs new file mode 100644 index 0000000000..4537da24f5 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/Bots/QnABot.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.AI.QnA; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples +{ + public class QnABot : ActivityHandler + { + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + + public QnABot(IConfiguration configuration, ILogger logger, IHttpClientFactory httpClientFactory) + { + _configuration = configuration; + _logger = logger; + _httpClientFactory = httpClientFactory; + } + + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) + { + var httpClient = _httpClientFactory.CreateClient(); + + var qnaMaker = new QnAMaker(new QnAMakerEndpoint + { + KnowledgeBaseId = _configuration["QnAKnowledgebaseId"], + EndpointKey = _configuration["QnAEndpointKey"], + Host = _configuration["QnAEndpointHostName"] + }, + null, + httpClient); + + _logger.LogInformation("Calling QnA Maker"); + + // The actual call to the QnA Maker service. + var response = await qnaMaker.GetAnswersAsync(turnContext); + if (response != null && response.Length > 0) + { + await turnContext.SendActivityAsync(MessageFactory.Text(response[0].Answer), cancellationToken); + } + else + { + await turnContext.SendActivityAsync(MessageFactory.Text("No QnA Maker answers were found."), cancellationToken); + } + } + } +} diff --git a/samples/csharp_dotnetcore/11a.qnamaker/CognitiveModels/smartLightFAQ.tsv b/samples/csharp_dotnetcore/11a.qnamaker/CognitiveModels/smartLightFAQ.tsv new file mode 100644 index 0000000000..754118909e --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/CognitiveModels/smartLightFAQ.tsv @@ -0,0 +1,15 @@ +Question Answer Source Keywords +Question Answer 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Source +My Contoso smart light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +My smart light app stopped responding. Restart the app. If the problem persists, contact support. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +How do I contact support? Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +I need help. Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +I upgraded the app and it doesn't work anymore. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Light doesn't work after upgrade. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Question Answer 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Source +Who should I contact for customer service? Please direct all customer service questions to (202) 555-0164 \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +Why does the light not work? The simplest way to troubleshoot your smart light is to turn it off and on. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +How long does the light's battery last for? The battery will last approximately 10 - 12 weeks with regular use. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +What type of light bulb do I need? A 26-Watt compact fluorescent light bulb that features both energy savings and long-life performance. 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +Hi Hello Editorial \ No newline at end of file diff --git a/samples/csharp_dotnetcore/11a.qnamaker/Controllers/BotController.cs b/samples/csharp_dotnetcore/11a.qnamaker/Controllers/BotController.cs new file mode 100644 index 0000000000..fa4ea81324 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/Controllers/BotController.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Microsoft.BotBuilderSamples +{ + // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter _adapter; + private readonly IBot _bot; + + public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) + { + _adapter = adapter; + _bot = bot; + } + + [HttpPost] + public async Task PostAsync() + { + // Delegate the processing of the HTTP POST to the adapter. + // The adapter will invoke the bot. + await _adapter.ProcessAsync(Request, Response, _bot); + } + } +} diff --git a/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/new-rg-parameters.json b/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/new-rg-parameters.json new file mode 100644 index 0000000000..ead3390932 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/preexisting-rg-parameters.json b/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 0000000000..b6f5114fcc --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/template-with-new-rg.json b/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/template-with-new-rg.json new file mode 100644 index 0000000000..06b8284158 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/template-with-new-rg.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('appServicePlanName')]" + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/template-with-preexisting-rg.json b/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 0000000000..43943b6581 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/DeploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('servicePlanName')]" + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/11a.qnamaker/Program.cs b/samples/csharp_dotnetcore/11a.qnamaker/Program.cs new file mode 100644 index 0000000000..43e91da93e --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/Program.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .ConfigureLogging((logging) => + { + logging.AddDebug(); + logging.AddConsole(); + }) + .UseStartup(); + } +} diff --git a/samples/csharp_dotnetcore/11a.qnamaker/Properties/launchSettings.json b/samples/csharp_dotnetcore/11a.qnamaker/Properties/launchSettings.json new file mode 100644 index 0000000000..9061983f7a --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:3978/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "QnABot": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:3978/" + } + } +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/11a.qnamaker/QnABot.csproj b/samples/csharp_dotnetcore/11a.qnamaker/QnABot.csproj new file mode 100644 index 0000000000..4b7e3aa011 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/QnABot.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + latest + + + + + + + + + + + Always + + + diff --git a/samples/csharp_dotnetcore/11a.qnamaker/README.md b/samples/csharp_dotnetcore/11a.qnamaker/README.md new file mode 100644 index 0000000000..1ee4821fc2 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/README.md @@ -0,0 +1,89 @@ +# QnA Maker + +Bot Framework v4 QnA Maker bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses the [QnA Maker Cognitive AI](https://www.qnamaker.ai) service. + +The [QnA Maker Service](https://www.qnamaker.ai) enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. In this sample, we demonstrate how to use the QnA Maker service to answer questions based on a FAQ text file used as input. + +## Prerequisites + +This samples **requires** prerequisites in order to run. + +### Overview + +This bot uses [QnA Maker Service](https://www.qnamaker.ai), an AI based cognitive service, to implement simple Question and Answer conversational patterns. + +- [.NET Core SDK](https://dotnet.microsoft.com/download) version 2.1 + + ```bash + # determine dotnet version + dotnet --version + ``` + +### Create a QnAMaker Application to enable QnA Knowledge Bases + +QnA knowledge base setup and application configuration steps can be found [here](https://aka.ms/qna-instructions). + +## To try this sample + +- Clone the repository + + ```bash + git clone https://github.com/Microsoft/botbuilder-samples.git + ``` + +- In a terminal, navigate to `samples/csharp_dotnetcore/11.qnamaker` +- Run the bot from a terminal or from Visual Studio, choose option A or B. + + A) From a terminal + + ```bash + # run the bot + dotnet run + ``` + + B) Or from Visual Studio + + - Launch Visual Studio + - File -> Open -> Project/Solution + - Navigate to `samples/csharp_dotnetcore/11.qnamaker` folder + - Select `QnABot.csproj` file + - Press `F5` to run the project + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## QnA Maker service + +QnA Maker enables you to power a question and answer service from your semi-structured content. + +One of the basic requirements in writing your own bot is to seed it with questions and answers. In many cases, the questions and answers already exist in content like FAQ URLs/documents, product manuals, etc. With QnA Maker, users can query your application in a natural, conversational manner. QnA Maker uses machine learning to extract relevant question-answer pairs from your content. It also uses powerful matching and ranking algorithms to provide the best possible match between the user query and the questions. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [QnA Maker Documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/overview/overview) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [QnA Maker CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/packages/QnAMaker) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/csharp_dotnetcore/11a.qnamaker/Startup.cs b/samples/csharp_dotnetcore/11a.qnamaker/Startup.cs new file mode 100644 index 0000000000..b0cbf7b589 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/Startup.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.BotFramework; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Connector.Authentication; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.BotBuilderSamples +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + // Add the HttpClientFactory to be used for the QnAMaker calls. + services.AddHttpClient(); + + // Create the credential provider to be used with the Bot Framework Adapter. + services.AddSingleton(); + + // Create the Bot Framework Adapter with error handling enabled. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseDefaultFiles(); + app.UseStaticFiles(); + + //app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/samples/csharp_dotnetcore/11a.qnamaker/appsettings.Development.json b/samples/csharp_dotnetcore/11a.qnamaker/appsettings.Development.json new file mode 100644 index 0000000000..e203e9407e --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/csharp_dotnetcore/11a.qnamaker/appsettings.json b/samples/csharp_dotnetcore/11a.qnamaker/appsettings.json new file mode 100644 index 0000000000..663430788d --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/appsettings.json @@ -0,0 +1,7 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "QnAKnowledgebaseId": "", + "QnAEndpointKey": "", + "QnAEndpointHostName": "" +} diff --git a/samples/csharp_dotnetcore/11a.qnamaker/wwwroot/default.htm b/samples/csharp_dotnetcore/11a.qnamaker/wwwroot/default.htm new file mode 100644 index 0000000000..bec7fa2be1 --- /dev/null +++ b/samples/csharp_dotnetcore/11a.qnamaker/wwwroot/default.htm @@ -0,0 +1,417 @@ + + + + + + + QnA Maker Sample + + + + + +
+
+
+
QnA Maker Sample
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/AdapterWithErrorHandler.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/AdapterWithErrorHandler.cs new file mode 100644 index 0000000000..da9206a655 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/AdapterWithErrorHandler.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.Bot.Builder.TraceExtensions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples +{ + public class AdapterWithErrorHandler : BotFrameworkHttpAdapter + { + public AdapterWithErrorHandler(IConfiguration configuration, ILogger logger, ConversationState conversationState = null) + : base(configuration, logger) + { + OnTurnError = async (turnContext, exception) => + { + // Log any leaked exception from the application. + logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); + + // Send a message to the user + await turnContext.SendActivityAsync("The bot encounted an error or bug."); + await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); + + if (conversationState != null) + { + try + { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" in a Web pages. + await conversationState.DeleteAsync(turnContext); + } + catch (Exception e) + { + logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); + } + } + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); + }; + } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/BotServices.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/BotServices.cs new file mode 100644 index 0000000000..a0785846f0 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/BotServices.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.AI.QnA; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.BotBuilderSamples +{ + public class BotServices : IBotServices + { + public BotServices(IConfiguration configuration) + { + QnAMakerService = new QnAMaker(new QnAMakerEndpoint + { + KnowledgeBaseId = configuration["QnAKnowledgebaseId"], + EndpointKey = configuration["QnAEndpointKey"], + Host = GetHostname(configuration["QnAEndpointHostName"]) + }); + } + + public QnAMaker QnAMakerService { get; private set; } + + private static string GetHostname(string hostname) + { + if (!hostname.StartsWith("https://")) + { + hostname = string.Concat("https://", hostname); + } + + if (!hostname.EndsWith("/qnamaker")) + { + hostname = string.Concat(hostname, "/qnamaker"); + } + + return hostname; + } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/Bots/QnABot.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/Bots/QnABot.cs new file mode 100644 index 0000000000..aa12698d24 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/Bots/QnABot.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.5.0 + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.Extensions.Logging; + +namespace Microsoft.BotBuilderSamples.Bots +{ + public class QnABot : ActivityHandler where T : Microsoft.Bot.Builder.Dialogs.Dialog + { + protected readonly BotState ConversationState; + protected readonly Microsoft.Bot.Builder.Dialogs.Dialog Dialog; + protected readonly BotState UserState; + + public QnABot(ConversationState conversationState, UserState userState, T dialog) + { + ConversationState = conversationState; + UserState = userState; + Dialog = dialog; + } + + public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default) + { + await base.OnTurnAsync(turnContext, cancellationToken); + + // Save any state changes that might have occured during the turn. + await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken); + await UserState.SaveChangesAsync(turnContext, false, cancellationToken); + } + + protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) => + // Run the Dialog with the new message Activity. + await Dialog.RunAsync(turnContext, ConversationState.CreateProperty(nameof(DialogState)), cancellationToken); + + protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) + { + foreach (var member in membersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + await turnContext.SendActivityAsync(MessageFactory.Text($"Hello and welcome!"), cancellationToken); + } + } + } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/CognitiveModels/smartLightFAQ.tsv b/samples/csharp_dotnetcore/49.qnamaker-all-features/CognitiveModels/smartLightFAQ.tsv new file mode 100644 index 0000000000..754118909e --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/CognitiveModels/smartLightFAQ.tsv @@ -0,0 +1,15 @@ +Question Answer Source Keywords +Question Answer 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Source +My Contoso smart light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +My smart light app stopped responding. Restart the app. If the problem persists, contact support. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +How do I contact support? Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +I need help. Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +I upgraded the app and it doesn't work anymore. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Light doesn't work after upgrade. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Question Answer 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Source +Who should I contact for customer service? Please direct all customer service questions to (202) 555-0164 \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +Why does the light not work? The simplest way to troubleshoot your smart light is to turn it off and on. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +How long does the light's battery last for? The battery will last approximately 10 - 12 weeks with regular use. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +What type of light bulb do I need? A 26-Watt compact fluorescent light bulb that features both energy savings and long-life performance. 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +Hi Hello Editorial \ No newline at end of file diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/Controllers/BotController.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/Controllers/BotController.cs new file mode 100644 index 0000000000..22306a10fc --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/Controllers/BotController.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.5.0 + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; + +namespace Microsoft.BotBuilderSamples.Controllers +{ + // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot + // implementation at runtime. Multiple different IBot implementations running at different endpoints can be + // achieved by specifying a more specific type for the bot constructor argument. + [Route("api/messages")] + [ApiController] + public class BotController : ControllerBase + { + private readonly IBotFrameworkHttpAdapter Adapter; + private readonly IBot Bot; + + public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) + { + Adapter = adapter; + Bot = bot; + } + + [HttpPost] + public async Task PostAsync() + { + // Delegate the processing of the HTTP POST to the adapter. + // The adapter will invoke the bot. + await Adapter.ProcessAsync(Request, Response, Bot); + } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/new-rg-parameters.json b/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/new-rg-parameters.json new file mode 100644 index 0000000000..ead3390932 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/preexisting-rg-parameters.json b/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 0000000000..b6f5114fcc --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/template-with-new-rg.json b/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/template-with-new-rg.json new file mode 100644 index 0000000000..06b8284158 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/template-with-new-rg.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('appServicePlanName')]" + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/template-with-preexisting-rg.json b/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 0000000000..43943b6581 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/DeploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('servicePlanName')]" + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/Dialog/QnAMakerBaseDialog.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/Dialog/QnAMakerBaseDialog.cs new file mode 100644 index 0000000000..890044314a --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/Dialog/QnAMakerBaseDialog.cs @@ -0,0 +1,339 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder.AI.QnA; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Microsoft.BotBuilderSamples.Utils; + +namespace Microsoft.BotBuilderSamples.Dialog +{ + /// + /// QnAMaker action builder class + /// + public class QnAMakerBaseDialog : ComponentDialog + { + // Dialog Options parameters + public const float DefaultThreshold = 0.3F; + public const int DefaultTopN = 3; + public const string DefaultNoAnswer = "No QnAMaker answers found."; + + // Card parameters + public const string DefaultCardTitle = "Did you mean:"; + public const string DefaultCardNoMatchText = "None of the above."; + public const string DefaultCardNoMatchResponse = "Thanks for the feedback."; + + // Define value names for values tracked inside the dialogs. + public const string QnAOptions = "qnaOptions"; + public const string QnADialogResponseOptions = "qnaDialogResponseOptions"; + private const string CurrentQuery = "currentQuery"; + private const string QnAData = "qnaData"; + private const string QnAContextData = "qnaContextData"; + private const string PreviousQnAId = "prevQnAId"; + + /// + /// QnA Maker action builder + /// + private const string QnAMakerDialogName = "qnamaker-dialog"; + private readonly IBotServices _services; + private readonly float maximumScoreForLowScoreVariation = 0.95F; + + /// + /// Initializes a new instance of the class. + /// Dialog helper to generate dialogs. + /// + /// Bot Services. + public QnAMakerBaseDialog(IBotServices services): base(nameof(QnAMakerBaseDialog)) + { + AddDialog(new WaterfallDialog(QnAMakerDialogName) + .AddStep(CallGenerateAnswerAsync) + .AddStep(CallTrain) + .AddStep(CheckForMultiTurnPrompt) + .AddStep(DisplayQnAResult)); + _services = services ?? throw new ArgumentNullException(nameof(services)); + + // The initial child Dialog to run. + InitialDialogId = QnAMakerDialogName; + } + + private static Dictionary GetDialogOptionsValue(DialogContext dialogContext) + { + var dialogOptions = new Dictionary(); + + if (dialogContext.ActiveDialog.State["options"] != null) + { + dialogOptions = dialogContext.ActiveDialog.State["options"] as Dictionary; + } + + return dialogOptions; + } + + private async Task CallGenerateAnswerAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var qnaMakerOptions = new QnAMakerOptions + { + ScoreThreshold = DefaultThreshold, + Top = DefaultTopN, + Context = new QnARequestContext() + }; + + var dialogOptions = GetDialogOptionsValue(stepContext); + + // Getting options + if (dialogOptions.ContainsKey(QnAOptions)) + { + qnaMakerOptions = dialogOptions[QnAOptions] as QnAMakerOptions; + qnaMakerOptions.ScoreThreshold = qnaMakerOptions?.ScoreThreshold ?? DefaultThreshold; + qnaMakerOptions.Top = DefaultTopN; + } + + // Storing the context info + stepContext.Values[CurrentQuery] = stepContext.Context.Activity.Text; + + // -Check if previous context is present, if yes then put it with the query + // -Check for id if query is present in reverse index. + if (!dialogOptions.ContainsKey(QnAContextData)) + { + dialogOptions[QnAContextData] = new Dictionary(); + } + else + { + var previousContextData = dialogOptions[QnAContextData] as Dictionary; + if (dialogOptions[PreviousQnAId] != null) + { + var previousQnAId = Convert.ToInt32(dialogOptions[PreviousQnAId]); + + if (previousQnAId > 0) + { + qnaMakerOptions.Context = new QnARequestContext + { + PreviousQnAId = previousQnAId + }; + + if (previousContextData.TryGetValue(stepContext.Context.Activity.Text.ToLower(), out var currentQnAId)) + { + qnaMakerOptions.QnAId = currentQnAId; + } + } + } + } + + // Calling QnAMaker to get response. + var response = await _services.QnAMakerService.GetAnswersRawAsync(stepContext.Context, qnaMakerOptions).ConfigureAwait(false); + + // Resetting previous query. + dialogOptions[PreviousQnAId] = -1; + stepContext.ActiveDialog.State["options"] = dialogOptions; + + // Take this value from GetAnswerResponse + var isActiveLearningEnabled = response.ActiveLearningEnabled; + + stepContext.Values[QnAData] = new List(response.Answers); + + // Check if active learning is enabled. + if (isActiveLearningEnabled && response.Answers.Any() && response.Answers.First().Score <= maximumScoreForLowScoreVariation) + { + // Get filtered list of the response that support low score variation criteria. + response.Answers = _services.QnAMakerService.GetLowScoreVariation(response.Answers); + + if (response.Answers.Count() > 1) + { + var suggestedQuestions = new List(); + foreach (var qna in response.Answers) + { + suggestedQuestions.Add(qna.Questions[0]); + } + + // Get active learning suggestion card activity. + var qnaDialogResponseOptions = dialogOptions[QnADialogResponseOptions] as QnADialogResponseOptions; + var message = QnACardBuilder.GetSuggestionsCard(suggestedQuestions, qnaDialogResponseOptions.ActiveLearningCardTitle, qnaDialogResponseOptions.CardNoMatchText); + await stepContext.Context.SendActivityAsync(message).ConfigureAwait(false); + + return new DialogTurnResult(DialogTurnStatus.Waiting); + } + } + + var result = new List(); + if (response.Answers.Any()) + { + result.Add(response.Answers.First()); + } + + stepContext.Values[QnAData] = result; + + // If card is not shown, move to next step with top qna response. + return await stepContext.NextAsync(result, cancellationToken).ConfigureAwait(false); + } + + private async Task CallTrain(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var trainResponses = stepContext.Values[QnAData] as List; + var currentQuery = stepContext.Values[CurrentQuery] as string; + + var reply = stepContext.Context.Activity.Text; + + var dialogOptions = GetDialogOptionsValue(stepContext); + var qnaDialogResponseOptions = dialogOptions[QnADialogResponseOptions] as QnADialogResponseOptions; + + if (trainResponses.Count > 1) + { + var qnaResult = trainResponses.FirstOrDefault(kvp => kvp.Questions[0] == reply); + + if (qnaResult != null) + { + stepContext.Values[QnAData] = new List() { qnaResult }; + + var records = new FeedbackRecord[] + { + new FeedbackRecord + { + UserId = stepContext.Context.Activity.Id, + UserQuestion = currentQuery, + QnaId = qnaResult.Id, + } + }; + + var feedbackRecords = new FeedbackRecords { Records = records }; + + // Call Active Learning Train API + await _services.QnAMakerService.CallTrainAsync(feedbackRecords).ConfigureAwait(false); + + return await stepContext.NextAsync(new List() { qnaResult }, cancellationToken).ConfigureAwait(false); + } + else if (reply.Equals(qnaDialogResponseOptions.CardNoMatchText, StringComparison.OrdinalIgnoreCase)) + { + await stepContext.Context.SendActivityAsync(qnaDialogResponseOptions.CardNoMatchResponse, cancellationToken: cancellationToken).ConfigureAwait(false); + return await stepContext.EndDialogAsync().ConfigureAwait(false); + } + else + { + return await stepContext.ReplaceDialogAsync(QnAMakerDialogName, stepContext.ActiveDialog.State["options"], cancellationToken).ConfigureAwait(false); + } + } + + return await stepContext.NextAsync(stepContext.Result, cancellationToken).ConfigureAwait(false); + } + + private async Task CheckForMultiTurnPrompt(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + if (stepContext.Result is List response && response.Count > 0) + { + // -Check if context is present and prompt exists + // -If yes: Add reverse index of prompt display name and its corresponding qna id + // -Set PreviousQnAId as answer.Id + // -Display card for the prompt + // -Wait for the reply + // -If no: Skip to next step + + var answer = response.First(); + + if (answer.Context != null && answer.Context.Prompts != null && answer.Context.Prompts.Count() > 0) + { + var dialogOptions = GetDialogOptionsValue(stepContext); + var qnaDialogResponseOptions = dialogOptions[QnADialogResponseOptions] as QnADialogResponseOptions; + var previousContextData = new Dictionary(); + if (dialogOptions.ContainsKey(QnAContextData)) + { + previousContextData = dialogOptions[QnAContextData] as Dictionary; + } + + foreach (var prompt in answer.Context.Prompts) + { + previousContextData.Add(prompt.DisplayText.ToLower(), prompt.QnaId); + } + + dialogOptions[QnAContextData] = previousContextData; + dialogOptions[PreviousQnAId] = answer.Id; + stepContext.ActiveDialog.State["options"] = dialogOptions; + + // Get multi-turn prompts card activity. + var message = GetQnAPromptsCardWithoutNoMatch(answer); + await stepContext.Context.SendActivityAsync(message).ConfigureAwait(false); + + return new DialogTurnResult(DialogTurnStatus.Waiting); + } + } + + return await stepContext.NextAsync(stepContext.Result, cancellationToken).ConfigureAwait(false); + } + + private async Task DisplayQnAResult(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + var dialogOptions = GetDialogOptionsValue(stepContext); + var qnaDialogResponseOptions = dialogOptions[QnADialogResponseOptions] as QnADialogResponseOptions; + var reply = stepContext.Context.Activity.Text; + + if (reply.Equals(qnaDialogResponseOptions.CardNoMatchText, StringComparison.OrdinalIgnoreCase)) + { + await stepContext.Context.SendActivityAsync(qnaDialogResponseOptions.CardNoMatchResponse, cancellationToken: cancellationToken).ConfigureAwait(false); + return await stepContext.EndDialogAsync().ConfigureAwait(false); + } + + // If previous QnAId is present, replace the dialog + var previousQnAId = Convert.ToInt32(dialogOptions[PreviousQnAId]); + if (previousQnAId > 0) + { + return await stepContext.ReplaceDialogAsync(QnAMakerDialogName, dialogOptions, cancellationToken).ConfigureAwait(false); + } + + // If response is present then show that response, else default answer. + if (stepContext.Result is List response && response.Count > 0) + { + await stepContext.Context.SendActivityAsync(response.First().Answer, cancellationToken: cancellationToken).ConfigureAwait(false); + } + else + { + await stepContext.Context.SendActivityAsync(qnaDialogResponseOptions.NoAnswer, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + return await stepContext.EndDialogAsync().ConfigureAwait(false); + } + + /// + /// Get multi-turn prompts card. + /// + /// Result to be dispalyed as prompts. + /// IMessageActivity. + private static IMessageActivity GetQnAPromptsCardWithoutNoMatch(QueryResult result) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var chatActivity = Activity.CreateMessageActivity(); + var buttonList = new List(); + + // Add all prompt + foreach (var prompt in result.Context.Prompts) + { + buttonList.Add( + new CardAction() + { + Value = prompt.DisplayText, + Type = "imBack", + Title = prompt.DisplayText, + }); + } + + var plCard = new HeroCard() + { + Text = result.Answer, + Subtitle = string.Empty, + Buttons = buttonList + }; + + // Create the attachment. + var attachment = plCard.ToAttachment(); + + chatActivity.Attachments.Add(attachment); + + return chatActivity; + } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/Dialog/RootDialog.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/Dialog/RootDialog.cs new file mode 100644 index 0000000000..f36343f3fc --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/Dialog/RootDialog.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Bot.Builder.AI.QnA; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.BotBuilderSamples.Utils; + +namespace Microsoft.BotBuilderSamples.Dialog +{ + /// + /// This is an example root dialog. Replace this with your applications. + /// + public class RootDialog : ComponentDialog + { + /// + /// QnA Maker initial dialog + /// + private const string InitialDialog = "initial-dialog"; + + /// + /// Initializes a new instance of the class. + /// + /// Bot Services. + public RootDialog(IBotServices services) + : base("root") + { + AddDialog(new QnAMakerBaseDialog(services)); + + AddDialog(new WaterfallDialog(InitialDialog) + .AddStep(InitialStepAsync)); + + // The initial child Dialog to run. + InitialDialogId = InitialDialog; + } + + private async Task InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) + { + // Set values for generate answer options. + var qnamakerOptions = new QnAMakerOptions + { + ScoreThreshold = QnAMakerBaseDialog.DefaultThreshold, + Top = QnAMakerBaseDialog.DefaultTopN, + Context = new QnARequestContext() + }; + + // Set values for dialog responses. + var qnaDialogResponseOptions = new QnADialogResponseOptions + { + NoAnswer = QnAMakerBaseDialog.DefaultNoAnswer, + ActiveLearningCardTitle = QnAMakerBaseDialog.DefaultCardTitle, + CardNoMatchText = QnAMakerBaseDialog.DefaultCardNoMatchText, + CardNoMatchResponse = QnAMakerBaseDialog.DefaultCardNoMatchResponse + }; + + var dialogOptions = new Dictionary + { + [QnAMakerBaseDialog.QnAOptions] = qnamakerOptions, + [QnAMakerBaseDialog.QnADialogResponseOptions] = qnaDialogResponseOptions + }; + + return await stepContext.BeginDialogAsync(nameof(QnAMakerBaseDialog), dialogOptions, cancellationToken); + } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/IBotServices.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/IBotServices.cs new file mode 100644 index 0000000000..67e9f5195e --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/IBotServices.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Bot.Builder.AI.QnA; + +namespace Microsoft.BotBuilderSamples +{ + public interface IBotServices + { + QnAMaker QnAMakerService { get; } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/Program.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/Program.cs new file mode 100644 index 0000000000..45554c6f4c --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/Program.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.5.0 + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace Microsoft.BotBuilderSamples +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/Properties/launchSettings.json b/samples/csharp_dotnetcore/49.qnamaker-all-features/Properties/launchSettings.json new file mode 100644 index 0000000000..776b057136 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:3978/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "QnABot": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:3978/" + } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/QnABotAllFeatures.csproj b/samples/csharp_dotnetcore/49.qnamaker-all-features/QnABotAllFeatures.csproj new file mode 100644 index 0000000000..37dada8d39 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/QnABotAllFeatures.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.1 + latest + 7e9cc97e-50d0-436e-8c72-a08b712b4ebf + + + + + + + + + + + + Always + + + + diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/README.md b/samples/csharp_dotnetcore/49.qnamaker-all-features/README.md new file mode 100644 index 0000000000..6408748154 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/README.md @@ -0,0 +1,96 @@ +# QnA Maker + +Bot Framework v4 QnA Maker bot sample. This sample shows how to integrate Multiturn and Active learning in a QnA Maker bot with ASP.Net Core-2. Click [here][72] to know more about using follow-up prompts to create multiturn conversation. To know more about how to enable and use active learning, click [here][71]. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses the [QnA Maker Cognitive AI](https://www.qnamaker.ai) service. + +The [QnA Maker Service](https://www.qnamaker.ai) enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. In this sample, we demonstrate how to use the QnA Maker service to answer questions based on a FAQ text file used as input. + +## Concepts introduced in this sample +The [QnA Maker Service][7] enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. +In this sample, we demonstrate how to use the Active Learning to generate suggestions for knowledge base. + +# Prerequisites +- Follow instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/set-up-qnamaker-service-azure) to create a QnA Maker service. +- Follow instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/multiturn-conversation) to create multiturn experience. +- Follow instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/quickstarts/create-publish-knowledge-base) to import and publish your newly created QnA Maker service. +- Update [appsettings.json](appsettings.json) with your kbid (KnowledgeBase Id), endpointKey and endpointHost. QnA knowledge base setup and application configuration steps can be found [here](https://aka.ms/qna-instructions). +- (Optional) Follow instructions [here](https://github.com/Microsoft/botbuilder-tools/tree/master/packages/QnAMaker) to set up the +QnA Maker CLI to deploy the model. + +## To try this sample + +- Clone the repository + + ```bash + git clone https://github.com/Microsoft/botbuilder-samples.git + ``` + +- In a terminal, navigate to `samples/csharp_dotnetcore/11.qnamaker` +- Run the bot from a terminal or from Visual Studio, choose option A or B. + + A) From a terminal + + ```bash + # run the bot + dotnet run + ``` + + B) Or from Visual Studio + + - Launch Visual Studio + - File -> Open -> Project/Solution + - Navigate to `samples/csharp_dotnetcore/11.qnamaker` folder + - Select `QnABot.csproj` file + - Press `F5` to run the project + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3999/api/messages` + +# Deploy the bot to Azure +See [Deploy your C# bot to Azure][50] for instructions. + +The deployment process assumes you have an account on Microsoft Azure and are able to log into the [Microsoft Azure Portal][60]. + +If you are new to Microsoft Azure, please refer to [Getting started with Azure][70] for guidance on how to get started on Azure. + +# Further reading +* [Active learning Documentation][al#1] +* [Bot Framework Documentation][80] +* [Bot Basics][90] +* [Azure Bot Service Introduction][100] +* [Azure Bot Service Documentation][110] +* [msbot CLI][130] +* [Azure Portal][140] + +[1]: https://dev.botframework.com +[2]: https://docs.microsoft.com/en-us/visualstudio/releasenotes/vs2017-relnotes +[3]: https://dotnet.microsoft.com/download/dotnet-core/2.1 +[4]: https://docs.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0 +[5]: https://github.com/microsoft/botframework-emulator +[6]: https://aka.ms/botframeworkemulator +[7]: https://www.qnamaker.ai + +[50]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0 +[60]: https://portal.azure.com +[70]: https://azure.microsoft.com/get-started/ +[80]: https://docs.botframework.com +[90]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 +[100]: https://docs.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0 +[110]: https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0 +[120]: https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest +[130]: https://github.com/Microsoft/botbuilder-tools/tree/master/packages/MSBot +[140]: https://portal.azure.com +[150]: https://www.luis.ai + +[71]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/improve-knowledge-base +[72]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/multiturn-conversation diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/Startup.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/Startup.cs new file mode 100644 index 0000000000..a793daac50 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/Startup.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.5.0 + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Integration.AspNet.Core; +using Microsoft.BotBuilderSamples.Bots; +using Microsoft.BotBuilderSamples.Dialog; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.BotBuilderSamples +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + // Create the Bot Framework Adapter with error handling enabled. + services.AddSingleton(); + + // Create the bot services(QnA) as a singleton. + services.AddSingleton(); + + // Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.) + services.AddSingleton(); + + // Create the User state. (Used in this bot's Dialog implementation.) + services.AddSingleton(); + + // Create the Conversation state. (Used by the Dialog system itself.) + services.AddSingleton(); + + // The Dialog that will be run by the bot. + services.AddSingleton(); + + // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. + services.AddTransient>(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseDefaultFiles(); + app.UseStaticFiles(); + + app.UseMvc(); + } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/Utils/QnADialogResponseOptions.cs b/samples/csharp_dotnetcore/49.qnamaker-all-features/Utils/QnADialogResponseOptions.cs new file mode 100644 index 0000000000..ab763655f1 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/Utils/QnADialogResponseOptions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.BotBuilderSamples.Utils +{ + /// + /// QnA dialog response options class. + /// + public class QnADialogResponseOptions + { + /// + /// Gets or sets get or set for No answer. + /// + public string NoAnswer { get; set; } + + /// + /// Gets or sets get or set for Active learning card title. + /// + public string ActiveLearningCardTitle { get; set; } + + /// + /// Gets or sets get or set for Card no match text. + /// + public string CardNoMatchText { get; set; } + + /// + /// Gets or sets get or set for Card no match response. + /// + public string CardNoMatchResponse { get; set; } + } +} diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/appsettings.Development.json b/samples/csharp_dotnetcore/49.qnamaker-all-features/appsettings.Development.json new file mode 100644 index 0000000000..b49abfc201 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } + } diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/appsettings.json b/samples/csharp_dotnetcore/49.qnamaker-all-features/appsettings.json new file mode 100644 index 0000000000..4aa3d5d33e --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/appsettings.json @@ -0,0 +1,7 @@ +{ + "MicrosoftAppId": "", + "MicrosoftAppPassword": "", + "QnAKnowledgebaseId": "", + "QnAEndpointKey": "", + "QnAEndpointHostName": "" +} \ No newline at end of file diff --git a/samples/csharp_dotnetcore/49.qnamaker-all-features/wwwroot/default.htm b/samples/csharp_dotnetcore/49.qnamaker-all-features/wwwroot/default.htm new file mode 100644 index 0000000000..56d72d5bb7 --- /dev/null +++ b/samples/csharp_dotnetcore/49.qnamaker-all-features/wwwroot/default.htm @@ -0,0 +1,420 @@ + + + + + + + QnAMakerBot + + + + + +
+
+
+
QnAMakerBot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/samples/csharp_dotnetcore/csharp_dotnetcore.sln b/samples/csharp_dotnetcore/csharp_dotnetcore.sln index 74f718002e..cf790d03a4 100644 --- a/samples/csharp_dotnetcore/csharp_dotnetcore.sln +++ b/samples/csharp_dotnetcore/csharp_dotnetcore.sln @@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdaptiveCardsBot", "07.usin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SuggestedActionsBot", "08.suggested-actions\SuggestedActionsBot.csproj", "{9C70CB81-4403-41B8-ABB8-72497061DDB0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QnABot", "11.qnamaker\QnABot.csproj", "{E8A19507-8EB5-4B37-B592-AB8316283A6F}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreBot", "13.core-bot\CoreBot.csproj", "{8FAE502C-68A1-47E8-9761-BCEA491BA4AA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLP-With-Dispatch-Bot", "14.nlp-with-dispatch\NLP-With-Dispatch-Bot.csproj", "{C1709610-3DEC-457F-B6CA-227F7E7DAC63}" @@ -75,6 +73,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsConversationBot", "57. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsFileUpload", "56.teams-file-upload\TeamsFileUpload.csproj", "{7DDAB89B-025E-4E3F-BCC9-36F5B8C64C44}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QnABot", "11a.qnamaker\QnABot.csproj", "{F15FCB75-8835-4E38-B978-2244F339EDDE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QnABotAllFeatures", "49.qnamaker-all-features\QnABotAllFeatures.csproj", "{05DAAFFB-B946-434A-AB44-4AC8C0BB4096}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,10 +107,6 @@ Global {9C70CB81-4403-41B8-ABB8-72497061DDB0}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C70CB81-4403-41B8-ABB8-72497061DDB0}.Release|Any CPU.ActiveCfg = Release|Any CPU {9C70CB81-4403-41B8-ABB8-72497061DDB0}.Release|Any CPU.Build.0 = Release|Any CPU - {E8A19507-8EB5-4B37-B592-AB8316283A6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8A19507-8EB5-4B37-B592-AB8316283A6F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E8A19507-8EB5-4B37-B592-AB8316283A6F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E8A19507-8EB5-4B37-B592-AB8316283A6F}.Release|Any CPU.Build.0 = Release|Any CPU {8FAE502C-68A1-47E8-9761-BCEA491BA4AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8FAE502C-68A1-47E8-9761-BCEA491BA4AA}.Debug|Any CPU.Build.0 = Debug|Any CPU {8FAE502C-68A1-47E8-9761-BCEA491BA4AA}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -221,6 +219,14 @@ Global {7DDAB89B-025E-4E3F-BCC9-36F5B8C64C44}.Debug|Any CPU.Build.0 = Debug|Any CPU {7DDAB89B-025E-4E3F-BCC9-36F5B8C64C44}.Release|Any CPU.ActiveCfg = Release|Any CPU {7DDAB89B-025E-4E3F-BCC9-36F5B8C64C44}.Release|Any CPU.Build.0 = Release|Any CPU + {F15FCB75-8835-4E38-B978-2244F339EDDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F15FCB75-8835-4E38-B978-2244F339EDDE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F15FCB75-8835-4E38-B978-2244F339EDDE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F15FCB75-8835-4E38-B978-2244F339EDDE}.Release|Any CPU.Build.0 = Release|Any CPU + {05DAAFFB-B946-434A-AB44-4AC8C0BB4096}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05DAAFFB-B946-434A-AB44-4AC8C0BB4096}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05DAAFFB-B946-434A-AB44-4AC8C0BB4096}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05DAAFFB-B946-434A-AB44-4AC8C0BB4096}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/javascript_nodejs/11a.qnamaker/.env b/samples/javascript_nodejs/11a.qnamaker/.env new file mode 100644 index 0000000000..a12e7944a5 --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/.env @@ -0,0 +1,5 @@ +MicrosoftAppId= +MicrosoftAppPassword= +QnAKnowledgebaseId= +QnAEndpointKey= +QnAEndpointHostName= diff --git a/samples/javascript_nodejs/11a.qnamaker/.eslintrc.js b/samples/javascript_nodejs/11a.qnamaker/.eslintrc.js new file mode 100644 index 0000000000..1c6ff1cba8 --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + "extends": "standard", + "rules": { + "semi": [2, "always"], + "indent": [2, 4], + "no-return-await": 0, + "space-before-function-paren": [2, { + "named": "never", + "anonymous": "never", + "asyncArrow": "always" + }], + "template-curly-spacing": [2, "always"] + } +}; \ No newline at end of file diff --git a/samples/javascript_nodejs/11a.qnamaker/README.md b/samples/javascript_nodejs/11a.qnamaker/README.md new file mode 100644 index 0000000000..96dfa54b3b --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/README.md @@ -0,0 +1,92 @@ +# QnA Maker + +Bot Framework v4 QnA Maker bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses the [QnA Maker Cognitive AI](https://www.qnamaker.ai) service. + +The [QnA Maker Service](https://www.qnamaker.ai) enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. In this sample, we demonstrate how to use the QnA Maker service to answer questions based on a FAQ text file used as input. + +## Prerequisites + +This samples **requires** prerequisites in order to run. + +### Overview + +This bot uses [QnA Maker Service](https://www.qnamaker.ai), an AI based cognitive service, to implement simple Question and Answer conversational patterns. + +- [Node.js](https://nodejs.org) version 10.14 or higher + + ```bash + # determine node version + node --version + ``` + +### Create a QnAMaker Application to enable QnA Knowledge Bases + +QnA knowledge base setup and application configuration steps can be found [here](https://aka.ms/qna-instructions). + +## To try this sample + +- Clone the repository + + ```bash + git clone https://github.com/microsoft/botbuilder-samples.git + ``` + +- In a terminal, navigate to `samples/javascript_nodejs/11.qnamaker` + + ```bash + cd samples/javascript_nodejs/11.qnamaker + ``` + +- Install modules + + ```bash + npm install + ``` + +- Setup QnAMaker + + The prerequisite outlined above contain the steps necessary to provision a QnA Knowledge Base on www.qnamaker.ai. Refer to [Use QnA Maker to answer questions][41] for directions to setup and configure QnAMaker. + +- Run the sample + + ```bash + npm start + ``` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +QnA Maker enables you to power a question and answer service from your semi-structured content. + +One of the basic requirements in writing your own bot is to seed it with questions and answers. In many cases, the questions and answers already exist in content like FAQ URLs/documents, product manuals, etc. With QnA Maker, users can query your application in a natural, conversational manner. QnA Maker uses machine learning to extract relevant question-answer pairs from your content. It also uses powerful matching and ranking algorithms to provide the best possible match between the user query and the questions. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [QnA Maker Documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/overview/overview) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [QnA Maker CLI](https://github.com/Microsoft/botbuilder-tools/tree/master/packages/QnAMaker) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Restify](https://www.npmjs.com/package/restify) +- [dotenv](https://www.npmjs.com/package/dotenv) diff --git a/samples/javascript_nodejs/11a.qnamaker/bots/QnABot.js b/samples/javascript_nodejs/11a.qnamaker/bots/QnABot.js new file mode 100644 index 0000000000..78364f3b40 --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/bots/QnABot.js @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityHandler } = require('botbuilder'); +const { QnAMaker } = require('botbuilder-ai'); + +class QnABot extends ActivityHandler { + constructor() { + super(); + + try { + this.qnaMaker = new QnAMaker({ + knowledgeBaseId: process.env.QnAKnowledgebaseId, + endpointKey: process.env.QnAEndpointKey, + host: process.env.QnAEndpointHostName + }); + } catch (err) { + console.warn(`QnAMaker Exception: ${ err } Check your QnAMaker configuration in .env`); + } + + // If a new user is added to the conversation, send them a greeting message + this.onMembersAdded(async (context, next) => { + const membersAdded = context.activity.membersAdded; + for (let cnt = 0; cnt < membersAdded.length; cnt++) { + if (membersAdded[cnt].id !== context.activity.recipient.id) { + await context.sendActivity('Welcome to the QnA Maker sample! Ask me a question and I will try to answer it.'); + } + } + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + + // When a user sends a message, perform a call to the QnA Maker service to retrieve matching Question and Answer pairs. + this.onMessage(async (context, next) => { + if (!process.env.QnAKnowledgebaseId || !process.env.QnAEndpointKey || !process.env.QnAEndpointHostName) { + let unconfiguredQnaMessage = 'NOTE: \r\n' + + 'QnA Maker is not configured. To enable all capabilities, add `QnAKnowledgebaseId`, `QnAEndpointKey` and `QnAEndpointHostName` to the .env file. \r\n' + + 'You may visit www.qnamaker.ai to create a QnA Maker knowledge base.' + + await context.sendActivity(unconfiguredQnaMessage) + } + else { + console.log('Calling QnA Maker'); + + const qnaResults = await this.qnaMaker.getAnswers(context); + + // If an answer was received from QnA Maker, send the answer back to the user. + if (qnaResults[0]) { + await context.sendActivity(qnaResults[0].answer); + + // If no answers were returned from QnA Maker, reply with help. + } else { + await context.sendActivity('No QnA Maker answers were found.'); + } + + } + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + } +} + +module.exports.QnABot = QnABot; diff --git a/samples/javascript_nodejs/11a.qnamaker/cognitiveModels/smartLightFAQ.tsv b/samples/javascript_nodejs/11a.qnamaker/cognitiveModels/smartLightFAQ.tsv new file mode 100644 index 0000000000..754118909e --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/cognitiveModels/smartLightFAQ.tsv @@ -0,0 +1,15 @@ +Question Answer Source Keywords +Question Answer 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Source +My Contoso smart light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +My smart light app stopped responding. Restart the app. If the problem persists, contact support. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +How do I contact support? Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +I need help. Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +I upgraded the app and it doesn't work anymore. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Light doesn't work after upgrade. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Question Answer 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Source +Who should I contact for customer service? Please direct all customer service questions to (202) 555-0164 \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +Why does the light not work? The simplest way to troubleshoot your smart light is to turn it off and on. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +How long does the light's battery last for? The battery will last approximately 10 - 12 weeks with regular use. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +What type of light bulb do I need? A 26-Watt compact fluorescent light bulb that features both energy savings and long-life performance. 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +Hi Hello Editorial \ No newline at end of file diff --git a/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/new-rg-parameters.json b/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 0000000000..ead3390932 --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/preexisting-rg-parameters.json b/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 0000000000..b6f5114fcc --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/template-with-new-rg.json b/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 0000000000..06b8284158 --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('appServicePlanName')]" + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/template-with-preexisting-rg.json b/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 0000000000..43943b6581 --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('servicePlanName')]" + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/javascript_nodejs/11a.qnamaker/index.js b/samples/javascript_nodejs/11a.qnamaker/index.js new file mode 100644 index 0000000000..1fbe17ed66 --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/index.js @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// index.js is used to setup and configure your bot + +// Import required packages +const path = require('path'); +const restify = require('restify'); + +// Import required bot services. +// See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { BotFrameworkAdapter } = require('botbuilder'); +const { ActivityTypes } = require('botbuilder-core'); + +// The bot. +const { QnABot } = require('./bots/QnABot'); + +// Note: Ensure you have a .env file and include QnAMakerKnowledgeBaseId, QnAMakerEndpointKey and QnAMakerHost. +const ENV_FILE = path.join(__dirname, '.env'); +require('dotenv').config({ path: ENV_FILE }); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about adapters. +const adapter = new BotFrameworkAdapter({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword +}); + +// Catch-all for errors. +adapter.onTurnError = async (context, error) => { + // Create a trace activity that contains the error object + const traceActivity = { + type: ActivityTypes.Trace, + timestamp: new Date(), + name: 'onTurnError Trace', + label: 'TurnError', + value: `${ error }`, + valueType: 'https://www.botframework.com/schemas/error' + }; + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${ error }`); + + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendActivity(traceActivity); + + // Send a message to the user + await context.sendActivity(`The bot encounted an error or bug.`); + await context.sendActivity(`To continue to run this bot, please fix the bot source code.`); +}; + +// Create the main dialog. +const bot = new QnABot(); + +// Create HTTP server +const server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 3978, function() { + console.log(`\n${ server.name } listening to ${ server.url }`); + console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`); +}); + +// Listen for incoming activities and route them to your bot main dialog. +server.post('/api/messages', (req, res) => { + // Route received a request to adapter for processing + adapter.processActivity(req, res, async (turnContext) => { + // route to bot activity handler. + await bot.run(turnContext); + }); +}); diff --git a/samples/javascript_nodejs/11a.qnamaker/package.json b/samples/javascript_nodejs/11a.qnamaker/package.json new file mode 100644 index 0000000000..ae91425315 --- /dev/null +++ b/samples/javascript_nodejs/11a.qnamaker/package.json @@ -0,0 +1,33 @@ +{ + "name": "qnamaker", + "version": "1.0.0", + "description": "Bot Builder v4 using QnA Maker service", + "author": "Microsoft", + "license": "MIT", + "main": "index.js", + "scripts": { + "start": "node ./index.js", + "watch": "nodemon ./index.js", + "lint": "eslint .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/BotBuilder-Samples.git" + }, + "dependencies": { + "botbuilder": "~4.6.0", + "botbuilder-ai": "~4.6.0", + "dotenv": "^8.0.0", + "restify": "~8.3.3" + }, + "devDependencies": { + "eslint": "^6.0.1", + "eslint-config-standard": "^13.0.1", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-node": "^9.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.0", + "nodemon": "~1.19.1" + } +} diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/.env b/samples/javascript_nodejs/49.qnamaker-all-features/.env new file mode 100644 index 0000000000..f1e854eb4d --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/.env @@ -0,0 +1,6 @@ +MicrosoftAppId= +MicrosoftAppPassword= + +QnAKnowledgebaseId= +QnAEndpointKey= +QnAEndpointHostName= \ No newline at end of file diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/.eslintrc.js b/samples/javascript_nodejs/49.qnamaker-all-features/.eslintrc.js new file mode 100644 index 0000000000..1c6ff1cba8 --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + "extends": "standard", + "rules": { + "semi": [2, "always"], + "indent": [2, 4], + "no-return-await": 0, + "space-before-function-paren": [2, { + "named": "never", + "anonymous": "never", + "asyncArrow": "always" + }], + "template-curly-spacing": [2, "always"] + } +}; \ No newline at end of file diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/README.md b/samples/javascript_nodejs/49.qnamaker-all-features/README.md new file mode 100644 index 0000000000..285c312fe6 --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/README.md @@ -0,0 +1,120 @@ +# QnA Maker + +Bot Framework v4 QnA Maker bot sample. This sample shows how to integrate Multiturn and Active learning in a QnA Maker bot with ASP.Net Core-2. Click [here][72] to know more about using follow-up prompts to create multiturn conversation. To know more about how to enable and use active learning, click [here][71]. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses the [QnA Maker Cognitive AI](https://www.qnamaker.ai) service. + +The [QnA Maker Service](https://www.qnamaker.ai) enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. In this sample, we demonstrate how to use the QnA Maker service to answer questions based on a FAQ text file used as input. + +## Concepts introduced in this sample +The [QnA Maker Service][19] enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. +In this sample, we demonstrate how to use the Multiturn experience for the knowledge base with active learning feature enabled. + +# Prerequisites + +This samples **requires** prerequisites in order to run. + +### Overview + +This bot uses [QnA Maker Service](https://www.qnamaker.ai), an AI based cognitive service, to implement simple Question and Answer conversational patterns. + +- [Node.js](https://nodejs.org) version 10.14 or higher + + ```bash + # determine node version + node --version + ``` + +### Create a QnAMaker Application to enable QnA Knowledge Bases + +QnA knowledge base setup and application configuration steps can be found [here](https://aka.ms/qna-instructions). + +## To try this sample + +- Clone the repository + + ```bash + git clone https://github.com/microsoft/botbuilder-samples.git + ``` + +- In a terminal, navigate to `samples/javascript_nodejs/11.qnamaker` + + ```bash + cd samples/javascript_nodejs/11.qnamaker + ``` + +- Install modules + + ```bash + npm install + ``` + +- Setup QnAMaker + + The prerequisite outlined above contain the steps necessary to provision a QnA Knowledge Base on www.qnamaker.ai. Refer to [Use QnA Maker to answer questions][41] for directions to setup and configure QnAMaker. + +- Run the sample + + ```bash + npm start + ``` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3999/api/messages` + +# QnA Maker service +QnA Maker enables you to power a question and answer service from your semi-structured content. + +One of the basic requirements in writing your own bot is to seed it with questions and answers. In many cases, the questions and answers already exist in content like FAQ URLs/documents, product manuals, etc. With QnA Maker, users can query your application in a natural, conversational manner. QnA Maker uses machine learning to extract relevant question-answer pairs from your content. It also uses powerful matching and ranking algorithms to provide the best possible match between the user query and the questions. + +# Deploy the bot to Azure +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure][40] for a complete list of deployment instructions. + +# Further reading +- [Bot Framework Documentation][20] +- [Bot Basics][32] +- [QnA Maker Documentation][23] +- [Active learning Documentation][50] +- [Activity Processing][25] +- [Azure Bot Service Introduction][21] +- [Azure Bot Service Documentation][22] +- [Azure CLI][7] +- [QnA Maker CLI][24] +- [Azure Portal][10] +- [Restify][30] +- [dotenv][31] + +[1]: https://dev.botframework.com +[4]: https://nodejs.org +[5]: https://github.com/microsoft/botframework-emulator +[6]: https://github.com/Microsoft/BotFramework-Emulator/releases +[7]: https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest +[8]: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest +[9]: https://github.com/Microsoft/botbuilder-tools/tree/master/packages/MSBot +[10]: https://portal.azure.com +[19]: https://www.qnamaker.ai +[20]: https://docs.botframework.com +[21]: https://docs.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0 +[22]: https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0 +[23]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/overview/overview +[24]: https://github.com/Microsoft/botbuilder-tools/tree/master/packages/QnAMaker +[25]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0 +[30]: https://www.npmjs.com/package/restify +[31]: https://www.npmjs.com/package/dotenv +[32]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 +[40]: https://aka.ms/azuredeployment +[50]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/improve-knowledge-base +[51]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/multiturn-conversation + +[41]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-qna?view=azure-bot-service-4.0&tabs=cs +[71]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/improve-knowledge-base +[72]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/multiturn-conversation diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/bots/QnABot.js b/samples/javascript_nodejs/49.qnamaker-all-features/bots/QnABot.js new file mode 100644 index 0000000000..b4e20f8bef --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/bots/QnABot.js @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { ActivityHandler } = require('botbuilder'); + +/** + * A simple bot that responds to utterances with answers from QnA Maker. + * If an answer is not found for an utterance, the bot responds with help. + */ +class QnABot extends ActivityHandler { + /** + * + * @param {ConversationState} conversationState + * @param {UserState} userState + * @param {Dialog} dialog + */ + constructor(conversationState, userState, dialog) { + super(); + if (!conversationState) throw new Error('[QnABot]: Missing parameter. conversationState is required'); + if (!userState) throw new Error('[QnABot]: Missing parameter. userState is required'); + if (!dialog) throw new Error('[QnABot]: Missing parameter. dialog is required'); + + this.conversationState = conversationState; + this.userState = userState; + this.dialog = dialog; + this.dialogState = this.conversationState.createProperty('DialogState'); + + this.onMessage(async (context, next) => { + console.log('Running dialog with Message Activity.'); + + // Run the Dialog with the new message Activity. + await this.dialog.run(context, this.dialogState); + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + + // If a new user is added to the conversation, send them a greeting message + this.onMembersAdded(async (context, next) => { + const membersAdded = context.activity.membersAdded; + for (let cnt = 0; cnt < membersAdded.length; cnt++) { + if (membersAdded[cnt].id !== context.activity.recipient.id) { + await context.sendActivity('Welcome to the QnA Maker sample! Ask me a question and I will try to answer it.'); + } + } + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + + this.onDialog(async (context, next) => { + // Save any state changes. The load happened during the execution of the Dialog. + await this.conversationState.saveChanges(context, false); + await this.userState.saveChanges(context, false); + + // By calling next() you ensure that the next BotHandler is run. + await next(); + }); + } +} + +module.exports.QnABot = QnABot; diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/cognitiveModels/smartLightFAQ.tsv b/samples/javascript_nodejs/49.qnamaker-all-features/cognitiveModels/smartLightFAQ.tsv new file mode 100644 index 0000000000..754118909e --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/cognitiveModels/smartLightFAQ.tsv @@ -0,0 +1,15 @@ +Question Answer Source Keywords +Question Answer 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Source +My Contoso smart light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +My smart light app stopped responding. Restart the app. If the problem persists, contact support. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +How do I contact support? Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +I need help. Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +I upgraded the app and it doesn't work anymore. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Light doesn't work after upgrade. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial +Question Answer 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Source +Who should I contact for customer service? Please direct all customer service questions to (202) 555-0164 \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +Why does the light not work? The simplest way to troubleshoot your smart light is to turn it off and on. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +How long does the light's battery last for? The battery will last approximately 10 - 12 weeks with regular use. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +What type of light bulb do I need? A 26-Watt compact fluorescent light bulb that features both energy savings and long-life performance. 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial +Hi Hello Editorial \ No newline at end of file diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/new-rg-parameters.json b/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 0000000000..ead3390932 --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/preexisting-rg-parameters.json b/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 0000000000..b6f5114fcc --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/template-with-new-rg.json b/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 0000000000..06b8284158 --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('appServicePlanName')]" + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/template-with-preexisting-rg.json b/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 0000000000..43943b6581 --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('servicePlanName')]" + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/qnamakerBaseDialog.js b/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/qnamakerBaseDialog.js new file mode 100644 index 0000000000..69f62230e0 --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/qnamakerBaseDialog.js @@ -0,0 +1,268 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { + ComponentDialog, + DialogTurnStatus, + WaterfallDialog +} = require('botbuilder-dialogs'); + +const { QnACardBuilder } = require('../utils/qnaCardBuilder'); + +// Default parameters +const DefaultThreshold = 0.3; +const DefaultTopN = 3; +const DefaultNoAnswer = 'No QnAMaker answers found.'; + +// Card parameters +const DefaultCardTitle = 'Did you mean:'; +const DefaultCardNoMatchText = 'None of the above.'; +const DefaultCardNoMatchResponse = 'Thanks for the feedback.'; + +// Define value names for values tracked inside the dialogs. +const QnAOptions = 'qnaOptions'; +const QnADialogResponseOptions = 'qnaDialogResponseOptions'; +const CurrentQuery = 'currentQuery'; +const QnAData = 'qnaData'; +const QnAContextData = 'qnaContextData'; +const PreviousQnAId = 'prevQnAId'; + +/// QnA Maker dialog. +const QNAMAKER_DIALOG = 'qnamaker-dialog'; +const QNAMAKER_BASE_DIALOG = 'qnamaker-base-dailog'; + +class QnAMakerBaseDialog extends ComponentDialog { + /** + * Core logic of QnA Maker dialog. + * @param {QnAMaker} qnaService A QnAMaker service object. + */ + constructor(qnaService) { + super(QNAMAKER_BASE_DIALOG); + + this._qnaMakerService = qnaService; + + this.addDialog(new WaterfallDialog(QNAMAKER_DIALOG, [ + this.callGenerateAnswerAsync.bind(this), + this.callTrain.bind(this), + this.checkForMultiTurnPrompt.bind(this), + this.displayQnAResult.bind(this) + ])); + + this.initialDialogId = QNAMAKER_DIALOG; + } + + /** + * @param {WaterfallStepContext} stepContext contextual information for the current step being executed. + */ + async callGenerateAnswerAsync(stepContext) { + // Default QnAMakerOptions + var qnaMakerOptions = { + scoreThreshold: DefaultThreshold, + top: DefaultTopN, + context: {}, + qnaId: -1 + }; + + var dialogOptions = getDialogOptionsValue(stepContext); + + if (dialogOptions[QnAOptions] != null) { + qnaMakerOptions = dialogOptions[QnAOptions]; + qnaMakerOptions.scoreThreshold = qnaMakerOptions.scoreThreshold ? qnaMakerOptions.scoreThreshold : DefaultThreshold; + qnaMakerOptions.top = qnaMakerOptions.top ? qnaMakerOptions.top : DefaultThreshold; + } + + // Storing the context info + stepContext.values[CurrentQuery] = stepContext.context.activity.text; + + var previousContextData = dialogOptions[QnAContextData]; + var prevQnAId = dialogOptions[PreviousQnAId]; + + if (previousContextData != null && prevQnAId != null) { + if (prevQnAId > 0) { + qnaMakerOptions.context = { + previousQnAId: prevQnAId + }; + + if (previousContextData[stepContext.context.activity.text.toLowerCase()] !== null) { + qnaMakerOptions.qnaId = previousContextData[stepContext.context.activity.text.toLowerCase()]; + } + } + } + + // Calling QnAMaker to get response. + var response = await this._qnaMakerService.getAnswersRaw(stepContext.context, qnaMakerOptions); + + // Resetting previous query. + dialogOptions[PreviousQnAId] = -1; + stepContext.activeDialog.state.options = dialogOptions; + + // Take this value from GetAnswerResponse. + var isActiveLearningEnabled = response.activeLearningEnabled; + + stepContext.values[QnAData] = response.answers; + + // Check if active learning is enabled. + if (isActiveLearningEnabled && response.answers.length > 0 && response.answers[0].score <= 0.95) { + response.answers = this._qnaMakerService.getLowScoreVariation(response.answers); + + var suggestedQuestions = []; + if (response.answers.length > 1) { + // Display suggestions card. + response.answers.forEach(element => { + suggestedQuestions.push(element.questions[0]); + }); + var qnaDialogResponseOptions = dialogOptions[QnADialogResponseOptions]; + var message = QnACardBuilder.GetSuggestionCard(suggestedQuestions, qnaDialogResponseOptions.activeLearningCardTitle, qnaDialogResponseOptions.cardNoMatchText); + await stepContext.context.sendActivity(message); + + return { status: DialogTurnStatus.waiting }; + } + } + + var result = []; + if (response.answers.length > 0) { + result.push(response.answers[0]); + } + + stepContext.values[QnAData] = result; + + return await stepContext.next(result); + } + + /** + * @param {WaterfallStepContext} stepContext contextual information for the current step being executed. + */ + async callTrain(stepContext) { + var trainResponses = stepContext.values[QnAData]; + var currentQuery = stepContext.values[CurrentQuery]; + + var reply = stepContext.context.activity.text; + + var dialogOptions = getDialogOptionsValue(stepContext); + var qnaDialogResponseOptions = dialogOptions[QnADialogResponseOptions]; + + if (trainResponses.length > 1) { + var qnaResults = trainResponses.filter(r => r.questions[0] === reply); + + if (qnaResults.length > 0) { + stepContext.values[QnAData] = qnaResults; + + var feedbackRecords = { + FeedbackRecords: [ + { + UserId: stepContext.context.activity.id, + UserQuestion: currentQuery, + QnaId: qnaResults[0].id + } + ] + }; + + // Call Active Learning Train API + this.qnaMaker.callTrainAsync(feedbackRecords); + + return await stepContext.next(qnaResults); + } else if (reply === qnaDialogResponseOptions.cardNoMatchText) { + await stepContext.context.sendActivity(qnaDialogResponseOptions.cardNoMatchResponse); + return await stepContext.endDialog(); + } else { + return await stepContext.replaceDialog(QNAMAKER_DIALOG, stepContext.activeDialog.state.options); + } + } + + return await stepContext.next(stepContext.result); + } + + /** + * @param {WaterfallStepContext} stepContext contextual information for the current step being executed. + */ + async checkForMultiTurnPrompt(stepContext) { + if (stepContext.result != null && stepContext.result.length > 0) { + // -Check if context is present and prompt exists. + // -If yes: Add reverse index of prompt display name and its corresponding qna id. + // -Set PreviousQnAId as answer.Id. + // -Display card for the prompt. + // -Wait for the reply. + // -If no: Skip to next step. + + var answer = stepContext.result[0]; + + if (answer.context != null && answer.context.prompts != null && answer.context.prompts.length > 0) { + var dialogOptions = getDialogOptionsValue(stepContext); + + var previousContextData = {}; + + if (!!dialogOptions[QnAContextData]) { + previousContextData = dialogOptions[QnAContextData]; + } + + answer.context.prompts.forEach(prompt => { + previousContextData[prompt.displayText.toLowerCase()] = prompt.qnaId; + }); + + dialogOptions[QnAContextData] = previousContextData; + dialogOptions[PreviousQnAId] = answer.id; + stepContext.activeDialog.state.options = dialogOptions; + + // Get multi-turn prompts card activity. + var message = QnACardBuilder.GetQnAPromptsCard(answer); + await stepContext.context.sendActivity(message); + + return { status: DialogTurnStatus.waiting }; + } + } + + return await stepContext.next(stepContext.result); + } + + /** + * @param {WaterfallStepContext} stepContext contextual information for the current step being executed. + */ + async displayQnAResult(stepContext) { + var dialogOptions = getDialogOptionsValue(stepContext); + var qnaDialogResponseOptions = dialogOptions[QnADialogResponseOptions]; + + var reply = stepContext.context.activity.text; + + if (reply === qnaDialogResponseOptions.cardNoMatchText) { + await stepContext.context.sendActivity(qnaDialogResponseOptions.cardNoMatchResponse); + return await stepContext.endDialog(); + } + + var previousQnAId = dialogOptions[PreviousQnAId]; + if (previousQnAId > 0) { + return await stepContext.replaceDialog(QNAMAKER_DIALOG, dialogOptions); + } + + var responses = stepContext.result; + if (responses != null) { + if (responses.length > 0) { + await stepContext.context.sendActivity(responses[0].answer); + } else { + await stepContext.context.sendActivity(qnaDialogResponseOptions.noAnswer); + } + } + + return await stepContext.endDialog(); + } +} + +function getDialogOptionsValue(dialogContext) { + var dialogOptions = {}; + + if (dialogContext.activeDialog.state.options !== null) { + dialogOptions = dialogContext.activeDialog.state.options; + } + + return dialogOptions; +} + +module.exports.QnAMakerBaseDialog = QnAMakerBaseDialog; +module.exports.QNAMAKER_BASE_DIALOG = QNAMAKER_BASE_DIALOG; +module.exports.DefaultThreshold = DefaultThreshold; +module.exports.DefaultTopN = DefaultTopN; +module.exports.DefaultNoAnswer = DefaultNoAnswer; +module.exports.DefaultCardTitle = DefaultCardTitle; +module.exports.DefaultCardNoMatchText = DefaultCardNoMatchText; +module.exports.DefaultCardNoMatchResponse = DefaultCardNoMatchResponse; +module.exports.QnAOptions = QnAOptions; +module.exports.QnADialogResponseOptions = QnADialogResponseOptions; diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/rootDialog.js b/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/rootDialog.js new file mode 100644 index 0000000000..3f258558b1 --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/dialogs/rootDialog.js @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { + ComponentDialog, + DialogSet, + DialogTurnStatus, + WaterfallDialog +} = require('botbuilder-dialogs'); + +const { + QnAMakerBaseDialog, + QNAMAKER_BASE_DIALOG, + DefaultCardNoMatchResponse, + DefaultCardNoMatchText, + DefaultCardTitle, + DefaultNoAnswer, + DefaultThreshold, + DefaultTopN, + QnAOptions, + QnADialogResponseOptions +} = require('./qnamakerBaseDialog'); + +const INITIAL_DIALOG = 'initial-dialog'; +const ROOT_DIALOG = 'root-dialog'; + +class RootDialog extends ComponentDialog { + /** + * Root dialog for this bot. + * @param {QnAMaker} qnaService A QnAMaker service object. + */ + constructor(qnaService) { + super(ROOT_DIALOG); + + // Initial waterfall dialog. + this.addDialog(new WaterfallDialog(INITIAL_DIALOG, [ + this.startInitialDialog.bind(this) + ])); + + this.addDialog(new QnAMakerBaseDialog(qnaService)); + + this.initialDialogId = INITIAL_DIALOG; + } + + /** + * The run method handles the incoming activity (in the form of a TurnContext) and passes it through the dialog system. + * If no dialog is active, it will start the default dialog. + * @param {*} turnContext + * @param {*} accessor + */ + async run(context, accessor) { + const dialogSet = new DialogSet(accessor); + dialogSet.add(this); + + const dialogContext = await dialogSet.createContext(context); + const results = await dialogContext.continueDialog(); + if (results.status === DialogTurnStatus.empty) { + await dialogContext.beginDialog(this.id); + } + } + + // This is the first step of the WaterfallDialog. + // It kicks off the dialog with the QnA Maker with provided options. + async startInitialDialog(step) { + // Set values for generate answer options. + var qnamakerOptions = { + scoreThreshold: DefaultThreshold, + top: DefaultTopN, + context: {} + }; + + // Set values for dialog responses. + var qnaDialogResponseOptions = { + noAnswer: DefaultNoAnswer, + activeLearningCardTitle: DefaultCardTitle, + cardNoMatchText: DefaultCardNoMatchText, + cardNoMatchResponse: DefaultCardNoMatchResponse + }; + + var dialogOptions = {}; + dialogOptions[QnAOptions] = qnamakerOptions; + dialogOptions[QnADialogResponseOptions] = qnaDialogResponseOptions; + + return await step.beginDialog(QNAMAKER_BASE_DIALOG, dialogOptions); + } +} + +module.exports.RootDialog = RootDialog; diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/index.js b/samples/javascript_nodejs/49.qnamaker-all-features/index.js new file mode 100644 index 0000000000..0ef28e75bb --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/index.js @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// index.js is used to setup and configure your bot + +// Import required packages +const path = require('path'); +const restify = require('restify'); + +// Import required bot services. See https://aka.ms/bot-services to learn more about the different parts of a bot. +const { BotFrameworkAdapter, ConversationState, MemoryStorage, UserState } = require('botbuilder'); +const { QnAMaker } = require('botbuilder-ai'); + +const { QnABot } = require('./bots/QnABot'); +const { RootDialog } = require('./dialogs/rootDialog'); + +// Note: Ensure you have a .env file and include QnAMakerKnowledgeBaseId, QnAMakerEndpointKey and QnAMakerHost. +const ENV_FILE = path.join(__dirname, '.env'); +require('dotenv').config({ path: ENV_FILE }); + +// Create HTTP server. +const server = restify.createServer(); +server.listen(process.env.port || process.env.PORT || 3978, function() { + console.log(`\n${ server.name } listening to ${ server.url }.`); + console.log('\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator'); + console.log('\nTo talk to your bot, open the emulator select "Open Bot"'); +}); + +// Create adapter. +// See https://aka.ms/about-bot-adapter to learn more about adapters. +const adapter = new BotFrameworkAdapter({ + appId: process.env.MicrosoftAppId, + appPassword: process.env.MicrosoftAppPassword +}); + +// Catch-all for errors. +adapter.onTurnError = async (context, error) => { + // This check writes out errors to console log .vs. app insights. + // NOTE: In production environment, you should consider logging this to Azure + // application insights. + console.error(`\n [onTurnError] unhandled error: ${ error }`); + + // Send a trace activity, which will be displayed in Bot Framework Emulator + await context.sendTraceActivity( + 'OnTurnError Trace', + `${ error }`, + 'https://www.botframework.com/schemas/error', + 'TurnError' + ); + + // Send a message to the user + await context.sendActivity('The bot encounted an error or bug.'); + await context.sendActivity('To continue to run this bot, please fix the bot source code.'); +}; + +// Define the state store for your bot. See https://aka.ms/about-bot-state to learn more about using MemoryStorage. +// A bot requires a state storage system to persist the dialog and user state between messages. +const memoryStorage = new MemoryStorage(); + +// Create conversation and user state with in-memory storage provider. +const conversationState = new ConversationState(memoryStorage); +const userState = new UserState(memoryStorage); + +var endpointHostName = process.env.QnAEndpointHostName; +if (!endpointHostName.startsWith('https://')) { + endpointHostName = 'https://' + endpointHostName; +} + +if (!endpointHostName.endsWith('/qnamaker')) { + endpointHostName = endpointHostName + '/qnamaker'; +} + +const qnaService = new QnAMaker({ + knowledgeBaseId: process.env.QnAKnowledgebaseId, + endpointKey: process.env.QnAEndpointKey, + host: endpointHostName +}); + +// Create the main dialog. +const dialog = new RootDialog(qnaService); + +// Create the bot's main handler. +const bot = new QnABot(conversationState, userState, dialog); + +// Listen for incoming requests. +server.post('/api/messages', (req, res) => { + adapter.processActivity(req, res, async (turnContext) => { + // Route the message to the bot's main handler. + await bot.run(turnContext); + }); +}); diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/package.json b/samples/javascript_nodejs/49.qnamaker-all-features/package.json new file mode 100644 index 0000000000..01270b05f0 --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/package.json @@ -0,0 +1,34 @@ +{ + "name": "qnamaker-bot", + "version": "1.0.0", + "description": "Bot Builder v4 using QnA Maker service with active learning and multi-turn experience.", + "author": "Microsoft", + "license": "MIT", + "main": "index.js", + "scripts": { + "start": "node ./index.js", + "watch": "nodemon ./index.js", + "lint": "./node_modules/.bin/eslint .", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/BotBuilder-Samples.git" + }, + "dependencies": { + "botbuilder": "~4.6.0", + "botbuilder-ai": "~4.6.0", + "botbuilder-dialogs": "~4.6.0", + "dotenv": "^8.2.0", + "restify": "^8.4.0" + }, + "devDependencies": { + "eslint": "^6.6.0", + "eslint-config-standard": "^14.1.0", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-node": "^10.0.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.1", + "nodemon": "~1.19.4" + } +} diff --git a/samples/javascript_nodejs/49.qnamaker-all-features/utils/qnaCardBuilder.js b/samples/javascript_nodejs/49.qnamaker-all-features/utils/qnaCardBuilder.js new file mode 100644 index 0000000000..f04eeac90a --- /dev/null +++ b/samples/javascript_nodejs/49.qnamaker-all-features/utils/qnaCardBuilder.js @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +const { CardFactory, ActionTypes } = require('botbuilder'); + +class QnACardBuilder { + /** + * Get multi-turn prompts card. + * @param {QnAMakerResult} result Result to be dispalyed as prompts. + * @param {string} cardNoMatchText No match text. + */ + static GetQnAPromptsCard(result) { + var cardActions = []; + result.context.prompts.forEach(prompt => { + cardActions.push({ + value: prompt.displayText, + type: ActionTypes.ImBack, + title: prompt.displayText + }); + }); + + var heroCard = CardFactory.heroCard('', result.answer, [], CardFactory.actions(cardActions)); + return { attachments: [heroCard] }; + } + + /** + * Get suggestion hero card to get user feedback. + * @param {Array} suggestionList A list of suggested questions strings. + * @param {string} cardTitle Title of the card. + * @param {string} cardNoMatchText No match text. + */ + static GetSuggestionCard(suggestionList, cardTitle, cardNoMatchText) { + var cardActions = []; + suggestionList.forEach(element => { + cardActions.push({ + value: element, + type: ActionTypes.ImBack, + title: element + }); + }); + + cardActions.push({ + value: cardNoMatchText, + type: ActionTypes.ImBack, + title: cardNoMatchText + }); + + var heroCard = CardFactory.heroCard( + '', + cardTitle, + [], + CardFactory.actions(cardActions)); + + return { attachments: [heroCard] }; + } +} + +module.exports.QnACardBuilder = QnACardBuilder;