diff --git a/infra/README.md b/infra/README.md index 50ec49f..afd958f 100644 --- a/infra/README.md +++ b/infra/README.md @@ -1,17 +1,41 @@ # Managed App Instructions -This guide is a temporary document that walks through the progress made so far to convert the graphrag solution accelerator to a managed app. +This guide is a temporary document that walks through the process to convert the graphrag solution accelerator to a managed app. -1. Auto format the bicep code +### 1. Auto format the bicep code + +As a precaution, start by auto-formating and linting the bicep code to detect any mistakes early-on. ```bash +cd /infra find . -type f -name "*.bicep" -exec az bicep format --file {} \; +find . -type f -name "*.bicep" -exec az bicep lint --file {} \; ``` -2. Convert bicep -> ARM +### 2. Convert bicep -> ARM ```bash az bicep build --file main.bicep --outfile managed-app/mainTemplate.json ``` -3. Test the Portal Interface -Use the [Azure Portal Sandbox](https://portal.azure.com/#blade/Microsoft_Azure_CreateUIDef/SandboxBlade) to test and make any UI changes in `managed-app/createUiDefinition.json`. To make additional changes to the Azure portal experience, start by reading some [documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/managed-applications/create-uidefinition-overview) and copying the contents of `createUiDefinition.json` into the sandbox environment. +### 3. Create & test the Azure portal interface + +Use the [Azure Portal Sandbox](https://portal.azure.com/#blade/Microsoft_Azure_CreateUIDef/SandboxBlade) to test and make any UI changes that are defined in [createUiDefinition.json](createUiDefinition.json). To make additional changes to the Azure portal experience, start by reading some [documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/managed-applications/create-uidefinition-overview) and copying the contents of `createUiDefinition.json` into the sandbox environment. + +### 4. Package up the managed app code + +The name of the final two files (`mainTemplate.json` and `createUiDefinition.json`) cannot be changed. The file names are also case-sensitive and cannot be changed at this time. Managed apps require these files to be packaged up into a zip file (where the json files must be at the root directory). + +```bash +cd /infra/managed-app +zip -rj managed-app.zip . +``` + +This zip file can then be uploaded to an Azure Storage location when setting up a [Service Catalog Managed Application Definition](https://ms.portal.azure.com/#view/Microsoft_Azure_Marketplace/GalleryItemDetailsBladeNopdl/id/Microsoft.ApplianceDefinition/selectionMode~/false/resourceGroupId//resourceGroupLocation//dontDiscardJourney~/false/selectedMenuId/home/launchingContext~/%7B%22galleryItemId%22%3A%22Microsoft.ApplianceDefinition%22%2C%22source%22%3A%5B%22GalleryFeaturedMenuItemPart%22%2C%22VirtualizedTileDetails%22%5D%2C%22menuItemId%22%3A%22home%22%2C%22subMenuItemId%22%3A%22Search%20results%22%2C%22telemetryId%22%3A%2220409084-39a1-4800-bbce-d0b26a6f46a4%22%7D/searchTelemetryId/d7d20e05-ca16-47f7-bed5-9c7b8d2fa641). + +### 5. Create the Service Catalog Managed App Definition + +In the Azure Portal, go to Marketplace and create a `Service Catalog Managed App Definition`. You must provide a uri link to the uploaded `managed-app.zip` file as part of the creation process. + +### 6. Deploy the managed app + +In the Azure Portal, find and click on the managed app definition resource that was created in the previous step. A button option to `Deploy from definition` will be available. Click on it and proceed through the setup steps (defined by the `createUiDefinitions.json` file) that a consumer would experience when installing the managed app. diff --git a/infra/core/aoai/aoai.bicep b/infra/core/aoai/aoai.bicep index a8a8fd0..b5b1b1e 100644 --- a/infra/core/aoai/aoai.bicep +++ b/infra/core/aoai/aoai.bicep @@ -4,17 +4,23 @@ param openAiName string = 'openai${uniqueString(resourceGroup().id)}' @description('Location for the Azure OpenAI instance') param location string = resourceGroup().location -@description('LLM model deployment name') -param llmModelDeploymentName string = 'gpt-4o' +@description('LLM model name') +param llmModelName string = 'gpt-4o' -@description('Embedding model deployment name') -param embeddingModelDeploymentName string = 'text-embedding-ada-002' +@description('LLM Model API version') +param llmModelVersion string -@description('TPM quota for GPT-4o deployment') -param gpt4oTpm int = 10 +@description('Embedding model name') +param embeddingModelName string = 'text-embedding-ada-002' -@description('TPM quota for text-embedding-ada-002 deployment') -param textEmbeddingAdaTpm int = 10 +@description('Embedding Model API version') +param embeddingModelVersion string + +@description('TPM quota for llm model deployment (x1000)') +param llmTpmQuota int = 10 + +@description('TPM quota for embedding model deployment (x1000)') +param embeddingTpmQuota int = 10 @description('Array of objects with fields principalId, roleDefinitionId') param roleAssignments array = [] @@ -32,39 +38,39 @@ resource aoai 'Microsoft.CognitiveServices/accounts@2024-10-01' = { } } -resource gpt4oDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = { +resource llmDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = { parent: aoai - name: llmModelDeploymentName + name: llmModelName sku: { name: 'GlobalStandard' - capacity: gpt4oTpm + capacity: llmTpmQuota } properties: { model: { format: 'OpenAI' - name: 'gpt-4o' - version: '2024-05-13' + name: llmModelName + version: llmModelVersion } - currentCapacity: gpt4oTpm + currentCapacity: llmTpmQuota } } -resource textEmbeddingAdaDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = { +resource embeddingDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = { parent: aoai - name: embeddingModelDeploymentName + name: embeddingModelName // NOTE: simultaneous model deployments are not supported at this time. As a workaround, use dependsOn to force the models to be deployed in a sequential manner. - dependsOn: [gpt4oDeployment] + dependsOn: [llmDeployment] sku: { name: 'Standard' - capacity: textEmbeddingAdaTpm + capacity: embeddingTpmQuota } properties: { model: { format: 'OpenAI' - name: 'text-embedding-ada-002' - version: '2' + name: embeddingModelName + version: embeddingModelVersion } - currentCapacity: textEmbeddingAdaTpm + currentCapacity: embeddingTpmQuota } } @@ -77,9 +83,9 @@ resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ ] output openAiEndpoint string = aoai.properties.endpoint -output llmModel string = gpt4oDeployment.properties.model.name -output llmModelDeploymentName string = gpt4oDeployment.name -output llmModelApiVersion string = gpt4oDeployment.apiVersion -output textEmbeddingModel string = textEmbeddingAdaDeployment.properties.model.name -output textEmbeddingModelDeploymentName string = textEmbeddingAdaDeployment.name -output textEmbeddingModelApiVersion string = textEmbeddingAdaDeployment.apiVersion +output llmModel string = llmDeployment.properties.model.name +output llmModelDeploymentName string = llmDeployment.name +output llmModelApiVersion string = llmDeployment.apiVersion +output textEmbeddingModel string = embeddingDeployment.properties.model.name +output textEmbeddingModelDeploymentName string = embeddingDeployment.name +output textEmbeddingModelApiVersion string = embeddingDeployment.apiVersion diff --git a/infra/deploy.sh b/infra/deploy.sh index 9882b92..991589b 100755 --- a/infra/deploy.sh +++ b/infra/deploy.sh @@ -16,7 +16,6 @@ GRAPHRAG_IMAGE="" PUBLISHER_EMAIL="" PUBLISHER_NAME="" RESOURCE_BASE_NAME="" -CONTAINER_REGISTRY_NAME="" requiredParams=( LOCATION @@ -316,7 +315,6 @@ deployAzureResources () { --parameters "apiPublisherName=$PUBLISHER_NAME" \ --parameters "apiPublisherEmail=$PUBLISHER_EMAIL" \ --parameters "enablePrivateEndpoints=$ENABLE_PRIVATE_ENDPOINTS" \ - --parameters "acrName=$CONTAINER_REGISTRY_NAME" \ --output json) # errors in deployment may not be caught by exitIfCommandFailed function so we also check the output for errors exitIfCommandFailed $? "Error deploying Azure resources..." diff --git a/infra/main.bicep b/infra/main.bicep index bfc0b76..c2fde43 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -56,6 +56,24 @@ param storageAccountName string = '' param cosmosDbName string = '' param aiSearchName string = '' +// AOAI parameters +@description('Name of the AOAI LLM model to use. Must match official model id. For more information: https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models') +@allowed(['gpt-4o', 'gpt-4o-mini']) +param llmModelName string = 'gpt-4o' +@description('Version of the AOAI LLM model to use.') +param llmModelVersion string = '2024-08-06' +@description('Quota of the AOAI LLM model to use.') +@minValue(1) +param llmModelQuota int = 10 + +@description('Name of the AOAI embedding model to use. Must match official model id. For more information: https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models') +@allowed(['text-embedding-ada-002', 'text-embedding-3-large']) +param embeddingModelName string = 'text-embedding-ada-002' +param embeddingModelVersion string = '2' +@description('Quota of the AOAI embedding model to use.') +@minValue(1) +param embeddingModelQuota int = 10 + var abbrs = loadJsonContent('abbreviations.json') var tags = { 'azd-env-name': resourceGroup } var workloadIdentityName = '${abbrs.managedIdentityUserAssignedIdentities}${resourceBaseNameFinal}' @@ -175,10 +193,12 @@ module aoai 'core/aoai/aoai.bicep' = { params: { openAiName: '${abbrs.cognitiveServicesAccounts}${resourceBaseNameFinal}' location: location - llmModelDeploymentName: 'gpt-4o-${uniqueString(resourceBaseNameFinal)}' - gpt4oTpm: 10 - embeddingModelDeploymentName: 'text-embedding-ada-002-${uniqueString(resourceBaseNameFinal)}' - textEmbeddingAdaTpm: 10 + llmModelName: llmModelName + llmModelVersion: llmModelVersion + llmTpmQuota: llmModelQuota + embeddingModelName: embeddingModelName + embeddingModelVersion: embeddingModelVersion + embeddingTpmQuota: embeddingModelQuota roleAssignments: [ { principalId: workloadIdentity.outputs.principalId @@ -419,6 +439,11 @@ output azure_ai_search_name string = aiSearch.outputs.name output azure_acr_login_server string = acr.outputs.loginServer output azure_acr_name string = acr.outputs.name +output azure_aks_name string = aks.outputs.name +output azure_aks_controlplanefqdn string = aks.outputs.controlPlaneFqdn +output azure_aks_managed_rg string = aks.outputs.managedResourceGroup +output azure_aks_service_account_name string = aksServiceAccountName + output azure_aoai_endpoint string = aoai.outputs.openAiEndpoint output azure_aoai_llm_model string = aoai.outputs.llmModel output azure_aoai_llm_model_deployment_name string = aoai.outputs.llmModelDeploymentName @@ -427,32 +452,27 @@ output azure_aoai_embedding_model string = aoai.outputs.textEmbeddingModel output azure_aoai_embedding_model_deployment_name string = aoai.outputs.textEmbeddingModelDeploymentName output azure_aoai_embedding_model_api_version string = aoai.outputs.textEmbeddingModelApiVersion -output azure_aks_name string = aks.outputs.name -output azure_aks_controlplanefqdn string = aks.outputs.controlPlaneFqdn -output azure_aks_managed_rg string = aks.outputs.managedResourceGroup -output azure_aks_service_account_name string = aksServiceAccountName +output azure_apim_name string = apim.outputs.name +output azure_apim_gateway_url string = apim.outputs.apimGatewayUrl -output azure_workload_identity_client_id string = workloadIdentity.outputs.clientId -output azure_workload_identity_principal_id string = workloadIdentity.outputs.principalId -output azure_workload_identity_name string = workloadIdentity.outputs.name +output azure_app_hostname string = appHostname +output azure_app_url string = appUrl -output azure_storage_account string = storage.outputs.name -output azure_storage_account_blob_url string = storage.outputs.primaryEndpoints.blob +output azure_app_insights_connection_string string = apim.outputs.appInsightsConnectionString output azure_cosmosdb_endpoint string = cosmosdb.outputs.endpoint output azure_cosmosdb_name string = cosmosdb.outputs.name output azure_cosmosdb_id string = cosmosdb.outputs.id -output azure_app_insights_connection_string string = apim.outputs.appInsightsConnectionString - -output azure_apim_name string = apim.outputs.name -output azure_apim_gateway_url string = apim.outputs.apimGatewayUrl - output azure_dns_zone_name string = privateDnsZone.outputs.name -output azure_app_hostname string = appHostname -output azure_app_url string = appUrl - output azure_private_dns_zones array = enablePrivateEndpoints ? union(privatelinkPrivateDns.outputs.privateDnsZones, [privateDnsZone.outputs.name]) : [] + +output azure_storage_account string = storage.outputs.name +output azure_storage_account_blob_url string = storage.outputs.primaryEndpoints.blob + +output azure_workload_identity_client_id string = workloadIdentity.outputs.clientId +output azure_workload_identity_principal_id string = workloadIdentity.outputs.principalId +output azure_workload_identity_name string = workloadIdentity.outputs.name diff --git a/infra/managed-app/createUiDefinition.json b/infra/managed-app/createUiDefinition.json new file mode 100644 index 0000000..50a0975 --- /dev/null +++ b/infra/managed-app/createUiDefinition.json @@ -0,0 +1,176 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#", + "handler": "Microsoft.Azure.CreateUIDef", + "version": "0.1.2-preview", + "parameters": { + "basics": [ + {} + ], + "steps": [ + { + "name": "aoaiSettings", + "label": "AOAI Settings", + "subLabel": { + "preValidation": "Configure the AOAI settings", + "postValidation": "Completed" + }, + "elements": [ + { + "name": "llmModel", + "type": "Microsoft.Common.DropDown", + "label": "LLM Model", + "defaultValue": "gpt-4o", + "toolTip": "LLM model to use.", + "constraints": { + "allowedValues": [ + { + "label": "gpt-4o", + "value": "gpt-4o" + }, + { + "label": "gpt-4o-mini", + "value": "gpt-4o-mini" + } + ], + "required": true + }, + "visible": true + }, + { + "name": "llmModelVersion", + "type": "Microsoft.Common.DropDown", + "label": "LLM Model Version", + "defaultValue": "2024-08-06", + "toolTip": "LLM model version to use.", + "constraints": { + "allowedValues": [ + { + "label": "2024-08-06", + "value": "2024-08-06" + }, + { + "label": "2024-07-18", + "value": "2024-07-18" + } + ], + "required": true + }, + "visible": true + }, + { + "name": "llmModelQuota", + "type": "Microsoft.Common.TextBox", + "label": "LLM Model Quota (x1000)", + "placeholder": "85", + "defaultValue": "", + "toolTip": "Model quota to use.", + "constraints": { + "required": true, + "regex": "^[1-9][0-9]*$", + "validationMessage": "Valid LLM model quota." + }, + "visible": true + }, + { + "name": "embeddingModel", + "type": "Microsoft.Common.DropDown", + "label": "Embedding Model", + "defaultValue": "text-embedding-ada-002", + "toolTip": "Embedding model to use", + "constraints": { + "allowedValues": [ + { + "label": "text-embedding-ada-002", + "value": "text-embedding-ada-002" + }, + { + "label": "text-embedding-3-large", + "value": "text-embedding-3-large" + } + ], + "required": true + }, + "visible": true + }, + { + "name": "embeddingModelQuota", + "type": "Microsoft.Common.TextBox", + "label": "Embedding Model Quota (x1000)", + "placeholder": "100", + "defaultValue": "", + "toolTip": "Model quota to use.", + "constraints": { + "required": true, + "regex": "^[1-9][0-9]*$", + "validationMessage": "Valid embedding model quota." + }, + "visible": true + }, + { + "name": "embeddingModelVersion", + "type": "Microsoft.Common.DropDown", + "label": "Embedding Model Version", + "defaultValue": "2", + "toolTip": "Use a valid embedding model version.", + "constraints": { + "allowedValues": [ + { + "label": "2", + "value": "2" + }, + { + "label": "1", + "value": "1" + } + ], + "required": true + }, + "visible": true + } + ] + }, + { + "name": "graphragSettings", + "label": "GraphRAG Settings", + "subLabel": { + "preValidation": "Configure the graphrag settings", + "postValidation": "Completed" + }, + "elements": [ + { + "name": "apimTier", + "type": "Microsoft.Common.DropDown", + "label": "APIM Tier", + "defaultValue": "StandardV2", + "toolTip": "APIM tier to use", + "constraints": { + "allowedValues": [ + { + "label": "Developer", + "value": "Developer" + }, + { + "label": "StandardV2", + "value": "StandardV2" + } + ], + "required": true + }, + "visible": true + } + ] + } + ], + "outputs": { + "resourceGroup": "[resourceGroup().name]", + "location": "[location()]", + "apimTier": "[steps('graphragSettings').apimTier]", + "llmModelName": "[steps('aoaiSettings').llmModel]", + "llmModelQuota": "[int(steps('aoaiSettings').llmModelQuota)]", + "embeddingModelName": "[steps('aoaiSettings').embeddingModel]", + "embeddingModelQuota": "[int(steps('aoaiSettings').embeddingModelQuota)]", + "llmModelVersion": "[steps('aoaiSettings').llmModelVersion]", + "embeddingModelVersion": "[steps('aoaiSettings').embeddingModelVersion]" + } + } +} \ No newline at end of file