diff --git a/infra/main.bicep b/infra/main.bicep index 830e943b4..b523275fb 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -460,12 +460,6 @@ var dnsZoneIndex = { sqlServer: 9 search: 10 } -// List of DNS zone indices that correspond to AI-related services. -var aiRelatedDnsZoneIndices = [ - dnsZoneIndex.cognitiveServices - dnsZoneIndex.openAI - dnsZoneIndex.aiServices -] // =================================================== // DEPLOY PRIVATE DNS ZONES @@ -1575,6 +1569,9 @@ module webSiteBackend 'modules/web-sites.bicep' = { SOLUTION_NAME: solutionSuffix APP_ENV: 'Prod' AZURE_CLIENT_ID: backendUserAssignedIdentity.outputs.clientId + AZURE_BASIC_LOGGING_LEVEL: 'INFO' + AZURE_PACKAGE_LOGGING_LEVEL: 'WARNING' + AZURE_LOGGING_PACKAGES: '' } // WAF aligned configuration for Monitoring applicationInsightResourceId: enableMonitoring ? applicationInsights!.outputs.resourceId : null diff --git a/infra/main.json b/infra/main.json index 55222e5d0..f671ecbec 100644 --- a/infra/main.json +++ b/infra/main.json @@ -5,8 +5,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "18267085918254210296" + "version": "0.37.4.10188", + "templateHash": "4682612971990946423" } }, "parameters": { @@ -359,11 +359,6 @@ "sqlServer": 9, "search": 10 }, - "aiRelatedDnsZoneIndices": [ - "[variables('dnsZoneIndex').cognitiveServices]", - "[variables('dnsZoneIndex').openAI]", - "[variables('dnsZoneIndex').aiServices]" - ], "userAssignedIdentityResourceName": "[format('id-{0}', variables('solutionSuffix'))]", "backendUserAssignedIdentityResourceName": "[format('id-backend-{0}', variables('solutionSuffix'))]", "keyVaultName": "[format('kv-{0}', variables('solutionSuffix'))]", @@ -504,7 +499,7 @@ "logAnalyticsWorkspace": { "condition": "[and(parameters('enableMonitoring'), not(variables('useExistingLogAnalytics')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.operational-insights.workspace.{0}', variables('logAnalyticsWorkspaceResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -3610,7 +3605,7 @@ "applicationInsights": { "condition": "[parameters('enableMonitoring')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.insights.component.{0}', variables('applicationInsightsResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -4341,7 +4336,7 @@ "virtualNetwork": { "condition": "[parameters('enablePrivateNetworking')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('module.virtualNetwork.{0}', variables('solutionSuffix')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -4378,8 +4373,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "16551195719711772164" + "version": "0.37.4.10188", + "templateHash": "150702682969982307" } }, "definitions": { @@ -4786,7 +4781,7 @@ }, "condition": "[not(empty(tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.network.network-security-group.{0}.{1}', tryGet(parameters('subnets')[copyIndex()], 'networkSecurityGroup', 'name'), parameters('resourceSuffix')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -5438,7 +5433,7 @@ }, "virtualNetwork": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.network.virtual-network.{0}', parameters('name')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -7169,7 +7164,7 @@ "bastionHost": { "condition": "[parameters('enablePrivateNetworking')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.network.bastion-host.{0}', variables('bastionHostName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -8488,7 +8483,7 @@ "jumpboxVM": { "condition": "[parameters('enablePrivateNetworking')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.compute.virtual-machine.{0}', variables('jumpboxVmName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -16838,7 +16833,7 @@ }, "condition": "[parameters('enablePrivateNetworking')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('avm.res.network.private-dns-zone.{0}', split(variables('privateDnsZones')[copyIndex()], '.')[1])]", "properties": { "expressionEvaluationOptions": { @@ -20005,7 +20000,7 @@ }, "userAssignedIdentity": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.managed-identity.user-assigned-identity.{0}', variables('userAssignedIdentityResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -20487,7 +20482,7 @@ }, "backendUserAssignedIdentity": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.managed-identity.user-assigned-identity.{0}', variables('backendUserAssignedIdentityResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -20969,7 +20964,7 @@ }, "keyvault": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.key-vault.vault.{0}', variables('keyVaultName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -21040,7 +21035,7 @@ }, { "name": "AZURE-COSMOSDB-ACCOUNT-KEY", - "value": "[listOutputsWithSecureValues('cosmosDb', '2025-04-01').primaryReadWriteKey]" + "value": "[listOutputsWithSecureValues('cosmosDb', '2022-09-01').primaryReadWriteKey]" }, { "name": "AZURE-COSMOSDB-DATABASE", @@ -21064,7 +21059,7 @@ }, { "name": "ADLS-ACCOUNT-KEY", - "value": "[listOutputsWithSecureValues('storageAccount', '2025-04-01').primaryAccessKey]" + "value": "[listOutputsWithSecureValues('storageAccount', '2022-09-01').primaryAccessKey]" }, { "name": "AZURE-SEARCH-ENDPOINT", @@ -24276,7 +24271,7 @@ "aiFoundryAiServices": { "condition": "[variables('aiFoundryAIservicesEnabled')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.cognitive-services.account.{0}', variables('aiFoundryAiServicesResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -24405,8 +24400,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13577584254455791464" + "version": "0.37.4.10188", + "templateHash": "9302733089815614062" }, "name": "Cognitive Services", "description": "This module deploys a Cognitive Service." @@ -25586,7 +25581,7 @@ "cognitive_service_dependencies": { "condition": "[not(variables('useExistingService'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('cognitive_service_dependencies-{0}', uniqueString('cognitive_service_dependencies', deployment().name))]", "properties": { "expressionEvaluationOptions": { @@ -25638,8 +25633,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "12629047609675461422" + "version": "0.37.4.10188", + "templateHash": "13864482829550647329" } }, "definitions": { @@ -26678,7 +26673,7 @@ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-cognitiveService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", @@ -27429,7 +27424,7 @@ "secretsExport": { "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]", "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", @@ -27453,8 +27448,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4291957610087788581" + "version": "0.37.4.10188", + "templateHash": "2491273843075489892" } }, "definitions": { @@ -27573,7 +27568,7 @@ "aiProject": { "condition": "[or(not(empty(parameters('projectName'))), not(empty(parameters('existingFoundryProjectResourceId'))))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('{0}-ai-project-{1}-deployment', parameters('name'), parameters('projectName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -27607,8 +27602,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13987065577218259048" + "version": "0.37.4.10188", + "templateHash": "7781450680156271399" } }, "definitions": { @@ -27787,7 +27782,7 @@ "existing_cognitive_service_dependencies": { "condition": "[variables('useExistingService')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('existing_cognitive_service_dependencies-{0}', uniqueString('existing_cognitive_service_dependencies', deployment().name))]", "subscriptionId": "[variables('existingCognitiveServiceDetails')[2]]", "resourceGroup": "[variables('existingCognitiveServiceDetails')[4]]", @@ -27844,8 +27839,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "12629047609675461422" + "version": "0.37.4.10188", + "templateHash": "13864482829550647329" } }, "definitions": { @@ -28884,7 +28879,7 @@ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-cognitiveService-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", @@ -29635,7 +29630,7 @@ "secretsExport": { "condition": "[not(equals(parameters('secretsExportConfiguration'), null()))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-secrets-kv', uniqueString(deployment().name, parameters('location')))]", "subscriptionId": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[2]]", "resourceGroup": "[split(tryGet(parameters('secretsExportConfiguration'), 'keyVaultResourceId'), '/')[4]]", @@ -29659,8 +29654,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4291957610087788581" + "version": "0.37.4.10188", + "templateHash": "2491273843075489892" } }, "definitions": { @@ -29779,7 +29774,7 @@ "aiProject": { "condition": "[or(not(empty(parameters('projectName'))), not(empty(parameters('existingFoundryProjectResourceId'))))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('{0}-ai-project-{1}-deployment', parameters('name'), parameters('projectName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -29813,8 +29808,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13987065577218259048" + "version": "0.37.4.10188", + "templateHash": "7781450680156271399" } }, "definitions": { @@ -30082,7 +30077,7 @@ }, "cognitiveServicesCu": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.cognitive-services.account.{0}', variables('aiFoundryAiServicesCUResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -32400,9 +32395,9 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').openAI)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').aiServices)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').cognitiveServices)]", "logAnalyticsWorkspace", "userAssignedIdentity", "virtualNetwork" @@ -32410,7 +32405,7 @@ }, "searchSearchServices": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.search.search-service.{0}', variables('aiSearchName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -34806,7 +34801,7 @@ "existing_AIProject_SearchConnectionModule": { "condition": "[variables('useExistingAiFoundryAiProject')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "aiProjectSearchConnectionDeployment", "subscriptionId": "[variables('aiFoundryAiServicesSubscriptionId')]", "resourceGroup": "[variables('aiFoundryAiServicesResourceGroupName')]", @@ -34841,8 +34836,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "904007681755275486" + "version": "0.37.4.10188", + "templateHash": "6038840175458269917" } }, "parameters": { @@ -34910,7 +34905,7 @@ "searchServiceToExistingAiServicesRoleAssignment": { "condition": "[variables('useExistingAiFoundryAiProject')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "searchToExistingAiServices-roleAssignment", "subscriptionId": "[variables('aiFoundryAiServicesSubscriptionId')]", "resourceGroup": "[variables('aiFoundryAiServicesResourceGroupName')]", @@ -34936,8 +34931,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "10276790018915749779" + "version": "0.37.4.10188", + "templateHash": "3644919950024112374" } }, "parameters": { @@ -34987,7 +34982,7 @@ }, "storageAccount": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.storage.storage-account.{0}', variables('storageAccountName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -40772,9 +40767,9 @@ } }, "dependsOn": [ - "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageFile)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageBlob)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageQueue)]", + "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageFile)]", "[format('avmPrivateDnsZones[{0}]', variables('dnsZoneIndex').storageDfs)]", "userAssignedIdentity", "virtualNetwork" @@ -40782,7 +40777,7 @@ }, "cosmosDb": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.document-db.database-account.{0}', variables('cosmosDbResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -44621,7 +44616,7 @@ }, "sqlDBModule": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('avm.res.sql.server.{0}', variables('sqlServerResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -51265,7 +51260,7 @@ }, "uploadFiles": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take('avm.res.resources.deployment-script.uploadFiles', 64)]", "properties": { "expressionEvaluationOptions": { @@ -51849,7 +51844,7 @@ }, "createIndex": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take('avm.res.resources.deployment-script.createIndex', 64)]", "properties": { "expressionEvaluationOptions": { @@ -52436,7 +52431,7 @@ }, "createSqlUserAndRole": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take('avm.res.resources.deployment-script.createSqlUserAndRole', 64)]", "properties": { "expressionEvaluationOptions": { @@ -53022,7 +53017,7 @@ }, "webServerFarm": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "deploy_app_service_plan_serverfarm", "properties": { "expressionEvaluationOptions": { @@ -53593,7 +53588,7 @@ }, "webSiteBackend": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('module.web-sites.{0}', variables('backendWebSiteResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -53660,7 +53655,10 @@ "DUMMY_TEST": "True", "SOLUTION_NAME": "[variables('solutionSuffix')]", "APP_ENV": "Prod", - "AZURE_CLIENT_ID": "[reference('backendUserAssignedIdentity').outputs.clientId.value]" + "AZURE_CLIENT_ID": "[reference('backendUserAssignedIdentity').outputs.clientId.value]", + "AZURE_BASIC_LOGGING_LEVEL": "INFO", + "AZURE_PACKAGE_LOGGING_LEVEL": "WARNING", + "AZURE_LOGGING_PACKAGES": "" }, "applicationInsightResourceId": "[if(parameters('enableMonitoring'), reference('applicationInsights').outputs.resourceId.value, null())]" } @@ -53681,8 +53679,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13074777962389399773" + "version": "0.37.4.10188", + "templateHash": "4298119334635398540" } }, "definitions": { @@ -54659,7 +54657,7 @@ "count": "[length(coalesce(parameters('configs'), createArray()))]" }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-Site-Config-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { @@ -54694,8 +54692,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "11666262061409473778" + "version": "0.37.4.10188", + "templateHash": "4653685834544796273" }, "name": "Site App Settings", "description": "This module deploys a Site App Setting." @@ -54840,7 +54838,7 @@ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-app-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", @@ -55666,7 +55664,7 @@ }, "webSiteFrontend": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[take(format('module.web-sites.{0}', variables('webSiteResourceName')), 64)]", "properties": { "expressionEvaluationOptions": { @@ -55721,8 +55719,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13074777962389399773" + "version": "0.37.4.10188", + "templateHash": "4298119334635398540" } }, "definitions": { @@ -56699,7 +56697,7 @@ "count": "[length(coalesce(parameters('configs'), createArray()))]" }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-Site-Config-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "properties": { "expressionEvaluationOptions": { @@ -56734,8 +56732,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "11666262061409473778" + "version": "0.37.4.10188", + "templateHash": "4653685834544796273" }, "name": "Site App Settings", "description": "This module deploys a Site App Setting." @@ -56880,7 +56878,7 @@ "count": "[length(coalesce(parameters('privateEndpoints'), createArray()))]" }, "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}-app-PrivateEndpoint-{1}', uniqueString(deployment().name, parameters('location')), copyIndex())]", "subscriptionId": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[2]]", "resourceGroup": "[split(coalesce(tryGet(coalesce(parameters('privateEndpoints'), createArray())[copyIndex()], 'resourceGroupResourceId'), resourceGroup().id), '/')[4]]", diff --git a/infra/scripts/run_process_data_scripts.sh b/infra/scripts/run_process_data_scripts.sh index 94b31fc33..77a1b32be 100644 --- a/infra/scripts/run_process_data_scripts.sh +++ b/infra/scripts/run_process_data_scripts.sh @@ -25,7 +25,7 @@ echo "Fetching Key Vault and Managed Identity from resource group: $resourceGrou keyVaultName=$(az keyvault list --resource-group "$resourceGroupName" --query "[0].name" -o tsv) # === Retrieve the ID of the first user-assigned identity with name starting with 'id-' === -managedIdentityResourceId=$(az identity list --resource-group "$resourceGroupName" --query "[?starts_with(name, 'id-') && !starts_with(name, 'id-sql-')].id | [0]" -o tsv) +managedIdentityResourceId=$(az identity list --resource-group "$resourceGroupName" --query "[?starts_with(name, 'id-') && !starts_with(name, 'id-backend-')].id | [0]" -o tsv) # === Normalize managedIdentityResourceId (necessary for compatibility in Git Bash on Windows) === managedIdentityResourceId=$(echo "$managedIdentityResourceId" | sed -E 's|.*(/subscriptions/)|\1|') @@ -34,7 +34,7 @@ managedIdentityResourceId=$(echo "$managedIdentityResourceId" | sed -E 's|.*(/su sqlServerLocation=$(az sql server list --resource-group "$resourceGroupName" --query "[0].location" -o tsv) # === Retrieve the principal ID of the first user-assigned identity with name starting with 'id-' === -managedIdentityClientId=$(az identity list --resource-group "$resourceGroupName" --query "[?starts_with(name, 'id-') && !starts_with(name, 'id-sql-')].clientId | [0]" -o tsv) +managedIdentityClientId=$(az identity list --resource-group "$resourceGroupName" --query "[?starts_with(name, 'id-') && !starts_with(name, 'id-backend-')].clientId | [0]" -o tsv) # === Check for VNet deployment === echo "Checking for VNet deployment in resource group: $resourceGroupName" diff --git a/src/App/src/App.tsx b/src/App/src/App.tsx index cfd69cafb..229db1442 100644 --- a/src/App/src/App.tsx +++ b/src/App/src/App.tsx @@ -234,16 +234,6 @@ const Dashboard: React.FC = () => { const [ASSISTANT, TOOL, ERROR, USER] = ["assistant", "tool", "error", "user"]; - const getLastRagResponse = (messages: ChatMessage[]) => { - const lastAssistantObj = [...messages] - .reverse() - .find((obj) => obj.role === ASSISTANT && typeof obj.content === "string"); - if (typeof lastAssistantObj?.content === "string") { - return lastAssistantObj.content.trim(); - } - return null; - }; - const onSelectConversation = async (id: string) => { if (!id) { console.error("No conversation ID found"); @@ -257,10 +247,6 @@ const Dashboard: React.FC = () => { type: actionConstants.UPDATE_SELECTED_CONV_ID, payload: id, }); - dispatch({ - type: actionConstants.SET_LAST_RAG_RESPONSE, - payload: null, - }); try { const responseMessages = await historyRead(id); @@ -273,11 +259,7 @@ const Dashboard: React.FC = () => { }, }); } - const lastRagResponse = getLastRagResponse(responseMessages); - dispatch({ - type: actionConstants.SET_LAST_RAG_RESPONSE, - payload: lastRagResponse, - }); + } catch (error) { console.error("Error fetching conversation messages:", error); } finally { diff --git a/src/App/src/api/api.ts b/src/App/src/api/api.ts index 7d6c4f3f9..8199dcda0 100644 --- a/src/App/src/api/api.ts +++ b/src/App/src/api/api.ts @@ -287,9 +287,8 @@ export async function callConversationApi( "X-Ms-Client-Principal-Id": userId || "", }, body: JSON.stringify({ - messages: options.messages, - conversation_id: options.id, - last_rag_response: options.last_rag_response + query: options.query, + conversation_id: options.id }), signal: abortSignal, }); @@ -432,42 +431,6 @@ export const historyEnsure = async (): Promise => { return response; }; -export const historyGenerate = async ( - options: ConversationRequest, - abortSignal: AbortSignal, - convId?: string -): Promise => { - let body; - if (convId) { - body = JSON.stringify({ - conversation_id: convId, - messages: options.messages, - }); - } else { - body = JSON.stringify({ - messages: options.messages, - }); - } - const userId = getUserIdFromLocalStorage(); - const response = await fetch(`${baseURL}/history/generate`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "X-Ms-Client-Principal-Id": userId || "", - }, - body: body, - signal: abortSignal, - }) - .then((res) => { - return res; - }) - .catch((_err) => { - console.error("There was an issue fetching your data."); - return new Response(); - }); - return response; -}; - export const fetchCitationContent = async (body: any) => { try { const response = await fetch(`${baseURL}/api/fetch-azure-search-content`, { diff --git a/src/App/src/components/Chat/Chat.tsx b/src/App/src/components/Chat/Chat.tsx index baf7176ff..e1db0679b 100644 --- a/src/App/src/components/Chat/Chat.tsx +++ b/src/App/src/components/Chat/Chat.tsx @@ -63,8 +63,8 @@ const Chat: React.FC = ({ } }, []); - const saveToDB = async (messages: ChatMessage[], convId: string, reqType: string = 'Text') => { - if (!convId || !messages.length) { + const saveToDB = async (newMessages: ChatMessage[], convId: string, reqType: string = 'Text') => { + if (!convId || !newMessages.length) { return; } const isNewConversation = reqType !== 'graph' ? !state.selectedConversationId : false; @@ -73,14 +73,14 @@ const Chat: React.FC = ({ payload: true, }); - if (((reqType !== 'graph' && reqType !== 'error') && messages[messages.length - 1].role !== ERROR) && isCharthDisplayDefault ){ + if (((reqType !== 'graph' && reqType !== 'error') && newMessages[newMessages.length - 1].role !== ERROR) && isCharthDisplayDefault ){ setIsChartLoading(true); setTimeout(()=>{ - makeApiRequestForChart('show in a graph by default', convId, messages[messages.length - 1].content as string) + makeApiRequestForChart('show in a graph by default', convId, newMessages[newMessages.length - 1].content as string) },5000) } - await historyUpdate(messages, convId) + await historyUpdate(newMessages, convId) .then(async (res) => { if (!res.ok) { if (!messages) { @@ -96,7 +96,7 @@ const Chat: React.FC = ({ const newConversation: Conversation = { id: responseJson?.data?.conversation_id, title: responseJson?.data?.title, - messages: messages, + messages: state.chat.messages, date: responseJson?.data?.date, updatedAt: responseJson?.data?.date, }; @@ -218,10 +218,7 @@ const Chat: React.FC = ({ const request: ConversationRequest = { id: conversationId, - messages: [...state.chat.messages, newMessage].filter( - (messageObj) => messageObj.role !== ERROR - ), - last_rag_response: lrg + query: question }; const streamMessage: ChatMessage = { @@ -271,7 +268,7 @@ const Chat: React.FC = ({ content: errorMsg, date: new Date().toISOString(), }; - updatedMessages = [...state.chat.messages, newMessage, errorMessage]; + updatedMessages = [newMessage, errorMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [errorMessage], @@ -294,11 +291,7 @@ const Chat: React.FC = ({ parsedChartResponse.object as unknown as ChartDataResponse, date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - chartMessage, - ]; + updatedMessages = [newMessage, chartMessage]; // Update messages with the response content dispatch({ type: actionConstants.UPDATE_MESSAGES, @@ -313,11 +306,7 @@ const Chat: React.FC = ({ content: "Error while generating Chart.", date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - chartMessage, - ]; + updatedMessages = [newMessage, chartMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [chartMessage], @@ -357,13 +346,9 @@ const Chat: React.FC = ({ console.log("Caught with an error while chat and save", e); if (abortController.signal.aborted) { if (streamMessage.content) { - updatedMessages = [ - ...state.chat.messages, - newMessage, - ...[streamMessage], - ]; + updatedMessages = [newMessage, streamMessage]; } else { - updatedMessages = [...state.chat.messages, newMessage]; + updatedMessages = [newMessage]; } console.log( "@@@ Abort Signal detected: Formed updated msgs", @@ -429,13 +414,7 @@ const Chat: React.FC = ({ const request: ConversationRequest = { id: conversationId, - messages: [...state.chat.messages, newMessage].filter( - (messageObj) => messageObj.role !== ERROR - ), - last_rag_response: - isChartQuery(userMessage) && state.chat.lastRagResponse - ? JSON.stringify(state.chat.lastRagResponse) - : null, + query: question }; const streamMessage: ChatMessage = { @@ -550,7 +529,7 @@ const Chat: React.FC = ({ content: errorMsg, date: new Date().toISOString(), }; - updatedMessages = [...state.chat.messages, newMessage, errorMessage]; + updatedMessages = [newMessage, errorMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [errorMessage], @@ -593,11 +572,7 @@ const Chat: React.FC = ({ chartResponse as unknown as ChartDataResponse, date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - chartMessage, - ]; + updatedMessages = [newMessage, chartMessage]; // Update messages with the response content dispatch({ type: actionConstants.UPDATE_MESSAGES, @@ -612,11 +587,7 @@ const Chat: React.FC = ({ content: "Error while generating Chart.", date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - chartMessage, - ]; + updatedMessages = [newMessage, chartMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [chartMessage], @@ -646,11 +617,7 @@ const Chat: React.FC = ({ content: errorMsg, date: new Date().toISOString(), }; - updatedMessages = [ - ...state.chat.messages, - newMessage, - errorMessage, - ]; + updatedMessages = [newMessage, errorMessage]; dispatch({ type: actionConstants.UPDATE_MESSAGES, payload: [errorMessage], @@ -661,15 +628,7 @@ const Chat: React.FC = ({ console.log("Error while parsing charts response", e); } } else if (!isChartResponseReceived) { - dispatch({ - type: actionConstants.SET_LAST_RAG_RESPONSE, - payload: streamMessage?.content as string, - }); - updatedMessages = [ - ...state.chat.messages, - newMessage, - ...[streamMessage], - ]; + updatedMessages = [newMessage, streamMessage]; } } if (updatedMessages[updatedMessages.length-1]?.role !== "error") { @@ -679,13 +638,9 @@ const Chat: React.FC = ({ console.log("Caught with an error while chat and save", e); if (abortController.signal.aborted) { if (streamMessage.content) { - updatedMessages = [ - ...state.chat.messages, - newMessage, - ...[streamMessage], - ]; + updatedMessages = [newMessage, streamMessage]; } else { - updatedMessages = [...state.chat.messages, newMessage]; + updatedMessages = [newMessage]; } console.log( "@@@ Abort Signal detected: Formed updated msgs", diff --git a/src/App/src/state/ActionConstants.tsx b/src/App/src/state/ActionConstants.tsx index 0468a9eee..3491fb218 100644 --- a/src/App/src/state/ActionConstants.tsx +++ b/src/App/src/state/ActionConstants.tsx @@ -22,7 +22,6 @@ export const actionConstants = { ADD_NEW_CONVERSATION_TO_CHAT_HISTORY: "ADD_NEW_CONVERSATION_TO_CHAT_HISTORY", UPDATE_APP_SPINNER_STATUS: "UPDATE_APP_SPINNER_STATUS", UPDATE_HISTORY_UPDATE_API_FLAG: "UPDATE_HISTORY_UPDATE_API_FLAG", - SET_LAST_RAG_RESPONSE: "SET_LAST_RAG_RESPONSE", UPDATE_MESSAGE_BY_ID: "UPDATE_MESSAGE_BY_ID", UPDATE_STREAMING_FLAG: "UPDATE_STREAMING_FLAG", UPDATE_CHARTS_FETCHING_FLAG: "UPDATE_CHARTS_FETCHING_FLAG", diff --git a/src/App/src/state/AppProvider.tsx b/src/App/src/state/AppProvider.tsx index 5e66fc3cc..7c812e435 100644 --- a/src/App/src/state/AppProvider.tsx +++ b/src/App/src/state/AppProvider.tsx @@ -28,7 +28,6 @@ export type AppState = { generatingResponse: boolean; messages: ChatMessage[]; userMessage: string; - lastRagResponse: string | null; isStreamingInProgress: boolean; citations: string |null; }; @@ -71,7 +70,6 @@ const initialState: AppState = { generatingResponse: false, messages: [], userMessage: "", - lastRagResponse: null, citations: "", isStreamingInProgress: false, }, @@ -187,10 +185,6 @@ export type Action = type: typeof actionConstants.UPDATE_HISTORY_UPDATE_API_FLAG; payload: boolean; } - | { - type: typeof actionConstants.SET_LAST_RAG_RESPONSE; - payload: string | null; - } | { type: typeof actionConstants.UPDATE_MESSAGE_BY_ID; payload: ChatMessage; diff --git a/src/App/src/state/AppReducer.tsx b/src/App/src/state/AppReducer.tsx index df47521ba..38ca78703 100644 --- a/src/App/src/state/AppReducer.tsx +++ b/src/App/src/state/AppReducer.tsx @@ -88,7 +88,7 @@ const appReducer = (state: AppState, action: Action): AppState => { case actionConstants.NEW_CONVERSATION_START: return { ...state, - chat: { ...state.chat, messages: [], lastRagResponse: null }, + chat: { ...state.chat, messages: [] }, selectedConversationId: "", generatedConversationId: generateUUIDv4(), }; @@ -199,14 +199,6 @@ const appReducer = (state: AppState, action: Action): AppState => { isHistoryUpdateAPIPending: action.payload, }, }; - case actionConstants.SET_LAST_RAG_RESPONSE: - return { - ...state, - chat: { - ...state.chat, - lastRagResponse: action.payload, - }, - }; case actionConstants.UPDATE_MESSAGE_BY_ID: const messageID = action.payload.id; // console.log("aaction::",action.payload) diff --git a/src/App/src/types/AppTypes.ts b/src/App/src/types/AppTypes.ts index 922c4dc9a..0db22ccdb 100644 --- a/src/App/src/types/AppTypes.ts +++ b/src/App/src/types/AppTypes.ts @@ -39,8 +39,7 @@ export type ChatMessage = { export type ConversationRequest = { id?: string; - messages: ChatMessage[]; - last_rag_response: string | null; + query: string; }; export type AskResponse = { @@ -146,10 +145,6 @@ export type HistoryMetaData = { export type ParsedChunk = { error?: string; - id: string; - model: string; - created: number; - object: string; choices: [ { messages: [ @@ -158,10 +153,8 @@ export type ParsedChunk = { role: string; } ]; - history_metadata: object; } ]; - "apim-request-id": string; }; export type ToolMessageContent = { diff --git a/src/api/.env.sample b/src/api/.env.sample index 88fc57119..a6499abad 100644 --- a/src/api/.env.sample +++ b/src/api/.env.sample @@ -24,3 +24,10 @@ SQLDB_DATABASE= SQLDB_SERVER= USE_CHAT_HISTORY_ENABLED="True" APP_ENV="dev" +# Basic application logging (default: INFO level) +AZURE_BASIC_LOGGING_LEVEL=INFO +# Azure package logging (default: WARNING level to suppress INFO) +AZURE_PACKAGE_LOGGING_LEVEL=WARNING +# Comma-separated list of specific logger names to configure (default: empty - no custom loggers) +# Example: AZURE_LOGGING_PACKAGES=azure.identity.aio._internal,azure.monitor.opentelemetry.exporter.export._base +AZURE_LOGGING_PACKAGES= diff --git a/src/api/api/api_routes.py b/src/api/api/api_routes.py index f93203be5..dec6960c3 100644 --- a/src/api/api/api_routes.py +++ b/src/api/api/api_routes.py @@ -18,8 +18,6 @@ router = APIRouter() -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Check if the Application Insights Instrumentation Key is set in the environment variables @@ -32,20 +30,6 @@ # Log a warning if the Instrumentation Key is not found logging.warning("No Application Insights Instrumentation Key found. Skipping configuration") -# Configure logging -logging.basicConfig(level=logging.INFO) - -# Suppress INFO logs from 'azure.core.pipeline.policies.http_logging_policy' -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel( - logging.WARNING -) -logging.getLogger("azure.identity.aio._internal").setLevel(logging.WARNING) - -# Suppress info logs from OpenTelemetry exporter -logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel( - logging.WARNING -) - @router.get("/fetchChartData") async def fetch_chart_data(): @@ -117,9 +101,9 @@ async def conversation(request: Request): # Get the request JSON and last RAG response from the client request_json = await request.json() conversation_id = request_json.get("conversation_id") - query = request_json.get("messages")[-1].get("content") + query = request_json.get("query") chat_service = ChatService(request=request) - result = await chat_service.stream_chat_request(request_json, conversation_id, query) + result = await chat_service.stream_chat_request(conversation_id, query) track_event_if_configured( "ChatStreamSuccess", {"conversation_id": conversation_id, "query": query} diff --git a/src/api/api/history_routes.py b/src/api/api/history_routes.py index 011e1ec6e..7f1cfef5a 100644 --- a/src/api/api/history_routes.py +++ b/src/api/api/history_routes.py @@ -11,8 +11,6 @@ router = APIRouter() -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Check if the Application Insights Instrumentation Key is set in the environment variables @@ -25,50 +23,10 @@ # Log a warning if the Instrumentation Key is not found logging.warning("No Application Insights Instrumentation Key found. Skipping configuration") -# Configure logging -logging.basicConfig(level=logging.INFO) - -# Suppress INFO logs from 'azure.core.pipeline.policies.http_logging_policy' -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel( - logging.WARNING -) -logging.getLogger("azure.identity.aio._internal").setLevel(logging.WARNING) - -# Suppress info logs from OpenTelemetry exporter -logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel( - logging.WARNING -) - # Single instance of HistoryService (if applicable) history_service = HistoryService() -@router.post("/generate") -async def add_conversation(request: Request): - try: - authenticated_user = get_authenticated_user_details( - request_headers=request.headers) - user_id = authenticated_user["user_principal_id"] - - # Parse request body - request_json = await request.json() - - response = await history_service.add_conversation(user_id, request_json) - track_event_if_configured("ConversationCreated", { - "user_id": user_id, - "request": request_json, - }) - return response - - except Exception as e: - logger.exception("Exception in /generate: %s", str(e)) - span = trace.get_current_span() - if span is not None: - span.record_exception(e) - span.set_status(Status(StatusCode.ERROR, str(e))) - return JSONResponse(content={"error": "An internal error has occurred!"}, status_code=500) - - @router.post("/update") async def update_conversation(request: Request): try: diff --git a/src/api/app.py b/src/api/app.py index 2ba6f1158..c91e5fbc9 100644 --- a/src/api/app.py +++ b/src/api/app.py @@ -7,6 +7,8 @@ """ +import logging +import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -21,8 +23,29 @@ from api.api_routes import router as backend_router from api.history_routes import router as history_router +# Load environment variables load_dotenv() +# Configure logging +# Basic application logging (default: INFO level) +AZURE_BASIC_LOGGING_LEVEL = os.getenv("AZURE_BASIC_LOGGING_LEVEL", "INFO").upper() +# Azure package logging (default: WARNING level to suppress INFO) +AZURE_PACKAGE_LOGGING_LEVEL = os.getenv("AZURE_PACKAGE_LOGGING_LEVEL", "WARNING").upper() +# Azure logging packages (default: empty list) +AZURE_LOGGING_PACKAGES = [ + pkg.strip() for pkg in os.getenv("AZURE_LOGGING_PACKAGES", "").split(",") if pkg.strip() +] + +# Basic config: logging.basicConfig(level=logging.INFO) +logging.basicConfig( + level=getattr(logging, AZURE_BASIC_LOGGING_LEVEL, logging.INFO), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +# Package config: Azure loggers set to WARNING to suppress INFO +for logger_name in AZURE_LOGGING_PACKAGES: + logging.getLogger(logger_name).setLevel(getattr(logging, AZURE_PACKAGE_LOGGING_LEVEL, logging.WARNING)) + @asynccontextmanager async def lifespan(fastapi_app: FastAPI): diff --git a/src/api/helpers/utils.py b/src/api/helpers/utils.py index 4c7511d4d..ce854d8c9 100644 --- a/src/api/helpers/utils.py +++ b/src/api/helpers/utils.py @@ -5,10 +5,6 @@ import requests -DEBUG = os.environ.get("DEBUG", "false") -if DEBUG.lower() == "true": - logging.basicConfig(level=logging.DEBUG) - AZURE_SEARCH_PERMITTED_GROUPS_COLUMN = os.environ.get( "AZURE_SEARCH_PERMITTED_GROUPS_COLUMN" ) diff --git a/src/api/services/chart_service.py b/src/api/services/chart_service.py index e1e22a2a3..0b2e60861 100644 --- a/src/api/services/chart_service.py +++ b/src/api/services/chart_service.py @@ -4,8 +4,6 @@ from api.models.input_models import ChartFilters from common.database.sqldb_service import adjust_processed_data_dates, fetch_chart_data, fetch_filters_data -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/src/api/services/chat_service.py b/src/api/services/chat_service.py index a3321a6b5..6ddce2e64 100644 --- a/src/api/services/chat_service.py +++ b/src/api/services/chat_service.py @@ -8,9 +8,6 @@ import json import logging -import time -import uuid -from types import SimpleNamespace import asyncio import random import re @@ -25,15 +22,12 @@ from cachetools import TTLCache -from helpers.utils import format_stream_response from common.config.config import Config # Constants HOST_NAME = "CKM" HOST_INSTRUCTIONS = "Answer questions about call center operations" -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -135,11 +129,10 @@ async def stream_openai_text(self, conversation_id: str, query: str) -> Streamin ChatService.thread_cache[corrupt_key] = thread_id yield "I cannot answer this question with the current data. Please rephrase or add more details." - async def stream_chat_request(self, request_body, conversation_id, query): + async def stream_chat_request(self, conversation_id, query): """ Handles streaming chat requests. """ - history_metadata = request_body.get("history_metadata", {}) async def generate(): try: @@ -150,38 +143,17 @@ async def generate(): assistant_content += str(chunk) if assistant_content: - chat_completion_chunk = { - "id": "", - "model": "", - "created": 0, - "object": "", + # Optimized response - only send fields used by frontend + response = { "choices": [ { - "messages": [], - "delta": {}, + "messages": [ + {"role": "assistant", "content": assistant_content} + ] } - ], - "history_metadata": history_metadata, - "apim-request-id": "", + ] } - - chat_completion_chunk["id"] = str(uuid.uuid4()) - chat_completion_chunk["model"] = "rag-model" - chat_completion_chunk["created"] = int(time.time()) - chat_completion_chunk["object"] = "extensions.chat.completion.chunk" - chat_completion_chunk["choices"][0]["messages"].append( - {"role": "assistant", "content": assistant_content} - ) - chat_completion_chunk["choices"][0]["delta"] = { - "role": "assistant", - "content": assistant_content, - } - - completion_chunk_obj = json.loads( - json.dumps(chat_completion_chunk), - object_hook=lambda d: SimpleNamespace(**d), - ) - yield json.dumps(format_stream_response(completion_chunk_obj, history_metadata, "")) + "\n\n" + yield json.dumps(response) + "\n\n" except AgentException as e: error_message = str(e) @@ -201,27 +173,3 @@ async def generate(): yield json.dumps({"error": "An error occurred while processing the request."}) + "\n\n" return generate() - - async def complete_chat_request(self, query, last_rag_response=None): - """ - Completes a chat request by generating a chart from the RAG response. - """ - if not last_rag_response: - return {"error": "A previous RAG response is required to generate a chart."} - - # Process RAG response to generate chart data - chart_data = await self.process_rag_response(last_rag_response, query) - - if not chart_data or "error" in chart_data: - return { - "error": "Chart could not be generated from this data. Please ask a different question.", - "error_desc": str(chart_data), - } - - logger.info("Successfully generated chart data.") - return { - "id": str(uuid.uuid4()), - "model": "azure-openai", - "created": int(time.time()), - "object": chart_data, - } diff --git a/src/api/services/history_service.py b/src/api/services/history_service.py index 9eab4a130..39000d7e5 100644 --- a/src/api/services/history_service.py +++ b/src/api/services/history_service.py @@ -6,11 +6,8 @@ from azure.ai.agents.models import MessageRole, ListSortOrder from common.config.config import Config from common.database.cosmosdb_service import CosmosConversationClient -from helpers.chat_helper import complete_chat_request from helpers.azure_credential_utils import get_azure_credential -# Configure logging -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) @@ -123,41 +120,6 @@ async def generate_title(self, conversation_messages): return user_messages[-1]["content"][:50] return "New Conversation" - async def add_conversation(self, user_id: str, request_json: dict): - try: - conversation_id = request_json.get("conversation_id") - messages = request_json.get("messages", []) - - history_metadata = {} - - # make sure cosmos is configured - cosmos_conversation_client = self.init_cosmosdb_client() - if not cosmos_conversation_client: - raise ValueError("CosmosDB is not configured or unavailable") - - if not conversation_id: - title = await self.generate_title(messages) - conversation_dict = await cosmos_conversation_client.create_conversation(user_id, title) - conversation_id = conversation_dict["id"] - history_metadata["title"] = title - history_metadata["date"] = conversation_dict["createdAt"] - - if messages and messages[-1]["role"] == "user": - created_message = await cosmos_conversation_client.create_message(conversation_id, user_id, messages[-1]) - if created_message == "Conversation not found": - raise ValueError( - f"Conversation not found for ID: {conversation_id}") - else: - raise ValueError("No user message found") - - request_body = { - "messages": messages, "history_metadata": { - "conversation_id": conversation_id}} - return await complete_chat_request(request_body) - except Exception: - logger.exception("Error in add_conversation") - raise - async def update_conversation(self, user_id: str, request_json: dict): conversation_id = request_json.get("conversation_id") messages = request_json.get("messages", []) diff --git a/src/tests/api/api/test_history_routes.py b/src/tests/api/api/test_history_routes.py index b054f7236..41ae728d9 100644 --- a/src/tests/api/api/test_history_routes.py +++ b/src/tests/api/api/test_history_routes.py @@ -27,19 +27,6 @@ async def client(): yield ac -@pytest.mark.asyncio -@patch("auth.auth_utils.get_authenticated_user_details") -@patch("services.history_service.HistoryService.add_conversation", new_callable=AsyncMock) -@patch("common.logging.event_utils.track_event_if_configured") -async def test_add_conversation(mock_track, mock_add, mock_auth, client, headers): - mock_auth.return_value = {"user_principal_id": "user123"} - mock_add.return_value = {"result": "ok"} - - res = await client.post("/generate", json={"message": "hello"}, headers=headers) - assert res.status_code == 200 - assert res.json() == {"result": "ok"} - - @pytest.mark.asyncio @patch("auth.auth_utils.get_authenticated_user_details") @patch("services.history_service.HistoryService.update_conversation", new_callable=AsyncMock) @@ -311,8 +298,3 @@ async def test_ensure_cosmos_unknown_error(mock_track, mock_ensure, client): assert res.json()["error"] == "CosmosDB is not configured or not working" -@pytest.mark.asyncio -@patch("auth.auth_utils.get_authenticated_user_details", side_effect=Exception("auth error")) -async def test_add_conversation_exception(mock_auth, client, headers): - res = await client.post("/generate", json={"message": "hi"}, headers=headers) - assert res.status_code == 500 \ No newline at end of file diff --git a/src/tests/api/services/test_chat_service.py b/src/tests/api/services/test_chat_service.py index ffb45b4d8..09f091ff9 100644 --- a/src/tests/api/services/test_chat_service.py +++ b/src/tests/api/services/test_chat_service.py @@ -5,7 +5,6 @@ import pytest from fastapi import HTTPException, status from semantic_kernel.exceptions.agent_exceptions import AgentException as RealAgentException -from azure.ai.agents.models import MessageRole @@ -244,16 +243,8 @@ async def mock_invoke_stream(*args, **kwargs): assert "I cannot answer this question with the current data" in chunks[0] @pytest.mark.asyncio - @patch('services.chat_service.uuid.uuid4') - @patch('services.chat_service.time.time') - @patch('services.chat_service.format_stream_response') - async def test_stream_chat_request_success(self, mock_format_stream, mock_time, mock_uuid, chat_service): + async def test_stream_chat_request_success(self, chat_service): """Test successful stream chat request.""" - # Setup mocks - mock_uuid.return_value = "test-uuid" - mock_time.return_value = 1234567890 - mock_format_stream.return_value = {"formatted": "response"} - # Mock stream_openai_text async def mock_stream_openai_text(conversation_id, query): yield "Hello" @@ -261,8 +252,7 @@ async def mock_stream_openai_text(conversation_id, query): chat_service.stream_openai_text = mock_stream_openai_text - request_body = {"history_metadata": {"test": "metadata"}} - generator = await chat_service.stream_chat_request(request_body, "conv_1", "Hello") + generator = await chat_service.stream_chat_request("conv_1", "Hello") chunks = [] async for chunk in generator: @@ -272,7 +262,11 @@ async def mock_stream_openai_text(conversation_id, query): # Verify the chunks contain expected structure for chunk in chunks: chunk_data = json.loads(chunk.strip()) - assert "formatted" in chunk_data + assert "choices" in chunk_data + assert len(chunk_data["choices"]) > 0 + assert "messages" in chunk_data["choices"][0] + assert len(chunk_data["choices"][0]["messages"]) > 0 + assert chunk_data["choices"][0]["messages"][0]["role"] == "assistant" @pytest.mark.asyncio async def test_stream_chat_request_agent_exception_rate_limit(self, chat_service): @@ -285,8 +279,7 @@ async def mock_stream_openai_text_rate_limit_error(conversation_id, query): chat_service.stream_openai_text = mock_stream_openai_text_rate_limit_error - request_body = {"history_metadata": {}} - generator = await chat_service.stream_chat_request(request_body, "conv_1", "Hello") + generator = await chat_service.stream_chat_request("conv_1", "Hello") chunks = [] async for chunk in generator: @@ -309,8 +302,7 @@ async def mock_stream_openai_text_generic_error(conversation_id, query): chat_service.stream_openai_text = mock_stream_openai_text_generic_error - request_body = {"history_metadata": {}} - generator = await chat_service.stream_chat_request(request_body, "conv_1", "Hello") + generator = await chat_service.stream_chat_request("conv_1", "Hello") chunks = [] async for chunk in generator: @@ -333,8 +325,7 @@ async def mock_stream_openai_text_generic_error(conversation_id, query): chat_service.stream_openai_text = mock_stream_openai_text_generic_error - request_body = {"history_metadata": {}} - generator = await chat_service.stream_chat_request(request_body, "conv_1", "Hello") + generator = await chat_service.stream_chat_request("conv_1", "Hello") chunks = [] async for chunk in generator: @@ -345,46 +336,4 @@ async def mock_stream_openai_text_generic_error(conversation_id, query): error_data = json.loads(chunks[0].strip()) assert "error" in error_data assert "An error occurred while processing the request." == error_data["error"] - - @pytest.mark.asyncio - async def test_complete_chat_request_success(self, chat_service): - mock_chart_data = { - "type": "bar", - "data": { - "labels": ["A"], - "datasets": [{"data": [1]}] - } - } - - chat_service.process_rag_response = AsyncMock(return_value=mock_chart_data) - - result = await chat_service.complete_chat_request("Query", last_rag_response="RAG response") - - assert result["object"]["type"] == "bar" - - - @pytest.mark.asyncio - async def test_complete_chat_request_no_rag_response(self, chat_service): - """Test complete chat request without RAG response.""" - result = await chat_service.complete_chat_request("Query", last_rag_response=None) - - assert "error" in result - assert result["error"] == "A previous RAG response is required to generate a chart." - - @pytest.mark.asyncio - async def test_complete_chat_request_chart_error(self, chat_service): - chat_service.process_rag_response = AsyncMock(return_value={"error": "Chart generation failed"}) - - result = await chat_service.complete_chat_request("Query", last_rag_response="RAG response") - - assert "error" in result - - - @pytest.mark.asyncio - async def test_complete_chat_request_empty_chart_data(self, chat_service): - chat_service.process_rag_response = AsyncMock(return_value=None) - - result = await chat_service.complete_chat_request("Query", last_rag_response="RAG response") - - assert "error" in result diff --git a/src/tests/api/services/test_history_service.py b/src/tests/api/services/test_history_service.py index 59b84e5bc..5626bb78b 100644 --- a/src/tests/api/services/test_history_service.py +++ b/src/tests/api/services/test_history_service.py @@ -30,9 +30,8 @@ def history_service(mock_config_instance): with patch("services.history_service.Config", return_value=mock_config_instance): # Create patches for other dependencies used by HistoryService with patch("services.history_service.CosmosConversationClient"): - with patch("services.history_service.complete_chat_request"): - service = HistoryService() - return service + service = HistoryService() + return service @pytest.fixture @@ -146,95 +145,6 @@ async def test_generate_title_exception(self, history_service): result = await history_service.generate_title(conversation_messages) assert result == "Fallback content" - @pytest.mark.asyncio - async def test_add_conversation_new(self, history_service): - """Test adding a new conversation""" - user_id = "test-user-id" - request_json = { - "conversation_id": None, - "messages": [{"role": "user", "content": "Hello"}] - } - - mock_cosmos_client = AsyncMock() - mock_cosmos_client.create_conversation = AsyncMock( - return_value={"id": "new-conv-id", "title": "Test Title", "createdAt": "2023-01-01T00:00:00Z"} - ) - mock_cosmos_client.create_message = AsyncMock(return_value="success") - - with patch.object(history_service, "init_cosmosdb_client", return_value=mock_cosmos_client): - with patch.object(history_service, "generate_title", AsyncMock(return_value="Test Title")): - with patch("services.history_service.complete_chat_request", AsyncMock(return_value={"response": "test"})): - result = await history_service.add_conversation(user_id, request_json) - assert result == {"response": "test"} - - # Verify calls - mock_cosmos_client.create_conversation.assert_awaited_once() - mock_cosmos_client.create_message.assert_awaited_once() - - @pytest.mark.asyncio - async def test_add_conversation_existing(self, history_service): - """Test adding to an existing conversation""" - user_id = "test-user-id" - request_json = { - "conversation_id": "existing-id", - "messages": [{"role": "user", "content": "Hello"}] - } - - mock_cosmos_client = AsyncMock() - mock_cosmos_client.create_message = AsyncMock(return_value="success") - - with patch.object(history_service, "init_cosmosdb_client", return_value=mock_cosmos_client): - with patch("services.history_service.complete_chat_request", AsyncMock(return_value={"response": "test"})): - result = await history_service.add_conversation(user_id, request_json) - assert result == {"response": "test"} - - # Verify calls - mock_cosmos_client.create_message.assert_awaited_once() - - @pytest.mark.asyncio - async def test_add_conversation_cosmos_not_configured(self, history_service): - """Test adding conversation when cosmos is not configured""" - user_id = "test-user-id" - request_json = { - "conversation_id": "existing-id", - "messages": [{"role": "user", "content": "Hello"}] - } - - with patch.object(history_service, "init_cosmosdb_client", return_value=None): - with pytest.raises(ValueError, match="CosmosDB is not configured or unavailable"): - await history_service.add_conversation(user_id, request_json) - - @pytest.mark.asyncio - async def test_add_conversation_no_user_message(self, history_service): - """Test adding conversation with no user message""" - user_id = "test-user-id" - request_json = { - "conversation_id": "existing-id", - "messages": [{"role": "assistant", "content": "Hello"}] - } - - mock_cosmos_client = AsyncMock() - - with patch.object(history_service, "init_cosmosdb_client", return_value=mock_cosmos_client): - with pytest.raises(ValueError, match="No user message found"): - await history_service.add_conversation(user_id, request_json) - - @pytest.mark.asyncio - async def test_add_conversation_conversation_not_found(self, history_service): - """Test adding to a non-existent conversation""" - user_id = "test-user-id" - request_json = { - "conversation_id": "non-existent-id", - "messages": [{"role": "user", "content": "Hello"}] - } - - mock_cosmos_client = AsyncMock() - mock_cosmos_client.create_message = AsyncMock(return_value="Conversation not found") - - with patch.object(history_service, "init_cosmosdb_client", return_value=mock_cosmos_client): - with pytest.raises(ValueError, match="Conversation not found"): - await history_service.add_conversation(user_id, request_json) - @pytest.mark.asyncio async def test_update_conversation(self, history_service): """Test updating an existing conversation"""