Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 57 additions & 28 deletions infra/avm/modules/ai/ai-foundry-model-deployment.bicep
Original file line number Diff line number Diff line change
@@ -1,35 +1,64 @@
@description('Required. Name of the AI Services account.')
param aiServicesName string
// ============================================================================
// Module: Model Deployment
// Description: Deploys a single AI model to an existing AI Services account.
// Called repetitively from main.bicep for each model in the array.
// Generic, reusable across GSAs.
// ============================================================================

@description('Required. Array of model deployments to create.')
param deployments array = []
@description('Required. Name of the parent AI Services account.')
param aiServicesAccountName string

// Reference AI Services account (module is scoped to the correct resource group)
resource aiServices 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = {
name: aiServicesName
@description('Required. Name for this model deployment.')
param deploymentName string

@description('Optional. Model format (e.g., OpenAI).')
param modelFormat string = 'OpenAI'

@description('Required. Model name (e.g., gpt-4o, text-embedding-ada-002).')
param modelName string

@description('Optional. Model version. Empty string means latest.')
param modelVersion string = ''

@description('Optional. RAI policy name.')
param raiPolicyName string = 'Microsoft.Default'

@description('Required. SKU name (e.g., Standard, GlobalStandard).')
param skuName string

@description('Required. SKU capacity (tokens per minute in thousands).')
param skuCapacity int

// ============================================================================
// Model Deployment
// ============================================================================
resource aiServicesAccount 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = {
name: aiServicesAccountName
}

// Deploy models to AI Services account
// Using batchSize(1) to avoid concurrent deployment issues
@batchSize(1)
resource modelDeployments 'Microsoft.CognitiveServices/accounts/deployments@2025-12-01' = [
for (deployment, index) in deployments: {
parent: aiServices
name: deployment.name
properties: {
model: {
format: deployment.format
name: deployment.model
version: deployment.version
}
raiPolicyName: deployment.raiPolicyName
}
sku: {
name: deployment.sku.name
capacity: deployment.sku.capacity
resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2025-12-01' = {
parent: aiServicesAccount
name: deploymentName
properties: {
model: {
format: modelFormat
name: modelName
version: !empty(modelVersion) ? modelVersion : null
}
raiPolicyName: raiPolicyName
}
sku: {
name: skuName
capacity: skuCapacity
}
]
}

// ============================================================================
// Outputs
// ============================================================================

@description('Name of the deployed model.')
output name string = modelDeployment.name

@description('The names of the deployed models.')
output deployedModelNames array = [for (deployment, i) in deployments: modelDeployments[i].name]
@description('Resource ID of the model deployment.')
output resourceId string = modelDeployment.id
159 changes: 121 additions & 38 deletions infra/avm/modules/ai/ai-foundry-project.bicep
Original file line number Diff line number Diff line change
@@ -1,58 +1,141 @@
@description('Required. Name of the AI Services project.')
param name string
// ============================================================================
// Module: AI Foundry Project (Account + Project)
// Description: AVM wrapper for Azure AI Services account creation and
// AI Foundry project provisioning. Generic, reusable across GSAs.
// AVM Module: avm/res/cognitive-services/account
// WAF: https://learn.microsoft.com/azure/well-architected/service-guides/azure-openai
// ============================================================================

@description('Required. The location of the Project resource.')
param location string = resourceGroup().location
@description('Required. Solution name suffix used to generate resource names.')
param solutionName string

@description('Optional. The description of the AI Foundry project to create. Defaults to the project name.')
param desc string = name
@description('Optional. Override name for the AI Services account. Defaults to aif-{solutionName}.')
param name string = 'aif-${solutionName}'

@description('Required. Name of the existing Cognitive Services resource to create the AI Foundry project in.')
param aiServicesName string
@description('Optional. Override name for the AI Foundry project. Defaults to proj-{solutionName}.')
param projectName string = 'proj-${solutionName}'

@description('Required. Azure Existing AI Project ResourceID.')
param azureExistingAIProjectResourceId string = ''
@description('Required. Azure region for the resources.')
param location string

@description('Optional. Tags to be applied to the resources.')
@description('Optional. Tags to apply to resources.')
param tags object = {}

var useExistingAiFoundryAiProject = !empty(azureExistingAIProjectResourceId)
var existingOpenAIEndpoint = useExistingAiFoundryAiProject
? format('https://{0}.openai.azure.com/', split(azureExistingAIProjectResourceId, '/')[8])
: ''
@description('Optional. SKU name for the AI Services account.')
param skuName string = 'S0'

// Reference to cognitive service in current resource group for new projects
resource cogServiceReference 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = {
name: aiServicesName
@description('Optional. Whether to disable local (key-based) authentication.')
param disableLocalAuth bool = true

@description('Optional. Whether to allow project management (AI Foundry hub).')
param allowProjectManagement bool = true

@description('Optional. Public network access setting.')
param publicNetworkAccess string = 'Enabled'

@description('Optional. Managed identity type for the resources.')
@allowed(['SystemAssigned', 'UserAssigned', 'SystemAssigned, UserAssigned', 'None'])
param identityType string = 'SystemAssigned'

@description('Optional. Network ACLs default action.')
@allowed(['Allow', 'Deny'])
param networkAclsDefaultAction string = 'Allow'

// --- WAF: Monitoring ---
@description('Optional. Diagnostic settings for the resource.')
param diagnosticSettings array?

// --- WAF: Telemetry ---
@description('Optional. Enable/Disable usage telemetry for module.')
param enableTelemetry bool = true

// --- Role Assignments ---
@description('Optional. Array of role assignments to create on the AI Services account.')
param roleAssignments array?

// ============================================================================
// AI Services Account (AVM Module)
// ============================================================================
module aiServicesAccount 'br/public:avm/res/cognitive-services/account:0.14.2' = {
name: take('avm.res.cognitive-services.account.${name}', 64)
params: {
name: name
location: location
tags: tags
enableTelemetry: enableTelemetry
sku: skuName
kind: 'AIServices'
disableLocalAuth: disableLocalAuth
allowProjectManagement: allowProjectManagement
customSubDomainName: name
networkAcls: {
defaultAction: networkAclsDefaultAction
virtualNetworkRules: []
ipRules: []
}
publicNetworkAccess: publicNetworkAccess
managedIdentities: {
systemAssigned: true
}
diagnosticSettings: diagnosticSettings
deployments: []
roleAssignments: roleAssignments
// Private endpoints deployed separately to avoid AccountProvisioningStateInvalid
privateEndpoints: []
}
}

resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-12-01' = {
parent: cogServiceReference
// ============================================================================
// AI Foundry Project
// ============================================================================
resource aiServices 'Microsoft.CognitiveServices/accounts@2025-12-01' existing = {
name: name
tags: tags
dependsOn: [aiServicesAccount]
}

resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-12-01' = {
parent: aiServices
name: projectName
location: location
tags: tags
kind: 'AIServices'
identity: {
type: 'SystemAssigned'
}
properties: {
description: desc
displayName: name
type: identityType
}
properties: {}
dependsOn: [aiServicesAccount]
}

@description('Required. Name of the AI project.')
output name string = aiProject.name
// ============================================================================
// Outputs
// ============================================================================

@description('Resource ID of the AI Services account.')
output resourceId string = aiServices.id

@description('Name of the AI Services account.')
output name string = aiServices.name

@description('Endpoint of the AI Services account (OpenAI Language Model Instance API).')
output endpoint string = aiServices.properties.endpoints['OpenAI Language Model Instance API']

@description('Endpoint of the AI Services account (Cognitive Services).')
output cognitiveServicesEndpoint string = aiServices.properties.endpoint

@description('Azure OpenAI Content Understanding endpoint URL.')
output azureOpenAiCuEndpoint string = aiServices.properties.endpoints['Content Understanding']

@description('System-assigned identity principal ID of the AI Services account.')
output principalId string = aiServices.identity.principalId

@description('Required. Resource ID of the AI project.')
output resourceId string = aiProject.id
@description('Resource ID of the AI Foundry project.')
output projectResourceId string = aiProject.id

@description('Required. API endpoint for the AI project.')
output apiEndpoint string = aiProject!.properties.endpoints['AI Foundry API']
@description('Name of the AI Foundry project.')
output projectName string = aiProject.name

@description('Contains AI Endpoint.')
output aoaiEndpoint string = !empty(existingOpenAIEndpoint)
? existingOpenAIEndpoint
: cogServiceReference.properties.endpoints['OpenAI Language Model Instance API']
@description('AI Foundry project endpoint.')
output projectEndpoint string = aiProject.properties.endpoints['AI Foundry API']

@description('Required. Principal ID of the AI project system-assigned managed identity.')
output systemAssignedMIPrincipalId string = aiProject.identity.principalId
@description('System-assigned identity principal ID of the project.')
output projectIdentityPrincipalId string = aiProject.identity.principalId
Loading