diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/resources/parameters.template.json b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/resources/parameters.template.json index 0648ff5a49..95841eb589 100644 --- a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/resources/parameters.template.json +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/resources/parameters.template.json @@ -1,12 +1,24 @@ { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appInsightsLocation": { - "value": "westus2" - }, - "luisServiceLocation": { - "value": "westus" + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "useCosmosDb": { + "value": false + }, + "useStorage": { + "value": false + }, + "appServicePlanSku": { + "value": { + "tier": "Free", + "name": "F1" } + }, + "botServiceSku": { + "value": "F0" + }, + "luisServiceSku": { + "value": "F0" } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/resources/template.json b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/resources/template.json index 99c8e0c75a..39c3559343 100644 --- a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/resources/template.json +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/resources/template.json @@ -10,23 +10,35 @@ "type": "string", "defaultValue": "[resourceGroup().location]" }, + "suffix": { + "type": "string", + "defaultValue": "[take(uniqueString(resourceGroup().id), 7)]" + }, "microsoftAppId": { "type": "string" }, "microsoftAppPassword": { "type": "string" }, + "useCosmosDb": { + "type": "bool", + "defaultValue": true + }, "cosmosDbName": { "type": "string", - "defaultValue": "[toLower(parameters('name'))]" + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" + }, + "useStorage": { + "type": "bool", + "defaultValue": true }, "storageAccountName": { "type": "string", - "defaultValue": "[toLower(parameters('name'))]" + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" }, "appServicePlanName": { "type": "string", - "defaultValue": "[parameters('name')]" + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" }, "appServicePlanSku": { "type": "object", @@ -37,19 +49,19 @@ }, "appInsightsName": { "type": "string", - "defaultValue": "[parameters('name')]" + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" }, "appInsightsLocation": { "type": "string", - "defaultValue": "westus2" + "defaultValue": "[resourceGroup().location]" }, "botWebAppName": { "type": "string", - "defaultValue": "[parameters('name')]" + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" }, "botServiceName": { "type": "string", - "defaultValue": "[parameters('name')]" + "defaultValue": "[concat(parameters('name'), '-', parameters('suffix'))]" }, "botServiceSku": { "type": "string", @@ -57,7 +69,7 @@ }, "luisServiceName": { "type": "string", - "defaultValue": "[concat(parameters('name'), '-luis')]" + "defaultValue": "[concat(parameters('name'), '-luis-', parameters('suffix'))]" }, "luisServiceSku": { "type": "string", @@ -69,8 +81,10 @@ } }, "variables": { - "botEndpoint": "[concat('https://', toLower(parameters('botWebAppName')), '.azurewebsites.net/api/messages')]", - "cleanStorageAccountName": "[toLower(take(replace(replace(parameters('storageAccountName'), '-', ''), '_', ''), 24))]" + "botWebAppName": "[replace(parameters('botWebAppName'), '_', '')]", + "storageAccountName": "[toLower(take(replace(replace(parameters('storageAccountName'), '-', ''), '_', ''), 24))]", + "cosmosDbAccountName": "[toLower(take(replace(parameters('cosmosDbName'), '_', ''), 31))]", + "botEndpoint": "[concat('https://', toLower(variables('botWebAppName')), '.azurewebsites.net/api/messages')]" }, "resources": [ { @@ -91,7 +105,7 @@ "type": "Microsoft.DocumentDB/databaseAccounts", "kind": "GlobalDocumentDB", "apiVersion": "2015-04-08", - "name": "[parameters('cosmosDbName')]", + "name": "[variables('cosmosDbAccountName')]", "location": "[parameters('location')]", "properties": { "databaseAccountOfferType": "Standard", @@ -101,18 +115,20 @@ "failoverPriority": 0 } ] - } - }, + }, + "condition": "[parameters('useCosmosDb')]" + }, { "comments": "storage account", "type": "Microsoft.Storage/storageAccounts", "kind": "StorageV2", "apiVersion": "2018-07-01", - "name": "[variables('cleanStorageAccountName')]", + "name": "[variables('storageAccountName')]", "location": "[parameters('location')]", "sku": { "name": "Standard_LRS" - } + }, + "condition": "[parameters('useStorage')]" }, { "comments": "app service plan", @@ -138,7 +154,7 @@ "comments": "bot web app", "type": "Microsoft.Web/sites", "apiVersion": "2018-02-01", - "name": "[parameters('botWebAppName')]", + "name": "[variables('botWebAppName')]", "location": "[parameters('location')]", "properties": { "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]", @@ -185,7 +201,7 @@ "displayName": "[parameters('botServiceName')]", "endpoint": "[variables('botEndpoint')]", "msaAppId": "[parameters('microsoftAppId')]", - "developerAppInsightKey": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).InstrumentationKey]", + "developerAppInsightKey": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).instrumentationKey]", "developerAppInsightsApplicationId": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).ApplicationId]" } }, @@ -202,25 +218,28 @@ } ], "outputs": { + "botWebAppName": { + "type": "string", + "value": "[variables('botWebAppName')]" + }, "appInsights": { "type": "object", "value": { - "appId": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).AppId]", - "instrumentationKey": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).InstrumentationKey]" + "instrumentationKey": "[reference(resourceId('Microsoft.Insights/components', parameters('appInsightsName'))).instrumentationKey]" } }, - "storage": { + "blobStorage": { "type": "object", "value": { - "connectionString": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('cleanStorageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('cleanStorageAccountName')), '2018-07-01').keys[0].value, ';EndpointSuffix=core.windows.net')]", + "connectionString": "[if(parameters('useStorage'), concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2018-07-01').keys[0].value, ';EndpointSuffix=core.windows.net'), '')]", "container": "transcripts" } }, "cosmosDb": { "type": "object", "value": { - "cosmosDBEndpoint": "[reference(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDbName'))).documentEndpoint]", - "authkey": "[listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', parameters('cosmosDbName')), '2015-04-08').primaryMasterKey]", + "cosmosDBEndpoint": "[if(parameters('useCosmosDb'), reference(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmosDbAccountName'))).documentEndpoint, '')]", + "authKey": "[if(parameters('useCosmosDb'), listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmosDbAccountName')), '2015-04-08').primaryMasterKey, '')]", "databaseId": "botstate-db", "collectionId": "botstate-collection" } @@ -228,6 +247,8 @@ "luis": { "type": "object", "value": { + "accountName": "[parameters('luisServiceName')]", + "region": "[parameters('luisServiceLocation')]", "key": "[listKeys(resourceId('Microsoft.CognitiveServices/accounts', parameters('luisServiceName')),'2017-04-18').key1]" } } diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/deploy.ps1 b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/deploy.ps1 index 8562543bb7..b00b4ec4ce 100644 --- a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/deploy.ps1 +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/deploy.ps1 @@ -22,6 +22,13 @@ else { New-Item -Path $logFile | Out-Null } +if (-not (Test-Path (Join-Path $projDir 'appsettings.json'))) +{ + Write-Host "! Could not find an 'appsettings.json' file in the current directory." -ForegroundColor DarkRed + Write-Host "+ Please re-run this script from your project directory." -ForegroundColor Magenta + Break +} + # Get mandatory parameters if (-not $name) { $name = Read-Host "? Bot Name (used as default name for resource group and deployed resources)" @@ -72,9 +79,10 @@ if (-not $appId) { # Create app registration $app = (az ad app create ` --display-name $name ` - --password $appPassword ` + --password `"$($appPassword)`" ` --available-to-other-tenants ` - --reply-urls 'https://token.botframework.com/.auth/web/redirect') + --reply-urls 'https://token.botframework.com/.auth/web/redirect' ` + --output json) # Retrieve AppId if ($app) { @@ -94,18 +102,20 @@ $timestamp = Get-Date -f MMddyyyyHHmmss # Create resource group Write-Host "> Creating resource group ..." -(az group create --name $resourceGroup --location $location) 2>> $logFile | Out-Null +(az group create --name $resourceGroup --location $location --output json) 2>> $logFile | Out-Null # Deploy Azure services (deploys LUIS, QnA Maker, Content Moderator, CosmosDB) if ($parametersFile) { Write-Host "> Validating Azure deployment ..." $validation = az group deployment validate ` - --resource-group $resourceGroup ` - --template-file "$(Join-Path $PSScriptRoot '..' 'resources' 'template.json')" ` + --resource-group $resourcegroup ` + --template-file "$(Join-Path $PSScriptRoot '..' 'resources' 'template.json')" ` --parameters "@$($parametersFile)" ` - --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" + --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" ` + --output json if ($validation) { + $validation >> $logFile $validation = $validation | ConvertFrom-Json if (-not $validation.error) { @@ -115,11 +125,13 @@ if ($parametersFile) { --resource-group $resourceGroup ` --template-file "$(Join-Path $PSScriptRoot '..' 'resources' 'template.json')" ` --parameters "@$($parametersFile)" ` - --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" + --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" ` + --output json } else { - Write-Host "! Template is not valid with provided parameters." -ForegroundColor DarkRed + Write-Host "! Template is not valid with provided parameters. Review the log for more information." -ForegroundColor DarkRed Write-Host "! Error: $($validation.error.message)" -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed Write-Host "+ To delete this resource group, run 'az group delete -g $($resourceGroup) --no-wait'" -ForegroundColor Magenta Break } @@ -128,11 +140,13 @@ if ($parametersFile) { else { Write-Host "> Validating Azure deployment ..." $validation = az group deployment validate ` - --resource-group $resourceGroup ` - --template-file "$(Join-Path $PSScriptRoot '..' 'resources' 'template.json')" ` - --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" + --resource-group $resourcegroup ` + --template-file "$(Join-Path $PSScriptRoot '..' 'resources' 'template.json')" ` + --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" ` + --output json if ($validation) { + $validation >> $logFile $validation = $validation | ConvertFrom-Json if (-not $validation.error) { @@ -141,11 +155,13 @@ else { --name $timestamp ` --resource-group $resourceGroup ` --template-file "$(Join-Path $PSScriptRoot '..' 'resources' 'template.json')" ` - --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" + --parameters name=$name microsoftAppId=$appId microsoftAppPassword="`"$($appPassword)`"" ` + --output json } else { - Write-Host "! Template is not valid with provided parameters." -ForegroundColor DarkRed - Write-Host "! Error: $($validation.error.message)" -ForegroundColor DarkRed + Write-Host "! Template is not valid with provided parameters. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Error: $($validation.error.message)" -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed Write-Host "+ To delete this resource group, run 'az group delete -g $($resourceGroup) --no-wait'" -ForegroundColor Magenta Break } @@ -156,7 +172,8 @@ else { $outputs = (az group deployment show ` --name $timestamp ` --resource-group $resourceGroup ` - --query properties.outputs) 2>> $logFile + --query properties.outputs ` + --output json) 2>> $logFile # If it succeeded then we perform the remainder of the steps if ($outputs) @@ -164,6 +181,8 @@ if ($outputs) # Log and convert to JSON $outputs >> $logFile $outputs = $outputs | ConvertFrom-Json + $outputMap = @{} + $outputs.PSObject.Properties | Foreach-Object { $outputMap[$_.Name] = $_.Value } # Update appsettings.json Write-Host "> Updating appsettings.json ..." @@ -176,27 +195,24 @@ if ($outputs) $settings | Add-Member -Type NoteProperty -Force -Name 'microsoftAppId' -Value $appId $settings | Add-Member -Type NoteProperty -Force -Name 'microsoftAppPassword' -Value $appPassword - if ($outputs.appInsights) { $settings | Add-Member -Type NoteProperty -Force -Name 'appInsights' -Value $outputs.appInsights.value } - if ($outputs.storage) { $settings | Add-Member -Type NoteProperty -Force -Name 'blobStorage' -Value $outputs.storage.value } - if ($outputs.cosmosDb) { $settings | Add-Member -Type NoteProperty -Force -Name 'cosmosDb' -Value $outputs.cosmosDb.value } - if ($outputs.contentModerator) { $settings | Add-Member -Type NoteProperty -Force -Name 'contentModerator' -Value $outputs.contentModerator.value } - + foreach ($key in $outputMap.Keys) { $settings | Add-Member -Type NoteProperty -Force -Name $key -Value $outputMap[$key].value } $settings | ConvertTo-Json -depth 100 | Out-File $(Join-Path $projDir appsettings.json) + if ($outputs.qnaMaker.value.key) { $qnaSubscriptionKey = $outputs.qnaMaker.value.key } # Delay to let QnA Maker finish setting up Start-Sleep -s 30 # Deploy cognitive models - Invoke-Expression "$(Join-Path $PSScriptRoot 'deploy_cognitive_models.ps1') -name $($name) -luisAuthoringRegion $($luisAuthoringRegion) -luisAuthoringKey $($luisAuthoringKey) -outFolder $($projDir) -languages `"$($languages)`"" + Invoke-Expression "& '$(Join-Path $PSScriptRoot 'deploy_cognitive_models.ps1')' -name $($name) -luisAuthoringRegion $($luisAuthoringRegion) -luisAuthoringKey $($luisAuthoringKey) -luisAccountName $($outputs.luis.value.accountName) -luisAccountRegion $($outputs.luis.value.region) -luisSubscriptionKey $($outputs.luis.value.key) -resourceGroup $($resourceGroup) -qnaSubscriptionKey '$($qnaSubscriptionKey)' -outFolder '$($projDir)' -languages '$($languages)'" # Publish bot - Write-Host "+ To publish your bot, run '$(Join-Path $PSScriptRoot 'publish.ps1') -name $($name) -resourceGroup $($resourceGroup)'" -ForegroundColor Magenta + Write-Host "+ To publish your bot, run '$(Join-Path $PSScriptRoot 'publish.ps1')' -name $($outputs.botWebAppName.value) -resourceGroup $($resourceGroup) -projFolder '$($projDir)'" -ForegroundColor Magenta Write-Host "> Done." } else { # Check for failed deployments - $operations = (az group deployment operation list -g $resourceGroup -n $timestamp) 2>> $logFile | Out-Null + $operations = (az group deployment operation list -g $resourceGroup -n $timestamp --output json) 2>> $logFile | Out-Null if ($operations) { $operations = $operations | ConvertFrom-Json @@ -206,7 +222,7 @@ else switch ($operation.properties.statusmessage.error.code) { "MissingRegistrationForLocation" { Write-Host "! Deployment failed for resource of type $($operation.properties.targetResource.resourceType). This resource is not avaliable in the location provided." -ForegroundColor DarkRed - Write-Host "+ Update the .\deployment\resources\parameters.template.json file with a valid region for this resource and provide the file path in the -parametersFile parameter." -ForegroundColor Magenta + Write-Host "+ Update the .\Deployment\resources\parameters.template.json file with a valid region for this resource and provide the file path in the -parametersFile parameter." -ForegroundColor Magenta } default { Write-Host "! Deployment failed for resource of type $($operation.properties.targetResource.resourceType)." @@ -224,4 +240,4 @@ else Write-Host "+ To delete this resource group, run 'az group delete -g $($resourceGroup) --no-wait'" -ForegroundColor Magenta Break -} +} \ No newline at end of file diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/deploy_cognitive_models.ps1 b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/deploy_cognitive_models.ps1 index b35e4e9b76..c00947b3c7 100644 --- a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/deploy_cognitive_models.ps1 +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/deploy_cognitive_models.ps1 @@ -3,14 +3,20 @@ Param( [string] $name, [string] $luisAuthoringRegion, - [string] $luisAuthoringKey, + [string] $luisAuthoringKey, + [string] $luisAccountName, + [string] $luisAccountRegion, + [string] $luisSubscriptionKey, + [string] $qnaSubscriptionKey, + [string] $resourceGroup, + [switch] $useDispatch = $true, [string] $languages = "en-us", [string] $outFolder = $(Get-Location), [string] $logFile = $(Join-Path $PSScriptRoot .. "deploy_cognitive_models_log.txt") ) . $PSScriptRoot\luis_functions.ps1 - +. $PSScriptRoot\qna_functions.ps1 # Reset log file if (Test-Path $logFile) { @@ -23,7 +29,6 @@ else { # Get mandatory parameters if (-not $name) { $name = Read-Host "? Base name for Cognitive Models" - $resourceGroup = $name } if (-not $luisAuthoringRegion) { @@ -55,52 +60,240 @@ if (-not $luisAuthoringKey) { } } +if (-not $luisAccountName) { + $luisAccountName = Read-Host "? LUIS Service Name (exising service in Azure required)" +} + +if (-not $resourceGroup) { + $resourceGroup = $name + + $rgExists = az group exists -n $resourceGroup --output json + if ($rgExists -eq "false") + { + $resourceGroup = Read-Host "? LUIS Service Resource Group (exising service in Azure required)" + } +} + +if (-not $luisSubscriptionKey) { + $keys = az cognitiveservices account keys list --name $luisAccountName --resource-group $resourceGroup --output json | ConvertFrom-Json + + if ($keys) { + $luisSubscriptionKey = $keys.key1 + } + else { + Write-Host "! Could not retrieve LUIS Subscription Key." -ForgroundColor DarkRed + Write-Host "+ Verify the -luisAccountName and -resourceGroup parameters are correct." -ForegroundColor Magenta + Break + } +} + +if (-not $luisAccountRegion) { + $luisAccountRegion = Read-Host "? LUIS Service Location" +} +if (-not $qnaSubscriptionKey) { + $useQna = $false +} +else { + $useQna = $true +} + +$azAccount = az account show --output json | ConvertFrom-Json +$azAccessToken = $(Invoke-Expression "az account get-access-token --output json") | ConvertFrom-Json + # Get languages $languageArr = $languages -split "," # Initialize settings obj -$settings = @{ cognitiveModels = New-Object PSObject } +$settings = @{ defaultLocale = $languageArr[0]; cognitiveModels = New-Object PSObject } # Deploy localized resources Write-Host "> Deploying cognitive models ..." foreach ($language in $languageArr) { - $langCode = ($language -split "-")[0] + $langCode = ($language -split "-")[0] + $config = New-Object PSObject - $config = @{ - languageModels = @() - } + if ($useDispatch) { + # Add dispatch to config + $config | Add-Member -MemberType NoteProperty -Name dispatchModel -Value $(New-Object PSObject) + + # Initialize Dispatch + Write-Host "> Initializing dispatch model ..." + $dispatchName = "$($name)$($langCode)_Dispatch" + $dataFolder = Join-Path $PSScriptRoot .. resources Dispatch $langCode + (dispatch init ` + --name $dispatchName ` + --luisAuthoringKey $luisAuthoringKey ` + --luisAuthoringRegion $luisAuthoringRegion ` + --dataFolder $dataFolder) 2>> $logFile | Out-Null + } # Deploy LUIS apps $luisFiles = Get-ChildItem "$(Join-Path $PSScriptRoot .. 'resources' 'LU' $langCode)" | Where {$_.extension -eq ".lu"} - foreach ($lu in $luisFiles) - { - # Deploy LUIS model - $luisApp = DeployLUIS -name $name -lu_file $lu -region $luisAuthoringRegion -luisAuthoringKey $luisAuthoringKey -language $language -log $logFile - - if ($luisApp) { - # Add to config - $config.languageModels += @{ - id = $lu.BaseName - name = $luisApp.name - appId = $luisApp.id - authoringKey = $luisAuthoringKey - subscriptionKey = $luisAuthoringKey - version = $luisApp.activeVersion - region = $luisAuthoringRegion + if ($luisFiles) { + $config | Add-Member -MemberType NoteProperty -Name languageModels -Value @() + + foreach ($lu in $luisFiles) + { + # Deploy LUIS model + $luisApp = DeployLUIS ` + -name $name ` + -lu_file $lu ` + -region $luisAuthoringRegion ` + -luisAuthoringKey $luisAuthoringKey ` + -language $language ` + -log $logFile + + Write-Host "> Setting LUIS subscription key ..." + if ($luisApp) { + # Setting subscription key + $addKeyResult = luis add appazureaccount ` + --appId $luisApp.id ` + --authoringKey $luisAuthoringKey ` + --region $luisAuthoringRegion ` + --accountName $luisAccountName ` + --azureSubscriptionId $azAccount.id ` + --resourceGroup $resourceGroup ` + --armToken "$($azAccessToken.accessToken)" 2>> $logFile + + if (-not $addKeyResult) { + $luisKeySet = $false + Write-Host "! Could not assign subscription key automatically. Review the log for more information. " -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Write-Host "+ Please assign your subscription key manually in the LUIS portal." -ForegroundColor Magenta + } + + if ($useDispatch) { + # Add luis app to dispatch + Write-Host "> Adding $($lu.BaseName) app to dispatch model ..." + (dispatch add ` + --type "luis" ` + --name $luisApp.name ` + --id $luisApp.id ` + --region $luisApp.region ` + --intentName "l_$($lu.BaseName)" ` + --dataFolder $dataFolder ` + --dispatch "$(Join-Path $dataFolder "$($dispatchName).dispatch")") 2>> $logFile | Out-Null + } + + # Add to config + $config.languageModels += @{ + id = $lu.BaseName + name = $luisApp.name + appId = $luisApp.id + authoringKey = $luisAuthoringKey + authoringRegion = $luisAuthoringRegion + subscriptionKey = $luisSubscriptionKey + version = $luisApp.activeVersion + region = $luisAccountRegion + } } + else { + Write-Host "! Could not create LUIS app. Skipping dispatch add." -ForegroundColor Cyan + } + } + } + + if ($useQna) { + if (Test-Path $(Join-Path $PSScriptRoot .. 'resources' 'QnA' $langCode)) { + # Deploy QnA Maker KBs + $qnaFiles = Get-ChildItem "$(Join-Path $PSScriptRoot .. 'resources' 'QnA' $langCode)" -Recurse | Where {$_.extension -eq ".lu"} + + if ($qnaFiles) { + $config | Add-Member -MemberType NoteProperty -Name knowledgeBases -Value @() + + foreach ($lu in $qnaFiles) + { + # Deploy QnA Knowledgebase + $qnaKb = DeployKB -name $name -lu_file $lu -qnaSubscriptionKey $qnaSubscriptionKey -log $logFile + + if ($qnaKb) { + if ($useDispatch) { + Write-Host "> Adding $($lu.BaseName) kb to dispatch model ..." + (dispatch add ` + --type "qna" ` + --name $qnaKb.name ` + --id $qnaKb.id ` + --key $qnaSubscriptionKey ` + --intentName "q_$($lu.BaseName)" ` + --dataFolder $dataFolder ` + --dispatch "$(Join-Path $dataFolder "$($dispatchName).dispatch")") 2>> $logFile | Out-Null + } + + # Add to config + $config.knowledgeBases += @{ + id = $lu.BaseName + name = $qnaKb.name + kbId = $qnaKb.kbId + subscriptionKey = $qnaKb.subscriptionKey + endpointKey = $qnaKb.endpointKey + hostname = $qnaKb.hostname + } + } + else { + Write-Host "! Could not create knowledgebase. Skipping dispatch add." -ForegroundColor Cyan + } + } + } + } + else { + Write-Host "! No knowledgeBases found. Skipping." -ForegroundColor Cyan + } + } + else { + Write-Host "! No QnA Maker Subscription Key provided. Skipping knowledgeBases." -ForegroundColor Cyan + } + + if ($useDispatch) { + # Create dispatch model + Write-Host "> Creating dispatch model..." + $dispatch = (dispatch create ` + --dispatch "$(Join-Path $dataFolder "$($dispatchName).dispatch")" ` + --dataFolder $dataFolder ` + --culture $language) 2>> $logFile - RunLuisGen $lu "$($lu.BaseName)" $(Join-Path $outFolder Services) + if (-not $dispatch) { + Write-Host "! Could not create Dispatch app. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Break } else { - Write-Host "! Deployment failed for LUIS app: $($lu.BaseName)" -ForegroundColor Cyan + $dispatchApp = $dispatch | ConvertFrom-Json + + # Setting subscription key + Write-Host "> Setting LUIS subscription key ..." + $addKeyResult = luis add appazureaccount ` + --appId $dispatchApp.appId ` + --accountName $luisAccountName ` + --authoringKey $luisAuthoringKey ` + --region $luisAuthoringRegion ` + --azureSubscriptionId $azAccount.id ` + --resourceGroup $resourceGroup ` + --armToken $azAccessToken.accessToken 2>> $logFile + + if (-not $addKeyResult) { + $luisKeySet = $false + Write-Host "! Could not assign subscription key automatically. Review the log for more information. " -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed + Write-Host "+ Please assign your subscription key manually in the LUIS portal." -ForegroundColor Magenta + } + + # Add to config + $config.dispatchModel = @{ + type = "dispatch" + name = $dispatchApp.name + appId = $dispatchApp.appId + authoringKey = $luisAuthoringKey + authoringRegion = $luisAuthoringRegion + subscriptionKey = $luisSubscriptionKey + region = $luisAuthoringRegion + } } - } + } # Add config to cognitivemodels dictionary $settings.cognitiveModels | Add-Member -Type NoteProperty -Force -Name $langCode -Value $config } -$settings | Add-Member -Type NoteProperty -Force -Name "defaultLocale" -Value "en" # Write out config to file $settings | ConvertTo-Json -depth 100 | Out-File $(Join-Path $outFolder "cognitivemodels.json" ) \ No newline at end of file diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/luis_functions.ps1 b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/luis_functions.ps1 index 04907c3754..2301c8e9b1 100644 --- a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/luis_functions.ps1 +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/luis_functions.ps1 @@ -30,20 +30,20 @@ function DeployLUIS ($name, $lu_file, $region, $luisAuthoringKey, $language, $lo } else { # train and publish luis app - $(luis train version --appId $luisApp.id --authoringKey $luisAuthoringKey --versionId $luisApp.activeVersion --wait - & luis publish version --appId $luisApp.id --authoringKey $luisAuthoringKey --versionId $luisApp.activeVersion --wait) 2>> $log | Out-Null + $(luis train version --appId $luisApp.id --region $region --authoringKey $luisAuthoringKey --versionId $luisApp.activeVersion --wait + & luis publish version --appId $luisApp.id --region $region --authoringKey $luisAuthoringKey --versionId $luisApp.activeVersion --wait) 2>> $log | Out-Null Return $luisApp } } -function UpdateLUIS ($lu_file, $appId, $version, $authoringKey, $subscriptionKey, $log) +function UpdateLUIS ($lu_file, $appId, $version, $region, $authoringKey, $subscriptionKey, $log) { $id = $lu_file.BaseName $outFile = "$($id).luis" $outFolder = $lu_file.DirectoryName - $luisApp = luis get application --appId $appId --authoringKey $authoringKey | ConvertFrom-Json + $luisApp = luis get application --appId $appId --region $region --authoringKey $authoringKey | ConvertFrom-Json # Parse LU file Write-Host "> Parsing $($id) LU file ..." @@ -53,10 +53,11 @@ function UpdateLUIS ($lu_file, $appId, $version, $authoringKey, $subscriptionKey --out_folder $outFolder ` --out $outFile - Write-Host "? Getting current versions ..." + Write-Host "> Getting current versions ..." # Get list of current versions $versions = luis list versions ` --appId $appId ` + --region $region ` --authoringKey $authoringKey | ConvertFrom-Json # If the current version exists @@ -69,6 +70,7 @@ function UpdateLUIS ($lu_file, $appId, $version, $authoringKey, $subscriptionKey luis delete version ` --appId $appId ` --versionId "backup" ` + --region $region ` --authoringKey $authoringKey ` --force --wait | Out-Null } @@ -78,6 +80,7 @@ function UpdateLUIS ($lu_file, $appId, $version, $authoringKey, $subscriptionKey luis rename version ` --appId $appId ` --versionId $version ` + --region $region ` --newVersionId backup ` --authoringKey $authoringKey ` --subscriptionKey $subscriptionKey ` @@ -89,14 +92,15 @@ function UpdateLUIS ($lu_file, $appId, $version, $authoringKey, $subscriptionKey luis import version ` --appId $appId ` --versionId $version ` + --region $region ` --authoringKey $authoringKey ` --subscriptionKey $subscriptionKey ` --in "$(Join-Path $outFolder $outFile)" ` --wait | ConvertFrom-Json # train and publish luis app - $(luis train version --appId $appId --authoringKey $authoringKey --versionId $version --wait - & luis publish version --appId $appId --authoringKey $authoringKey --versionId $version --wait) 2>&1 | Out-Null + $(luis train version --appId $appId --region $region --authoringKey $authoringKey --versionId $version --wait + & luis publish version --appId $appId --region $region --authoringKey $authoringKey --versionId $version --wait) 2>> $log | Out-Null } function RunLuisGen($lu_file, $outName, $outFolder) { @@ -104,5 +108,5 @@ function RunLuisGen($lu_file, $outName, $outFolder) { $luisFolder = $lu_file.DirectoryName $luisFile = Join-Path $luisFolder "$($id).luis" - luisgen $luisFile -cs "$($outName)Luis" -o $outFolder + luisgen $luisFile -ts "$($outName)Luis" -o $outFolder } \ No newline at end of file diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/publish.ps1 b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/publish.ps1 index b535fbf44f..037e7f7bb8 100644 --- a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/publish.ps1 +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/publish.ps1 @@ -7,6 +7,16 @@ Param( [string] $logFile = $(Join-Path $PSScriptRoot .. "publish.txt") ) +# Get mandatory parameters +if (-not $name) { + $name = Read-Host "? Bot Web App Name" +} + +if (-not $resourceGroup) { + $resourceGroup = Read-Host "? Bot Resource Group" +} + + # Reset log file if (Test-Path $logFile) { Clear-Content $logFile -Force | Out-Null @@ -16,17 +26,10 @@ else { } # Check for existing deployment files -if (-not (Test-Path (Join-Path $projFolder 'web.config'))) { - - # Add needed deployment files for az - az bot prepare-deploy --code-dir $projFolder --lang Typescript -} - -# Check for existing deployment configuration if (-not (Test-Path (Join-Path $projFolder '.deployment'))) { - # Add needed deployment configuration - Add-Content -Path $(Join-Path $projFolder ".deployment") -Value @("[config]", "SCM_DO_BUILD_DURING_DEPLOYMENT=true") + # Add needed deployment files for az + az bot prepare-deploy --code-dir $projFolder --lang Typescript --output json | Out-Null } # Delete src zip, if it exists @@ -35,12 +38,21 @@ if (Test-Path $zipPath) { Remove-Item $zipPath -Force | Out-Null } -# Compress source code -Get-ChildItem -Path "$($projFolder)" -Exclude @("node_modules", "test", "deployment") | Compress-Archive -DestinationPath "$($zipPath)" -Force | Out-Null - -# Publish zip to Azure -Write-Host "> Publishing to Azure ..." -(az webapp deployment source config-zip ` - --resource-group $resourceGroup ` - --name $name ` - --src $zipPath) 2>> $logFile | Out-Null \ No newline at end of file +if($?) +{ + # Compress source code + Get-ChildItem -Path "$($projFolder)" -Exclude @("node_modules", "test", "deployment") | Compress-Archive -DestinationPath "$($zipPath)" -Force | Out-Null + + # Publish zip to Azure + Write-Host "> Publishing to Azure ..." -ForegroundColor Green + (az webapp deployment source config-zip ` + --resource-group $resourceGroup ` + --name $name ` + --src $zipPath ` + --output json) 2>> $logFile | Out-Null +} + else +{ + Write-Host "! Could not deploy automatically to Azure. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($logFile)" -ForegroundColor DarkRed +} \ No newline at end of file diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/qna_functions.ps1 b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/qna_functions.ps1 new file mode 100644 index 0000000000..7a8ac67610 --- /dev/null +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/qna_functions.ps1 @@ -0,0 +1,87 @@ +function DeployKB ($name, $lu_file, $qnaSubscriptionKey, $log) +{ + $id = $lu_file.BaseName + $outFile = "$($id).qna" + $outFolder = $lu_file.DirectoryName + + # Parse LU file + Write-Host "> Parsing $($id) LU file ..." + ludown parse toqna ` + --in $lu_file ` + --out_folder $outFolder ` + --out $outFile + + # Create QnA Maker kb + Write-Host "> Deploying $($id) QnA kb ..." + + # These values pretty much guarantee success. We can decrease them if the QnA backend gets faster + $initialDelaySeconds = 30; + $retryAttemptsRemaining = 3; + $retryDelaySeconds = 15; + $retryDelayIncrease = 30; + + while ($retryAttemptsRemaining -ge 0) { + $qnaKb = (qnamaker create kb ` + --name $id ` + --subscriptionKey $qnaSubscriptionKey ` + --in $(Join-Path $outFolder $outFile) ` + --force ` + --wait ` + --msbot) 2>> $log + + if (-not $qnaKb) { + $retryAttemptsRemaining = $retryAttemptsRemaining - 1 + Write-Host $retryAttemptsRemaining + Start-Sleep -s $retryDelaySeconds + $retryDelaySeconds += $retryDelayIncrease + + if ($retryAttemptsRemaining -lt 0) { + Write-Host "! Unable to create QnA KB." -ForegroundColor Cyan + } + else { + Write-Host "> Retrying ..." + Continue + } + } + else { + Break + } + } + + if (-not $qnaKb) { + Write-Host "! Could not deploy knowledgebase. Review the log for more information." -ForegroundColor DarkRed + Write-Host "! Log: $($log)" -ForegroundColor DarkRed + Return $null + } + else { + $qnaKb = $qnaKb | ConvertFrom-Json + + # Publish QnA Maker knowledgebase + $(qnamaker publish kb --kbId $qnaKb.kbId --subscriptionKey $qnaSubscriptionKey) 2>> $log | Out-Null + + Return $qnaKb + } +} + +function UpdateKB ($lu_file, $kbId, $qnaSubscriptionKey) +{ + $id = $lu_file.BaseName + $outFile = "$($id).qna" + $outFolder = $lu_file.DirectoryName + + # Parse LU file + Write-Host "> Parsing $($id) LU file ..." + ludown parse toqna ` + --in $lu_file ` + --out_folder $outFolder ` + --out $outFile + + Write-Host "> Replacing $($id) QnA kb ..." + qnamaker replace kb ` + --in $(Join-Path $outFolder $outFile) ` + --kbId $kbId ` + --subscriptionKey $qnaSubscriptionKey + + # Publish QnA Maker knowledgebase + $(qnamaker publish kb --kbId $kbId --subscriptionKey $qnaSubscriptionKey) 2>&1 | Out-Null +} \ No newline at end of file diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/update_cognitive_models.ps1 b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/update_cognitive_models.ps1 new file mode 100644 index 0000000000..595d2de290 --- /dev/null +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/deployment/scripts/update_cognitive_models.ps1 @@ -0,0 +1,150 @@ +#Requires -Version 6 + +Param( + [switch] $RemoteToLocal, + [string] $configFile = $(Join-Path (Get-Location) 'src' 'cognitivemodels.json'), + [string] $dispatchFolder = $(Join-Path $PSScriptRoot '..' 'resources' 'Dispatch'), + [string] $luisFolder = $(Join-Path $PSScriptRoot '..' 'resources' 'LU'), + [string] $qnaFolder = $(Join-Path $PSScriptRoot '..' 'resources' 'QnA'), + [string] $lgOutFolder = $(Join-Path (Get-Location) 'src' 'services'), + [string] $logFile = $(Join-Path $PSScriptRoot .. "update_cognitive_models_log.txt") +) + +. $PSScriptRoot\luis_functions.ps1 +. $PSScriptRoot\qna_functions.ps1 + +# Reset log file +if (Test-Path $logFile) { + Clear-Content $logFile -Force | Out-Null +} +else { + New-Item -Path $logFile | Out-Null +} + +Write-Host "> Getting config file ..." +$languageMap = @{} +$config = Get-Content -Raw -Path $configFile | ConvertFrom-Json +$config.cognitiveModels.PSObject.Properties | Foreach-Object { $languageMap[$_.Name] = $_.Value } + +foreach ($langCode in $languageMap.Keys) { + $models = $languageMap[$langCode] + $dispatch = $models.dispatchModel + + if($RemoteToLocal) { + # Update local LU files based on hosted models + foreach ($luisApp in $models.languageModels){ + $culture = (luis get application ` + --appId $luisApp.appId ` + --authoringKey $luisApp.authoringKey ` + --subscriptionKey $luisApp.subscriptionKey ` + --region $luisApp.authoringRegion | ConvertFrom-Json).culture + Write-Host "> Updating local $($luisApp.id).lu file ..." + luis export version ` + --appId $luisApp.appId ` + --versionId $luisApp.version ` + --region $luisApp.authoringRegion ` + --authoringKey $luisApp.authoringKey | ludown refresh ` + --stdin ` + -n "$($luisApp.id).lu" ` + -o $(Join-Path $luisFolder $langCode) + + # Parse LU file + $id = $luisApp.id + $outFile = "$($id).luis" + $outFolder = $(Join-Path $luisFolder $langCode) + $appName = "$($name)$($langCode)_$($id)" + + Write-Host "> Parsing $($luisApp.id) LU file ..." + ludown parse toluis ` + --in $(Join-Path $outFolder "$($luisApp.id).lu") ` + --luis_culture $culture ` + --out_folder $(Join-Path $luisFolder $langCode) ` + --out "$($luisApp.id).luis" + if ($useLuisGen) { + Write-Host "> Running LuisGen for $($luisApp.id) app ..." + $luPath = $(Join-Path $luisFolder $langCode "$($luisApp.id).lu") + RunLuisGen -lu_file $(Get-Item $luPath) -outName "$($luisApp.id)" -outFolder $lgOutFolder + } + + # Add the LUIS application to the dispatch model. + # If the LUIS application id already exists within the model no action will be taken + if ($dispatch) { + Write-Host "> Adding $($luisApp.id) app to dispatch model ... " + (dispatch add ` + --type "luis" ` + --name $luisApp.name ` + --id $luisApp.appid ` + --region $luisApp.authoringRegion ` + --intentName "l_$($luisApp.id)" ` + --dispatch $(Join-Path $dispatchFolder $langCode "$($dispatch.name).dispatch") ` + --dataFolder $(Join-Path $dispatchFolder $langCode)) 2>> $logFile | Out-Null + } + } + + # Update local LU files based on hosted QnA KBs + foreach ($kb in $models.knowledgeBases) { + Write-Host "> Updating local $($kb.id).lu file ..." + qnamaker export kb ` + --environment Prod ` + --kbId $kb.kbId ` + --subscriptionKey $kb.subscriptionKey | ludown refresh ` + --stdin ` + -n "$($kb.id).lu" ` + -o $(Join-Path $qnaFolder $langCode) + + # Add the knowledge base to the dispatch model. + # If the knowledge base id already exists within the model no action will be taken + if ($dispatch) { + Write-Host "> Adding $($kb.id) kb to dispatch model ..." + (dispatch add ` + --type "qna" ` + --name $kb.name ` + --id $kb.kbId ` + --key $kb.subscriptionKey ` + --intentName "q_$($kb.id)" ` + --dispatch $(Join-Path $dispatchFolder $langCode "$($dispatch.name).dispatch") ` + --dataFolder $(Join-Path $dispatchFolder $langCode)) 2>> $logFile | Out-Null + } + } + } + else { + # Update each luis model based on local LU files + foreach ($luisApp in $models.languageModels) { + Write-Host "> Updating hosted $($luisApp.id) app..." + $lu = Get-Item -Path $(Join-Path $luisFolder $langCode "$($luisApp.id).lu") + UpdateLUIS ` + -lu_file $lu ` + -appId $luisApp.appId ` + -version $luisApp.version ` + -region $luisApp.region ` + -authoringKey $luisApp.authoringKey ` + -subscriptionKey $luisApp.subscriptionKey + } + + # Update each knowledgebase based on local LU files + foreach ($kb in $models.knowledgeBases) { + Write-Host "> Updating hosted $($kb.id) kb..." + $lu = Get-Item -Path $(Join-Path $qnaFolder $langCode "$($kb.id).lu") + UpdateKB ` + -lu_file $lu ` + -kbId $kb.kbId ` + -qnaSubscriptionKey $kb.subscriptionKey + } + } + + if ($dispatch) { + # Update dispatch model + Write-Host "> Updating dispatch model ..." + dispatch refresh ` + --dispatch $(Join-Path $dispatchFolder $langCode "$($dispatch.name).dispatch") ` + --dataFolder $(Join-Path $dispatchFolder $langCode) 2>> $logFile | Out-Null + + if ($useLuisGen) { + # Update dispatch.ts file + Write-Host "> Running LuisGen ..." + luisgen $(Join-Path $dispatchFolder $langCode "$($dispatch.name).json") -ts "DispatchLuis" -o $lgOutFolder 2>> $logFile | Out-Null + } + } +} + +Write-Host "> Done." \ No newline at end of file diff --git a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/src/appsettings.json b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/src/appsettings.json index e5e3c67edf..8dd05e53f3 100644 --- a/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/src/appsettings.json +++ b/templates/Virtual-Assistant-Template/typescript/samples/sample-skill/src/appsettings.json @@ -2,7 +2,6 @@ "microsoftAppId": "", "microsoftAppPassword": "", "appInsights": { - "appId": "", "instrumentationKey": "" }, "blobStorage": {