working version of a managed app

This commit is contained in:
Josh Bradley 2025-01-08 11:38:03 -05:00
parent bc24bfad1c
commit ca0c4890ad
5 changed files with 279 additions and 55 deletions

View File

@ -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 <repo_root_directory>/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 <repo_root_directory>/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.

View File

@ -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

View File

@ -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..."

View File

@ -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

View File

@ -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]"
}
}
}