diff --git a/samples/14.nlp-with-dispatch/.vscode/settings.json b/samples/14.nlp-with-dispatch/.vscode/settings.json new file mode 100644 index 000000000..e0f15db2e --- /dev/null +++ b/samples/14.nlp-with-dispatch/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/LICENSE b/samples/14.nlp-with-dispatch/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/14.nlp-with-dispatch/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/samples/14.nlp-with-dispatch/README.md b/samples/14.nlp-with-dispatch/README.md new file mode 100644 index 000000000..ef05e09ea --- /dev/null +++ b/samples/14.nlp-with-dispatch/README.md @@ -0,0 +1,103 @@ +# NLP with Dispatch + +Bot Framework v4 NLP with Dispatch bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that relies on multiple [LUIS.ai](https://www.luis.ai) and [QnAMaker.ai](https://www.qnamaker.ai) models for natural language processing (NLP). + +Use the Dispatch model in cases when: + +- Your bot consists of multiple language modules (LUIS + QnA) and you need assistance in routing user's utterances to these modules in order to integrate the different modules into your bot. +- Evaluate quality of intents classification of a single LUIS model. +- Create a text classification model from text files. + +This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +### Use Dispatch with Multiple LUIS and QnA Models + +To learn how to configure Dispatch with multiple LUIS models and QnA Maker services, refer to the steps found [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-tutorial-dispatch?view=azure-bot-service-4.0). + + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\nlp-with-dispatch-sample.jar` + +- Test 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` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## 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) +- [Using LUIS for Language Understanding](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=js) +- [LUIS documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/LUIS/) +- [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) +- [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/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "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": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "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')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "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 Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "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": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$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": "S1", + "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": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "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'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "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 Linux 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')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "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'))]" + ] + } + ] +} diff --git a/samples/14.nlp-with-dispatch/pom.xml b/samples/14.nlp-with-dispatch/pom.xml new file mode 100644 index 000000000..34e336096 --- /dev/null +++ b/samples/14.nlp-with-dispatch/pom.xml @@ -0,0 +1,248 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + nlp-with-dispatch + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java NLP with Dispatch sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.nlpwithdispatch.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-ai-luis-v3 + 4.6.0-preview9 + + + com.microsoft.bot + bot-ai-qna + 4.6.0-preview9 + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.nlpwithdispatch.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java new file mode 100644 index 000000000..5efd67782 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.nlpwithdispatch.bots.DispatchBot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(BotServices botServices) { + return new DispatchBot(botServices); + } + + @Bean + public BotServices getBotServices(Configuration configuration) { + return new BotServicesImpl(configuration); + } + + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java new file mode 100644 index 000000000..21d585047 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java @@ -0,0 +1,12 @@ +package com.microsoft.bot.sample.nlpwithdispatch; + +import com.microsoft.bot.ai.luis.LuisRecognizer; +import com.microsoft.bot.ai.qna.QnAMaker; + +public interface BotServices { + + LuisRecognizer getDispatch(); + + QnAMaker getSampleQnA(); + +} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java new file mode 100644 index 000000000..f94fdb927 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import com.microsoft.bot.ai.luis.LuisApplication; +import com.microsoft.bot.ai.luis.LuisRecognizer; +import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; +import com.microsoft.bot.ai.qna.QnAMaker; +import com.microsoft.bot.ai.qna.QnAMakerEndpoint; +import com.microsoft.bot.integration.Configuration; + +public class BotServicesImpl implements BotServices { + + private LuisRecognizer dispatch; + + private QnAMaker sampleQnA; + + public BotServicesImpl(Configuration configuration) { + // Read the setting for cognitive services (LUS, QnA) from the application.properties file. + // If includeApiResults instanceof set to true, the full response from the LUS api (LuisResult) + // will be made available in the properties collection of the RecognizerResult + + LuisApplication luisApplication = new LuisApplication( + configuration.getProperty("LuisAppId"), + configuration.getProperty("LuisAPIKey"), + String.format("https://%s.api.cognitive.microsoft.com", + configuration.getProperty("LuisAPIHostName"))); + + // Set the recognizer options depending on which endpoint version you want to use. + // More details can be found in https://docs.getmicrosoft().com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 + LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); + recognizerOptions.setIncludeAPIResults(true); + recognizerOptions.setIncludeAllIntents(true); + recognizerOptions.setIncludeInstanceData(true); + + dispatch = new LuisRecognizer(recognizerOptions); + + QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint(); + qnaMakerEndpoint.setKnowledgeBaseId(configuration.getProperty("QnAKnowledgebaseId")); + qnaMakerEndpoint.setEndpointKey(configuration.getProperty("QnAEndpointKey")); + qnaMakerEndpoint.setHost(configuration.getProperty("QnAEndpointHostName")); + + sampleQnA = new QnAMaker(qnaMakerEndpoint, null); + } + + /** + * @return the Dispatch value as a LuisRecognizer. + */ + public LuisRecognizer getDispatch() { + return this.dispatch; + } + + /** + * @return the SampleQnA value as a QnAMaker. + */ + public QnAMaker getSampleQnA() { + return this.sampleQnA; + } +} + diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java new file mode 100644 index 000000000..8f4f36235 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import java.util.List; + +public class Intent { + private String name; + private Double score; + private List childIntents; + private String topIntent; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getScore() { + return this.score; + } + + public void setScore(Double score) { + this.score = score; + } + + public List getChildIntents() { + return this.childIntents; + } + + public void setChildIntents(List childIntents) { + this.childIntents = childIntents; + } + + public String getTopIntent() { + return this.topIntent; + } + + public void setTopIntent(String topIntent) { + this.topIntent = topIntent; + } + +} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java new file mode 100644 index 000000000..f331d57d3 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import java.util.List; + +public class PredictionResult { + private String topIntent; + private List intents; + + public String getTopIntent() { + return this.topIntent; + } + + public void setTopIntent(String topIntent) { + this.topIntent = topIntent; + } + + public List getIntents() { + return this.intents; + } + + public void setIntents(List intents) { + this.intents = intents; + } +} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java new file mode 100644 index 000000000..6efd6de3c --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.nlpwithdispatch.bots; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.RecognizerResult.NamedIntentScore; +import com.microsoft.bot.sample.nlpwithdispatch.BotServices; +import com.microsoft.bot.sample.nlpwithdispatch.Intent; +import com.microsoft.bot.sample.nlpwithdispatch.PredictionResult; +import com.microsoft.bot.schema.ChannelAccount; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DispatchBot extends ActivityHandler { + + private final Logger _logger; + private final BotServices _botServices; + + public DispatchBot(BotServices botServices) { + _logger = LoggerFactory.getLogger(DispatchBot.class); + _botServices = botServices; + } + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + // First, we use the dispatch model to determine which cognitive service (LUS or + // QnA) to use. + RecognizerResult recognizerResult = _botServices.getDispatch().recognize(turnContext).join(); + + // Top intent tell us which cognitive service to use. + NamedIntentScore topIntent = recognizerResult.getTopScoringIntent(); + + // Next, we call the dispatcher with the top intent. + return dispatchToTopIntent(turnContext, topIntent.intent, recognizerResult); + } + + @Override + protected CompletableFuture onMembersAdded(List membersAdded, TurnContext turnContext) { + final String WelcomeText = "Type a greeting, or a question about the weather to get started."; + + return membersAdded.stream() + .filter(member -> !StringUtils.equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(member -> { + String msg = String.format("Welcome to Dispatch bot %s. %s", member.getName(), WelcomeText); + return turnContext.sendActivity(MessageFactory.text(msg)); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponses -> null); + } + + private CompletableFuture dispatchToTopIntent( + TurnContext turnContext, + String intent, + RecognizerResult recognizerResult + ) { + switch (intent) { + case "l_HomeAutomation": + return processHomeAutomation(turnContext, recognizerResult); + + case "l_Weather": + return processWeather(turnContext, recognizerResult); + + case "q_sample-qna": + return processSampleQnA(turnContext); + + default: + _logger.info(String.format("Dispatch unrecognized intent: %s.", intent)); + return turnContext + .sendActivity(MessageFactory.text(String.format("Dispatch unrecognized intent: %s.", intent))) + .thenApply(result -> null); + } + } + + private CompletableFuture processHomeAutomation(TurnContext turnContext, RecognizerResult luisResult) { + _logger.info("ProcessHomeAutomationAsync"); + + // Retrieve LUIS result for Process Automation. + PredictionResult predictionResult = mapPredictionResult(luisResult.getProperties().get("luisResult")); + + Intent topIntent = predictionResult.getIntents().get(0); + return turnContext + .sendActivity(MessageFactory.text(String.format("HomeAutomation top intent %s.", topIntent.getTopIntent()))) + .thenCompose(sendResult -> { + List intents = + topIntent.getChildIntents().stream().map(x -> x.getName()).collect(Collectors.toList()); + return turnContext + .sendActivity( + MessageFactory + .text(String.format("HomeAutomation intents detected:\n\n%s", String.join("\n\n", intents))) + ) + .thenCompose(nextSendResult -> { + if (luisResult.getEntities() != null) { + List entities = mapEntities(luisResult.getEntities()); + if (entities.size() > 0) { + return turnContext + .sendActivity( + MessageFactory.text( + String.format( + "HomeAutomation entities were found in the message:\n\n%s", + String.join("\n\n", entities) + ) + ) + ) + .thenApply(finalSendResult -> null); + } + } + return CompletableFuture.completedFuture(null); + }); + }); + } + + private List mapEntities(JsonNode entityNode) { + List entities = new ArrayList(); + for (Iterator> child = entityNode.fields(); child.hasNext();) { + Map.Entry childIntent = child.next(); + String childName = childIntent.getKey(); + if (!childName.startsWith("$")) { + entities.add(childIntent.getValue().get(0).asText()); + } + } + return entities; + } + + private PredictionResult mapPredictionResult(JsonNode luisResult) { + JsonNode prediction = luisResult.get("prediction"); + JsonNode intentsObject = prediction.get("intents"); + if (intentsObject == null) { + return null; + } + PredictionResult result = new PredictionResult(); + result.setTopIntent(prediction.get("topIntent").asText()); + List intents = new ArrayList(); + for (Iterator> it = intentsObject.fields(); it.hasNext();) { + Map.Entry intent = it.next(); + double score = intent.getValue().get("score").asDouble(); + String intentName = intent.getKey().replace(".", "_").replace(" ", "_"); + Intent newIntent = new Intent(); + newIntent.setName(intentName); + newIntent.setScore(score); + JsonNode childNode = intent.getValue().get("childApp"); + if (childNode != null) { + newIntent.setTopIntent(childNode.get("topIntent").asText()); + List childIntents = new ArrayList(); + JsonNode childIntentNodes = childNode.get("intents"); + for (Iterator> child = childIntentNodes.fields(); child.hasNext();) { + Map.Entry childIntent = child.next(); + double childScore = childIntent.getValue().get("score").asDouble(); + String childIntentName = childIntent.getKey(); + Intent newChildIntent = new Intent(); + newChildIntent.setName(childIntentName); + newChildIntent.setScore(childScore); + childIntents.add(newChildIntent); + } + newIntent.setChildIntents(childIntents); + } + + intents.add(newIntent); + } + result.setIntents(intents); + return result; + } + + private CompletableFuture processWeather(TurnContext turnContext, RecognizerResult luisResult) { + _logger.info("ProcessWeatherAsync"); + + // Retrieve LUIS result for Process Automation. + PredictionResult predictionResult = mapPredictionResult(luisResult.getProperties().get("luisResult")); + + Intent topIntent = predictionResult.getIntents().get(0); + return turnContext + .sendActivity(MessageFactory.text(String.format("ProcessWeather top intent %s.", topIntent.getTopIntent()))) + .thenCompose(sendResult -> { + List intents = + topIntent.getChildIntents().stream().map(x -> x.getName()).collect(Collectors.toList()); + return turnContext + .sendActivity( + MessageFactory + .text(String.format("ProcessWeather Intents detected:\n\n%s", String.join("\n\n", intents))) + ) + .thenCompose(secondResult -> { + if (luisResult.getEntities() != null) { + List entities = mapEntities(luisResult.getEntities()); + if (entities.size() > 0) { + return turnContext + .sendActivity( + MessageFactory.text( + String.format( + "ProcessWeather entities were found in the message:\n\n%s", + String.join("\n\n", entities) + ) + ) + ) + .thenApply(finalResult -> null); + } + } + return CompletableFuture.completedFuture(null); + }); + }); + } + + private CompletableFuture processSampleQnA(TurnContext turnContext) { + _logger.info("ProcessSampleQnAAsync"); + + return _botServices.getSampleQnA().getAnswers(turnContext, null).thenCompose(results -> { + if (results.length > 0) { + return turnContext.sendActivity(MessageFactory.text(results[0].getAnswer())).thenApply(result -> null); + } else { + return turnContext + .sendActivity(MessageFactory.text("Sorry, could not find an answer in the Q and A system.")) + .thenApply(result -> null); + } + }); + } +} diff --git a/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json b/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json new file mode 100644 index 000000000..245daedce --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json @@ -0,0 +1,605 @@ +{ + "luis_schema_version": "3.2.0", + "versionId": "0.1", + "name": "Home Automation", + "desc": "Home Automation LUIS application - Bot Builder Samples", + "culture": "en-us", + "tokenizerVersion": "1.0.0", + "intents": [ + { + "name": "HomeAutomation" + }, + { + "name": "None" + } + ], + "entities": [ + { + "name": "Device", + "roles": [] + }, + { + "name": "deviceProperty", + "roles": [] + }, + { + "name": "Room", + "roles": [] + } + ], + "composites": [], + "closedLists": [ + { + "name": "Operation", + "subLists": [ + { + "canonicalForm": "off", + "list": [ + "off", + "turn off", + "switch off", + "lock", + "out", + "shut down", + "stop" + ] + }, + { + "canonicalForm": "on", + "list": [ + "on", + "turn on", + "switch on", + "unlock", + "un lock", + "boot up", + "start" + ] + } + ], + "roles": [] + } + ], + "patternAnyEntities": [ + { + "name": "Device_PatternAny", + "roles": [], + "explicitList": [] + }, + { + "name": "Room_PatternAny", + "roles": [], + "explicitList": [] + } + ], + "regex_entities": [], + "prebuiltEntities": [ + { + "name": "number", + "roles": [] + } + ], + "model_features": [], + "regex_features": [], + "patterns": [ + { + "pattern": "{Device_PatternAny} in {Room_PatternAny} on [please]", + "intent": "HomeAutomation" + }, + { + "pattern": "{Device_PatternAny} on [please]", + "intent": "HomeAutomation" + }, + { + "pattern": "turn off {Device_PatternAny} in {Room_PatternAny}", + "intent": "HomeAutomation" + }, + { + "pattern": "turn on {Device_PatternAny} in {Room_PatternAny}", + "intent": "HomeAutomation" + }, + { + "pattern": "turn on {Device_PatternAny}", + "intent": "HomeAutomation" + }, + { + "pattern": "{Device_PatternAny} in {Room_PatternAny} off [please]", + "intent": "HomeAutomation" + }, + { + "pattern": "{Device_PatternAny} off [please]", + "intent": "HomeAutomation" + } + ], + "utterances": [ + { + "text": "breezeway on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 0, + "endPos": 8 + } + ] + }, + { + "text": "change temperature to seventy two degrees", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "deviceProperty", + "startPos": 7, + "endPos": 17 + } + ] + }, + { + "text": "coffee bar on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 0, + "endPos": 9 + } + ] + }, + { + "text": "decrease temperature for me please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "deviceProperty", + "startPos": 9, + "endPos": 19 + } + ] + }, + { + "text": "dim kitchen lights to 25 .", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 4, + "endPos": 10 + }, + { + "entity": "Device", + "startPos": 12, + "endPos": 17 + } + ] + }, + { + "text": "fish pond off please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 0, + "endPos": 8 + } + ] + }, + { + "text": "fish pond on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 0, + "endPos": 8 + } + ] + }, + { + "text": "illuminate please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 0, + "endPos": 9 + } + ] + }, + { + "text": "living room lamp on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 0, + "endPos": 10 + }, + { + "entity": "Device", + "startPos": 12, + "endPos": 15 + } + ] + }, + { + "text": "living room lamps off please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 0, + "endPos": 10 + }, + { + "entity": "Device", + "startPos": 12, + "endPos": 16 + } + ] + }, + { + "text": "lock the doors for me please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 9, + "endPos": 13 + } + ] + }, + { + "text": "lower your volume", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "deviceProperty", + "startPos": 11, + "endPos": 16 + } + ] + }, + { + "text": "make camera 1 off please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 5, + "endPos": 12 + } + ] + }, + { + "text": "make some coffee", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 10, + "endPos": 15 + } + ] + }, + { + "text": "play dvd", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 5, + "endPos": 7 + } + ] + }, + { + "text": "set lights out in bedroom", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 4, + "endPos": 9 + }, + { + "entity": "Room", + "startPos": 18, + "endPos": 24 + } + ] + }, + { + "text": "set lights to bright", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 4, + "endPos": 9 + }, + { + "entity": "deviceProperty", + "startPos": 14, + "endPos": 19 + } + ] + }, + { + "text": "set lights to concentrate", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 4, + "endPos": 9 + }, + { + "entity": "deviceProperty", + "startPos": 14, + "endPos": 24 + } + ] + }, + { + "text": "shut down my work computer", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 18, + "endPos": 25 + } + ] + }, + { + "text": "snap switch fan fifty percent", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 12, + "endPos": 14 + } + ] + }, + { + "text": "start master bedroom light.", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 6, + "endPos": 19 + }, + { + "entity": "Device", + "startPos": 21, + "endPos": 25 + } + ] + }, + { + "text": "theater on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 0, + "endPos": 6 + } + ] + }, + { + "text": "turn dimmer off", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 5, + "endPos": 10 + } + ] + }, + { + "text": "turn off ac please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 9, + "endPos": 10 + } + ] + }, + { + "text": "turn off foyer lights", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 9, + "endPos": 13 + }, + { + "entity": "Device", + "startPos": 15, + "endPos": 20 + } + ] + }, + { + "text": "turn off living room light", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 9, + "endPos": 19 + }, + { + "entity": "Device", + "startPos": 21, + "endPos": 25 + } + ] + }, + { + "text": "turn off staircase", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 9, + "endPos": 17 + } + ] + }, + { + "text": "turn off venice lamp", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 16, + "endPos": 19 + } + ] + }, + { + "text": "turn on bathroom heater", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 8, + "endPos": 15 + }, + { + "entity": "Device", + "startPos": 17, + "endPos": 22 + } + ] + }, + { + "text": "turn on external speaker", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 8, + "endPos": 23 + } + ] + }, + { + "text": "turn on kitchen faucet", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 8, + "endPos": 14 + }, + { + "entity": "Device", + "startPos": 16, + "endPos": 21 + } + ] + }, + { + "text": "turn on light in bedroom", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 8, + "endPos": 12 + }, + { + "entity": "Room", + "startPos": 17, + "endPos": 23 + } + ] + }, + { + "text": "turn on my bedroom lights.", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 11, + "endPos": 17 + }, + { + "entity": "Device", + "startPos": 19, + "endPos": 24 + } + ] + }, + { + "text": "turn on the furnace room lights", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 12, + "endPos": 23 + }, + { + "entity": "Device", + "startPos": 25, + "endPos": 30 + } + ] + }, + { + "text": "turn on the internet in my bedroom please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 27, + "endPos": 33 + } + ] + }, + { + "text": "turn on thermostat please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 8, + "endPos": 17 + } + ] + }, + { + "text": "turn the fan to high", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 9, + "endPos": 11 + } + ] + }, + { + "text": "turn thermostat on 70.", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 5, + "endPos": 14 + } + ] + } + ], + "settings": [] +} \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv b/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv new file mode 100644 index 000000000..5f42eaf85 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv @@ -0,0 +1,11 @@ +Question Answer Source Metadata +hi Hello! QnAMaker.tsv +greetings Hello! QnAMaker.tsv +good morning Hello! QnAMaker.tsv +good evening Hello! QnAMaker.tsv +What are you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +What? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +What do you do? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +Who are you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +What is your name? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +What should I call you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/resources/Weather.json b/samples/14.nlp-with-dispatch/src/main/resources/Weather.json new file mode 100644 index 000000000..c532ee35d --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/Weather.json @@ -0,0 +1,315 @@ +{ + "luis_schema_version": "3.2.0", + "name": "Weather", + "versionId": "0.1", + "desc": "Weather LUIS application - Bot Builder Samples", + "culture": "en-us", + "intents": [ + { + "name": "Get Weather Condition" + }, + { + "name": "Get Weather Forecast" + }, + { + "name": "None" + } + ], + "entities": [ + { + "name": "Location", + "roles": [] + } + ], + "closedLists": [], + "composites": [], + "patternAnyEntities": [ + { + "name": "Location_PatternAny", + "explicitList": [], + "roles": [] + } + ], + "regex_entities": [], + "prebuiltEntities": [], + "regex_features": [], + "model_features": [], + "patterns": [ + { + "pattern": "weather in {Location_PatternAny}", + "intent": "Get Weather Condition" + }, + { + "pattern": "how's the weather in {Location_PatternAny}", + "intent": "Get Weather Condition" + }, + { + "pattern": "current weather in {Location_PatternAny}", + "intent": "Get Weather Condition" + }, + { + "pattern": "what's the forecast for next week in {Location_PatternAny}", + "intent": "Get Weather Forecast" + }, + { + "pattern": "show me the forecast for {Location_PatternAny}", + "intent": "Get Weather Forecast" + }, + { + "pattern": "what's the forecast for {Location_PatternAny}", + "intent": "Get Weather Forecast" + } + ], + "utterances": [ + { + "text": "current weather ?", + "intent": "Get Weather Condition", + "entities": [] + }, + { + "text": "do florida residents usually need ice scrapers", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 3, + "endPos": 9, + "entity": "Location" + } + ] + }, + { + "text": "forecast in celcius", + "intent": "Get Weather Forecast", + "entities": [] + }, + { + "text": "get florence temperature in september", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 4, + "endPos": 11, + "entity": "Location" + } + ] + }, + { + "text": "get for me the weather conditions in sonoma county", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 37, + "endPos": 49, + "entity": "Location" + } + ] + }, + { + "text": "get the daily temperature greenwood indiana", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 26, + "endPos": 42, + "entity": "Location" + } + ] + }, + { + "text": "get the forcast for me", + "intent": "Get Weather Forecast", + "entities": [] + }, + { + "text": "get the weather at saint george utah", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 19, + "endPos": 35, + "entity": "Location" + } + ] + }, + { + "text": "how much rain does chambersburg get a year", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 19, + "endPos": 30, + "entity": "Location" + } + ] + }, + { + "text": "i want to know the temperature at death valley", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 34, + "endPos": 45, + "entity": "Location" + } + ] + }, + { + "text": "provide me by toronto weather please", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 14, + "endPos": 20, + "entity": "Location" + } + ] + }, + { + "text": "show average rainfall for boise", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 26, + "endPos": 30, + "entity": "Location" + } + ] + }, + { + "text": "show me the forecast at alabama", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 24, + "endPos": 30, + "entity": "Location" + } + ] + }, + { + "text": "soliciting today's weather", + "intent": "Get Weather Forecast", + "entities": [] + }, + { + "text": "temperature of delhi in celsius please", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 15, + "endPos": 19, + "entity": "Location" + } + ] + }, + { + "text": "was last year about this time as wet as it is now in the south ?", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 57, + "endPos": 61, + "entity": "Location" + } + ] + }, + { + "text": "what is the rain volume in sonoma county ?", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 27, + "endPos": 39, + "entity": "Location" + } + ] + }, + { + "text": "what is the weather in redmond ?", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 23, + "endPos": 29, + "entity": "Location" + } + ] + }, + { + "text": "what is the weather today at 10 day durham ?", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 36, + "endPos": 41, + "entity": "Location" + } + ] + }, + { + "text": "what to wear in march in california", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 25, + "endPos": 34, + "entity": "Location" + } + ] + }, + { + "text": "what will the weather be tomorrow in new york ?", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 37, + "endPos": 44, + "entity": "Location" + } + ] + }, + { + "text": "what's the weather going to be like in hawaii ?", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 39, + "endPos": 44, + "entity": "Location" + } + ] + }, + { + "text": "what's the weather like in minneapolis", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 27, + "endPos": 37, + "entity": "Location" + } + ] + }, + { + "text": "will it be raining in ranchi", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 22, + "endPos": 27, + "entity": "Location" + } + ] + }, + { + "text": "will it rain this weekend", + "intent": "Get Weather Forecast", + "entities": [] + }, + { + "text": "will it snow today", + "intent": "Get Weather Forecast", + "entities": [] + } + ] +} diff --git a/samples/14.nlp-with-dispatch/src/main/resources/application.properties b/samples/14.nlp-with-dispatch/src/main/resources/application.properties new file mode 100644 index 000000000..5e1dbd85c --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/application.properties @@ -0,0 +1,11 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 + +QnAKnowledgebaseId= +QnAEndpointKey= +QnAEndpointHostName= + +LuisAppId = +LuisAPIKey= +LuisAPIHostName= diff --git a/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json b/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF b/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml b/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/index.html b/samples/14.nlp-with-dispatch/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot 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
+
+
+
+
+ +
+ + + diff --git a/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java b/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java new file mode 100644 index 000000000..b2491036f --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +}