diff --git a/.gitignore b/.gitignore index e62f35001..f930f2ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -464,4 +464,4 @@ __pycache__/ data/sample_code/ # Bicep local files *.local*.bicepparam -*.local*.parameters.json \ No newline at end of file +*.local*.parameters.jsonMYC-SA diff --git a/azure.yaml b/azure.yaml index af9c81738..a3b344d94 100644 --- a/azure.yaml +++ b/azure.yaml @@ -4,6 +4,7 @@ metadata: template: multi-agent-custom-automation-engine-solution-accelerator@1.0 requiredVersions: azd: '>= 1.18.0 != 1.23.9' + bicep: '>= 0.33.0' hooks: postdeploy: windows: diff --git a/infra/main.bicep b/infra/main.bicep index 998a90a5e..5ece1fbd6 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -35,18 +35,18 @@ var deployerInfo = deployer() var deployingUserPrincipalId = deployerInfo.objectId // Restricting deployment to only supported Azure OpenAI regions validated with GPT-4o model -@allowed(['australiaeast', 'eastus2', 'francecentral', 'japaneast', 'norwayeast', 'swedencentral', 'uksouth', 'westus']) +@allowed(['eastus2', 'swedencentral', 'westus']) @metadata({ azd: { type: 'location' usageName: [ - 'OpenAI.GlobalStandard.gpt4.1, 150' - 'OpenAI.GlobalStandard.o4-mini, 50' - 'OpenAI.GlobalStandard.gpt4.1-mini, 50' + 'OpenAI.GlobalStandard.gpt4.1, 80' + 'OpenAI.GlobalStandard.o4-mini, 30' + 'OpenAI.GlobalStandard.gpt4.1-mini, 30' ] } }) -@description('Required. Location for all AI service resources. This should be one of the supported Azure AI Service locations.') +@description('Required. Location for all AI service resources. Limited to regions where gpt-4.1, gpt-4.1-mini, and o4-mini are all available as GlobalStandard.') param azureAiServiceLocation string @minLength(1) @@ -100,14 +100,17 @@ param gptModelDeploymentType string = 'GlobalStandard' @description('Optional. GPT model deployment type. Defaults to GlobalStandard.') param gptReasoningModelDeploymentType string = 'GlobalStandard' -@description('Optional. AI model deployment token capacity. Defaults to 50 for optimal performance.') -param gptModelCapacity int = 50 +@description('Optional. AI model deployment token capacity (thousands of tokens per minute). Total across all 3 models must not exceed your subscription GlobalStandard quota. Reduce if provisioning fails with InsufficientQuota.') +@minValue(1) +param gptModelCapacity int = 30 -@description('Optional. AI model deployment token capacity. Defaults to 150 for optimal performance.') -param gpt4_1ModelCapacity int = 150 +@description('Optional. AI model deployment token capacity (thousands of tokens per minute). This is the primary model — allocate the most capacity here.') +@minValue(1) +param gpt4_1ModelCapacity int = 80 -@description('Optional. AI model deployment token capacity. Defaults to 50 for optimal performance.') -param gptReasoningModelCapacity int = 50 +@description('Optional. AI model deployment token capacity (thousands of tokens per minute). Reasoning model used for complex tasks.') +@minValue(1) +param gptReasoningModelCapacity int = 30 @description('Optional. The tags to apply to all deployed Azure resources.') param tags resourceInput<'Microsoft.Resources/resourceGroups@2025-04-01'>.tags = {} @@ -125,10 +128,10 @@ param enableRedundancy bool = false param enablePrivateNetworking bool = false @secure() -@description('Optional. The user name for the administrator account of the virtual machine. Allows to customize credentials if `enablePrivateNetworking` is set to true.') +@description('Required when enablePrivateNetworking is true. The admin username for the jumpbox VM. Must be provided — no default for security.') param virtualMachineAdminUsername string? -@description('Optional. The password for the administrator account of the virtual machine. Allows to customize credentials if `enablePrivateNetworking` is set to true.') +@description('Required when enablePrivateNetworking is true. The admin password for the jumpbox VM. Must meet Azure complexity requirements (12+ chars, uppercase, lowercase, number, special char). Must be provided — no default for security.') @secure() param virtualMachineAdminPassword string? @@ -616,8 +619,8 @@ module virtualMachine 'br/public:avm/res/compute/virtual-machine:0.17.0' = if (e computerName: take(virtualMachineResourceName, 15) osType: 'Windows' vmSize: virtualMachineSize - adminUsername: virtualMachineAdminUsername ?? 'JumpboxAdminUser' - adminPassword: virtualMachineAdminPassword ?? 'JumpboxAdminP@ssw0rd1234!' + adminUsername: virtualMachineAdminUsername! + adminPassword: virtualMachineAdminPassword! patchMode: 'AutomaticByPlatform' bypassPlatformSafetyChecksOnUserSchedule: true maintenanceConfigurationResourceId: maintenanceConfiguration!.outputs.resourceId @@ -889,6 +892,53 @@ module aiFoundryAiServices 'br:mcr.microsoft.com/bicep/avm/res/cognitive-service apiProperties: { //staticsEnabled: false } + deployments: [] // Deploy models separately via ai-services-deployments.bicep to avoid ETag conflicts + networkAcls: { + defaultAction: 'Allow' + virtualNetworkRules: [] + ipRules: [] + } + managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] } //To create accounts or projects, you must enable a managed identity on your resource + roleAssignments: [ + { + roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' // Cognitive Services OpenAI User + principalId: userAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User + principalId: deployingUserPrincipalId + principalType: deployerPrincipalType + } + { + roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer + principalId: deployingUserPrincipalId + principalType: deployerPrincipalType + } + ] + // WAF aligned configuration for Monitoring + diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' + // Private endpoints are deployed separately via the aiFoundryPrivateEndpoint module below + privateEndpoints: [] + } +} + +// Deploy models sequentially after the AI Services account is fully created (avoids ETag conflicts) +module newAiFoundryModelDeployments 'modules/ai-services-deployments.bicep' = if (!useExistingAiFoundryAiProject) { + name: take('module.ai-services-model-deployments.new.${aiFoundryAiServicesResourceName}', 64) + params: { + name: aiFoundryAiServices!.outputs.name deployments: [ { name: aiFoundryAiServicesModelDeployment.name @@ -898,10 +948,7 @@ module aiFoundryAiServices 'br:mcr.microsoft.com/bicep/avm/res/cognitive-service version: aiFoundryAiServicesModelDeployment.version } raiPolicyName: aiFoundryAiServicesModelDeployment.raiPolicyName - sku: { - name: aiFoundryAiServicesModelDeployment.sku.name - capacity: aiFoundryAiServicesModelDeployment.sku.capacity - } + sku: aiFoundryAiServicesModelDeployment.sku } { name: aiFoundryAiServices4_1ModelDeployment.name @@ -911,10 +958,7 @@ module aiFoundryAiServices 'br:mcr.microsoft.com/bicep/avm/res/cognitive-service version: aiFoundryAiServices4_1ModelDeployment.version } raiPolicyName: aiFoundryAiServices4_1ModelDeployment.raiPolicyName - sku: { - name: aiFoundryAiServices4_1ModelDeployment.sku.name - capacity: aiFoundryAiServices4_1ModelDeployment.sku.capacity - } + sku: aiFoundryAiServices4_1ModelDeployment.sku } { name: aiFoundryAiServicesReasoningModelDeployment.name @@ -924,51 +968,38 @@ module aiFoundryAiServices 'br:mcr.microsoft.com/bicep/avm/res/cognitive-service version: aiFoundryAiServicesReasoningModelDeployment.version } raiPolicyName: aiFoundryAiServicesReasoningModelDeployment.raiPolicyName - sku: { - name: aiFoundryAiServicesReasoningModelDeployment.sku.name - capacity: aiFoundryAiServicesReasoningModelDeployment.sku.capacity - } + sku: aiFoundryAiServicesReasoningModelDeployment.sku } ] - networkAcls: { - defaultAction: 'Allow' - virtualNetworkRules: [] - ipRules: [] - } - managedIdentities: { userAssignedResourceIds: [userAssignedIdentity!.outputs.resourceId] } //To create accounts or projects, you must enable a managed identity on your resource roleAssignments: [ { - roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User + roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' principalId: userAssignedIdentity.outputs.principalId principalType: 'ServicePrincipal' } { - roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer + roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' principalId: userAssignedIdentity.outputs.principalId principalType: 'ServicePrincipal' } { - roleDefinitionIdOrName: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' // Cognitive Services OpenAI User + roleDefinitionIdOrName: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' principalId: userAssignedIdentity.outputs.principalId principalType: 'ServicePrincipal' } { - roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' // Azure AI User + roleDefinitionIdOrName: '53ca6127-db72-4b80-b1b0-d745d6d5456d' principalId: deployingUserPrincipalId principalType: deployerPrincipalType } { - roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' // Azure AI Developer + roleDefinitionIdOrName: '64702f94-c441-49e6-a78b-ef80e0188fee' principalId: deployingUserPrincipalId principalType: deployerPrincipalType } ] - // WAF aligned configuration for Monitoring - diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : null - publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' - // Private endpoints are deployed separately via the aiFoundryPrivateEndpoint module below - privateEndpoints: [] } + dependsOn: [aiFoundryAiServices] } module aiFoundryPrivateEndpoint 'br/public:avm/res/network/private-endpoint:0.8.1' = if (enablePrivateNetworking && !useExistingAiFoundryAiProject) { @@ -1449,7 +1480,7 @@ module containerAppMcp 'br/public:avm/res/app/container-app:0.18.1' = { } { name: 'ENABLE_AUTH' - value: 'false' + value: 'true' } { name: 'TENANT_ID' @@ -1689,8 +1720,7 @@ module searchServiceUpdate 'br/public:avm/res/search/search-service:0.11.1' = { // Enabled the Public access because other services are not able to connect with search search AVM module when public access is disabled - // publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' - publicNetworkAccess: 'Enabled' + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' networkRuleSet: { bypass: 'AzureServices' } @@ -1721,26 +1751,24 @@ module searchServiceUpdate 'br/public:avm/res/search/search-service:0.11.1' = { } ] - //Removing the Private endpoints as we are facing the issue with connecting to search service while comminicating with agents - - privateEndpoints: [] - // privateEndpoints: enablePrivateNetworking - // ? [ - // { - // name: 'pep-search-${solutionSuffix}' - // customNetworkInterfaceName: 'nic-search-${solutionSuffix}' - // privateDnsZoneGroup: { - // privateDnsZoneGroupConfigs: [ - // { - // privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.search]!.outputs.resourceId - // } - // ] - // } - // subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] - // service: 'searchService' - // } - // ] - // : [] + privateEndpoints: enablePrivateNetworking + ? [ + { + name: 'pep-search-${solutionSuffix}' + customNetworkInterfaceName: 'nic-search-${solutionSuffix}' + privateDnsZoneGroup: { + privateDnsZoneGroupConfigs: [ + { + privateDnsZoneResourceId: avmPrivateDnsZones[dnsZoneIndex.search]!.outputs.resourceId + } + ] + } + subnetResourceId: virtualNetwork!.outputs.subnetResourceIds[0] + service: 'searchService' + } + ] + : [] + publicNetworkAccess: enablePrivateNetworking ? 'Disabled' : 'Enabled' } dependsOn: [ searchService @@ -1784,6 +1812,7 @@ module keyvault 'br/public:avm/res/key-vault/vault:0.12.1' = { enableVaultForTemplateDeployment: true enableRbacAuthorization: true enableSoftDelete: true + enablePurgeProtection: false softDeleteRetentionInDays: 7 diagnosticSettings: enableMonitoring ? [{ workspaceResourceId: logAnalyticsWorkspaceResourceId }] : [] // WAF aligned configuration for Private Networking