From 353221f31eeac110fb5cd3e59a32e092b548bbe9 Mon Sep 17 00:00:00 2001 From: Karthik Saligrama Date: Fri, 19 Jun 2026 08:31:56 -0700 Subject: [PATCH 1/3] Add ACR with managed VNet support to Bicep template 18 (#578) * Add ACR with managed VNet support to template 18 Add Azure Container Registry (Premium SKU) to the managed virtual network template with: - New container-registry.bicep module with private endpoint, DNS zone, and VNet link in the customer VNet - Managed network outbound PE rule for ACR so hosted agents can pull images privately - Contributor role on ACR for the AI account identity to approve managed PE connections (Network Connection Approver role does not cover ACR) - AcrPull role assignment for the project managed identity - Optional developer IP CIDR allowlist for push access - enableContainerRegistry parameter (default: true) to toggle ACR creation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Automatic fixes --------- Co-authored-by: Karthik Saligrama Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: foundry-samples automation --- .../azuredeploy.json | 424 +++++++++++++++++- .../18-managed-virtual-network/main.bicep | 68 ++- .../container-registry.bicep | 145 ++++++ .../managed-network.bicep | 20 + 4 files changed, 644 insertions(+), 13 deletions(-) create mode 100644 infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/container-registry.bicep diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/azuredeploy.json b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/azuredeploy.json index 3a23b63e7..e196493a9 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/azuredeploy.json +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/azuredeploy.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.44.1.10279", - "templateHash": "5155895823004186245" + "templateHash": "6450781060789677542" } }, "parameters": { @@ -34,6 +34,41 @@ "description": "Name for your AI Services resource." } }, + "modelName": { + "type": "string", + "defaultValue": "gpt-4.1", + "metadata": { + "description": "The name of the model you want to deploy" + } + }, + "modelFormat": { + "type": "string", + "defaultValue": "OpenAI", + "metadata": { + "description": "The provider of your model" + } + }, + "modelVersion": { + "type": "string", + "defaultValue": "2025-04-14", + "metadata": { + "description": "The version of your model" + } + }, + "modelSkuName": { + "type": "string", + "defaultValue": "GlobalStandard", + "metadata": { + "description": "The sku of your model deployment" + } + }, + "modelCapacity": { + "type": "int", + "defaultValue": 30, + "metadata": { + "description": "The tokens per minute (TPM) of your model deployment" + } + }, "firstProjectName": { "type": "string", "defaultValue": "project", @@ -118,6 +153,20 @@ "description": "The API Management Service full ARM Resource ID. This is an optional field for existing API Management services." } }, + "enableContainerRegistry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Enable Azure Container Registry with Private Endpoint. When true, creates an ACR (Premium SKU) with a PE in the private endpoints subnet and a managed network outbound rule." + } + }, + "developerIpCidr": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional developer IP CIDR to allowlist for ACR push access (e.g., 203.0.113.0/26 or 10.0.0.0/16). When empty, public access remains disabled." + } + }, "existingDnsZones": { "type": "object", "defaultValue": { @@ -127,7 +176,8 @@ "privatelink.search.windows.net": "", "privatelink.blob.core.windows.net": "", "privatelink.documents.azure.com": "", - "privatelink.azure-api.net": "" + "privatelink.azure-api.net": "", + "privatelink.azurecr.io": "" }, "metadata": { "description": "Object mapping DNS zone names to their resource group, or empty string to indicate creation" @@ -142,7 +192,8 @@ "privatelink.search.windows.net", "privatelink.blob.core.windows.net", "privatelink.documents.azure.com", - "privatelink.azure-api.net" + "privatelink.azure-api.net", + "privatelink.azurecr.io" ], "metadata": { "description": "Zone Names for Validation of existing Private Dns Zones" @@ -163,6 +214,7 @@ "cosmosDBName": "[toLower(format('{0}{1}cosmosdb', parameters('aiServices'), variables('uniqueSuffix')))]", "aiSearchName": "[toLower(format('{0}{1}search', parameters('aiServices'), variables('uniqueSuffix')))]", "azureStorageName": "[toLower(format('{0}{1}storage', parameters('aiServices'), variables('uniqueSuffix')))]", + "acrName": "[toLower(format('acr{0}', variables('uniqueSuffix')))]", "storagePassedIn": "[not(equals(parameters('azureStorageAccountResourceId'), ''))]", "searchPassedIn": "[not(equals(parameters('aiSearchResourceId'), ''))]", "cosmosPassedIn": "[not(equals(parameters('azureCosmosDBAccountResourceId'), ''))]", @@ -180,9 +232,43 @@ "vnetSubscriptionId": "[if(variables('existingVnetPassedIn'), variables('vnetParts')[2], subscription().subscriptionId)]", "vnetResourceGroupName": "[if(variables('existingVnetPassedIn'), variables('vnetParts')[4], resourceGroup().name)]", "existingVnetName": "[if(variables('existingVnetPassedIn'), last(variables('vnetParts')), parameters('vnetName'))]", - "trimVnetName": "[trim(variables('existingVnetName'))]" + "trimVnetName": "[trim(variables('existingVnetName'))]", + "contributorRoleId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "acrPullRoleId": "7f951dda-4ed3-4680-a7ca-43fe172d538d" }, "resources": [ + { + "condition": "[parameters('enableContainerRegistry')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.ContainerRegistry/registries', variables('acrName'))]", + "name": "[guid(variables('acrName'), variables('accountName'), variables('contributorRoleId'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('contributorRoleId'))]", + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix'))), '2025-04-01').outputs.accountPrincipalId.value]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('acr-{0}-deployment', variables('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix')))]" + ] + }, + { + "condition": "[parameters('enableContainerRegistry')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.ContainerRegistry/registries', variables('acrName'))]", + "name": "[guid(resourceId('Microsoft.ContainerRegistry/registries', variables('acrName')), variables('acrPullRoleId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('acrPullRoleId'))]", + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix'))), '2025-04-01').outputs.projectPrincipalId.value]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('acr-{0}-deployment', variables('uniqueSuffix')))]", + "[resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('projectName'), variables('uniqueSuffix')))]" + ] + }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2025-04-01", @@ -651,6 +737,21 @@ }, "location": { "value": "[parameters('location')]" + }, + "modelName": { + "value": "[parameters('modelName')]" + }, + "modelFormat": { + "value": "[parameters('modelFormat')]" + }, + "modelVersion": { + "value": "[parameters('modelVersion')]" + }, + "modelSkuName": { + "value": "[parameters('modelSkuName')]" + }, + "modelCapacity": { + "value": "[parameters('modelCapacity')]" } }, "template": { @@ -660,7 +761,7 @@ "_generator": { "name": "bicep", "version": "0.44.1.10279", - "templateHash": "1242706645341609299" + "templateHash": "2155442496151587028" } }, "parameters": { @@ -669,6 +770,21 @@ }, "location": { "type": "string" + }, + "modelName": { + "type": "string" + }, + "modelFormat": { + "type": "string" + }, + "modelVersion": { + "type": "string" + }, + "modelSkuName": { + "type": "string" + }, + "modelCapacity": { + "type": "int" } }, "resources": [ @@ -700,7 +816,7 @@ "useMicrosoftManagedNetwork": true } ], - "disableLocalAuth": false + "disableLocalAuth": true } }, { @@ -715,6 +831,25 @@ "dependsOn": [ "[resourceId('Microsoft.CognitiveServices/accounts', parameters('accountName'))]" ] + }, + { + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2025-04-01-preview", + "name": "[format('{0}/{1}', parameters('accountName'), parameters('modelName'))]", + "sku": { + "capacity": "[parameters('modelCapacity')]", + "name": "[parameters('modelSkuName')]" + }, + "properties": { + "model": { + "name": "[parameters('modelName')]", + "format": "[parameters('modelFormat')]", + "version": "[parameters('modelVersion')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('accountName'))]" + ] } ], "outputs": { @@ -1398,7 +1533,7 @@ "_generator": { "name": "bicep", "version": "0.44.1.10279", - "templateHash": "9631179584529129232" + "templateHash": "13762210290131983061" } }, "parameters": { @@ -1600,10 +1735,10 @@ }, "dependsOn": [ "[resourceId('Microsoft.Network/privateEndpoints', format('ampls-pe-{0}', parameters('suffix')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('amplsDnsZones')[2])]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('amplsDnsZones')[1])]", "[resourceId('Microsoft.Network/privateDnsZones', variables('amplsDnsZones')[0])]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('amplsDnsZones')[3])]" + "[resourceId('Microsoft.Network/privateDnsZones', variables('amplsDnsZones')[2])]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('amplsDnsZones')[3])]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('amplsDnsZones')[1])]" ] } ], @@ -1635,6 +1770,243 @@ "[resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix')))]" ] }, + { + "condition": "[parameters('enableContainerRegistry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('acr-{0}-deployment', variables('uniqueSuffix'))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "acrName": { + "value": "[variables('acrName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "peSubnetId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.peSubnetId.value]" + }, + "vnetId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix'))), '2025-04-01').outputs.virtualNetworkId.value]" + }, + "suffix": { + "value": "[variables('uniqueSuffix')]" + }, + "existingDnsZoneResourceGroup": { + "value": "[parameters('existingDnsZones')['privatelink.azurecr.io']]" + }, + "developerIpCidr": { + "value": "[parameters('developerIpCidr')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.44.1.10279", + "templateHash": "4699943037135843809" + } + }, + "parameters": { + "acrName": { + "type": "string", + "metadata": { + "description": "Name of the Azure Container Registry" + } + }, + "location": { + "type": "string", + "metadata": { + "description": "Azure region for the ACR" + } + }, + "peSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Private Endpoint subnet" + } + }, + "vnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Virtual Network" + } + }, + "suffix": { + "type": "string", + "metadata": { + "description": "Suffix for unique resource names" + } + }, + "existingDnsZoneResourceGroup": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource group name for existing ACR DNS zone. Empty string means create a new zone." + } + }, + "dnsZonesSubscriptionId": { + "type": "string", + "defaultValue": "[subscription().subscriptionId]", + "metadata": { + "description": "Subscription ID where existing private DNS zones are located." + } + }, + "developerIpCidr": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional developer IP CIDR to allowlist for ACR push access (e.g., 203.0.113.0/26 or 10.0.0.0/16). When empty, public access remains disabled." + } + }, + "projectPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Principal ID of the project managed identity to grant AcrPull role. When empty, no role assignment is created." + } + } + }, + "variables": { + "acrDnsZoneName": "privatelink.azurecr.io", + "acrDnsZoneId": "[if(empty(parameters('existingDnsZoneResourceGroup')), resourceId('Microsoft.Network/privateDnsZones', variables('acrDnsZoneName')), extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', parameters('dnsZonesSubscriptionId'), parameters('existingDnsZoneResourceGroup')), 'Microsoft.Network/privateDnsZones', variables('acrDnsZoneName')))]", + "acrPullRoleId": "7f951dda-4ed3-4680-a7ca-43fe172d538d" + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2023-07-01", + "name": "[parameters('acrName')]", + "location": "[parameters('location')]", + "sku": { + "name": "Premium" + }, + "properties": { + "adminUserEnabled": false, + "publicNetworkAccess": "[if(empty(parameters('developerIpCidr')), 'Disabled', 'Enabled')]", + "networkRuleBypassOptions": "AzureServices", + "networkRuleSet": "[if(empty(parameters('developerIpCidr')), null(), createObject('defaultAction', 'Deny', 'ipRules', createArray(createObject('action', 'Allow', 'value', parameters('developerIpCidr')))))]" + } + }, + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2024-05-01", + "name": "[format('{0}-private-endpoint', parameters('acrName'))]", + "location": "[parameters('location')]", + "properties": { + "subnet": { + "id": "[parameters('peSubnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "[format('{0}-private-link-service-connection', parameters('acrName'))]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName'))]", + "groupIds": [ + "registry" + ] + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName'))]" + ] + }, + { + "condition": "[empty(parameters('existingDnsZoneResourceGroup'))]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[variables('acrDnsZoneName')]", + "location": "global" + }, + { + "condition": "[empty(parameters('existingDnsZoneResourceGroup'))]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', variables('acrDnsZoneName'), format('acr-{0}-link', parameters('suffix')))]", + "location": "global", + "properties": { + "virtualNetwork": { + "id": "[parameters('vnetId')]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', variables('acrDnsZoneName'))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2024-05-01", + "name": "[format('{0}/{1}', format('{0}-private-endpoint', parameters('acrName')), format('{0}-dns-group', parameters('acrName')))]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('{0}-dns-config', parameters('acrName'))]", + "properties": { + "privateDnsZoneId": "[variables('acrDnsZoneId')]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones/virtualNetworkLinks', variables('acrDnsZoneName'), format('acr-{0}-link', parameters('suffix')))]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('acrDnsZoneName'))]", + "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-private-endpoint', parameters('acrName')))]" + ] + }, + { + "condition": "[not(empty(parameters('projectPrincipalId')))]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName'))]", + "name": "[guid(resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName')), parameters('projectPrincipalId'), variables('acrPullRoleId'))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('acrPullRoleId'))]", + "principalId": "[parameters('projectPrincipalId')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName'))]" + ] + } + ], + "outputs": { + "acrId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Azure Container Registry" + }, + "value": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName'))]" + }, + "acrName": { + "type": "string", + "metadata": { + "description": "Name of the Azure Container Registry" + }, + "value": "[parameters('acrName')]" + }, + "acrLoginServer": { + "type": "string", + "metadata": { + "description": "Login server URL of the Azure Container Registry" + }, + "value": "[reference(resourceId('Microsoft.ContainerRegistry/registries', parameters('acrName')), '2023-07-01').loginServer]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('vnet-{0}-{1}-deployment', variables('trimVnetName'), variables('uniqueSuffix')))]" + ] + }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2025-04-01", @@ -1662,7 +2034,8 @@ }, "amplsResourceId": { "value": "[reference(resourceId('Microsoft.Resources/deployments', format('ampls-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.amplsResourceId.value]" - } + }, + "acrResourceId": "[if(parameters('enableContainerRegistry'), createObject('value', reference(resourceId('Microsoft.Resources/deployments', format('acr-{0}-deployment', variables('uniqueSuffix'))), '2025-04-01').outputs.acrId.value), createObject('value', ''))]" }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -1671,7 +2044,7 @@ "_generator": { "name": "bicep", "version": "0.44.1.10279", - "templateHash": "567868772931859562" + "templateHash": "8181508792646753714" } }, "parameters": { @@ -1715,6 +2088,13 @@ "metadata": { "description": "Resource ID of the Azure Monitor Private Link Scope for telemetry" } + }, + "acrResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Resource ID of the Azure Container Registry for outbound PE rule. When empty, no ACR outbound rule is created." + } } }, "resources": [ @@ -1813,6 +2193,24 @@ "[resourceId('Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules', parameters('accountName'), 'default', 'aisearch-rule')]", "[resourceId('Microsoft.CognitiveServices/accounts/managedNetworks', parameters('accountName'), 'default')]" ] + }, + { + "condition": "[not(empty(parameters('acrResourceId')))]", + "type": "Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules", + "apiVersion": "2025-10-01-preview", + "name": "[format('{0}/{1}/{2}', parameters('accountName'), 'default', 'acr-registry-rule')]", + "properties": { + "type": "PrivateEndpoint", + "destination": { + "serviceResourceId": "[parameters('acrResourceId')]", + "subresourceTarget": "registry" + }, + "category": "UserDefined" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules', parameters('accountName'), 'default', 'ampls-monitor-rule')]", + "[resourceId('Microsoft.CognitiveServices/accounts/managedNetworks', parameters('accountName'), 'default')]" + ] } ], "outputs": { @@ -1824,6 +2222,8 @@ } }, "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('acr-{0}-deployment', variables('uniqueSuffix')))]", + "[extensionResourceId(resourceId('Microsoft.ContainerRegistry/registries', variables('acrName')), 'Microsoft.Authorization/roleAssignments', guid(variables('acrName'), variables('accountName'), variables('contributorRoleId')))]", "[resourceId('Microsoft.Resources/deployments', format('ai-{0}-{1}-deployment', variables('accountName'), variables('uniqueSuffix')))]", "[resourceId('Microsoft.Resources/deployments', format('dependencies-{0}-deployment', variables('uniqueSuffix')))]", "[resourceId('Microsoft.Resources/deployments', format('ampls-{0}-deployment', variables('uniqueSuffix')))]", diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicep index af45092c4..716892fef 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicep +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/main.bicep @@ -67,6 +67,12 @@ param azureCosmosDBAccountResourceId string = '' @description('The API Management Service full ARM Resource ID. This is an optional field for existing API Management services.') param apiManagementResourceId string = '' +@description('Enable Azure Container Registry with Private Endpoint. When true, creates an ACR (Premium SKU) with a PE in the private endpoints subnet and a managed network outbound rule.') +param enableContainerRegistry bool = true + +@description('Optional developer IP CIDR to allowlist for ACR push access (e.g., 203.0.113.0/26 or 10.0.0.0/16). When empty, public access remains disabled.') +param developerIpCidr string = '' + //New Param for resource group of Private DNS zones //@description('Optional: Resource group containing existing private DNS zones. If specified, DNS zones will not be created.') //param existingDnsZonesResourceGroup string = '' @@ -79,7 +85,8 @@ param existingDnsZones object = { 'privatelink.search.windows.net': '' 'privatelink.blob.core.windows.net': '' 'privatelink.documents.azure.com': '' - 'privatelink.azure-api.net': '' + 'privatelink.azure-api.net': '' + 'privatelink.azurecr.io': '' } @description('Zone Names for Validation of existing Private Dns Zones') @@ -91,6 +98,7 @@ param dnsZoneNames array = [ 'privatelink.blob.core.windows.net' 'privatelink.documents.azure.com' 'privatelink.azure-api.net' + 'privatelink.azurecr.io' ] @@ -98,6 +106,7 @@ var projectName = toLower('${firstProjectName}${uniqueSuffix}') var cosmosDBName = toLower('${aiServices}${uniqueSuffix}cosmosdb') var aiSearchName = toLower('${aiServices}${uniqueSuffix}search') var azureStorageName = toLower('${aiServices}${uniqueSuffix}storage') +var acrName = toLower('acr${uniqueSuffix}') // Check if existing resources have been passed in var storagePassedIn = azureStorageAccountResourceId != '' @@ -244,6 +253,28 @@ module networkApproverRoleSearch 'modules-network-secured/network-connection-app } } +// Contributor role on the ACR for managed PE connection approval +// The "Network Connection Approver" role does not include Microsoft.ContainerRegistry actions, +// so the AI account identity needs Contributor on the ACR to approve the managed VNet outbound PE connection +var contributorRoleId = 'b24988ac-6180-42a0-ab88-20f7382dd24c' + +resource acrForRoleAssignment 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = if (enableContainerRegistry) { + name: acrName +} + +resource acrContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableContainerRegistry) { + name: guid(acrName, accountName, contributorRoleId) + scope: acrForRoleAssignment + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', contributorRoleId) + principalId: aiAccount.outputs.accountPrincipalId + principalType: 'ServicePrincipal' + } + dependsOn: [ + acr + ] +} + // Azure Monitor Private Link Scope (AMPLS) for Application Insights telemetry // This enables hosted agents to export traces/telemetry to App Insights via private network module ampls 'modules-network-secured/azure-monitor-private-link.bicep' = { @@ -258,6 +289,22 @@ module ampls 'modules-network-secured/azure-monitor-private-link.bicep' = { } } +// Optional: Azure Container Registry with Private Endpoint +// Creates an ACR accessible only via private endpoint (not publicly accessible) +// Also adds a managed network outbound rule so hosted agents can pull images +module acr 'modules-network-secured/container-registry.bicep' = if (enableContainerRegistry) { + name: 'acr-${uniqueSuffix}-deployment' + params: { + acrName: acrName + location: location + peSubnetId: vnet.outputs.peSubnetId + vnetId: vnet.outputs.virtualNetworkId + suffix: uniqueSuffix + existingDnsZoneResourceGroup: existingDnsZones['privatelink.azurecr.io'] + developerIpCidr: developerIpCidr + } +} + // Configure Managed Network for AI Services Account // This module sets up the managed virtual network and outbound PE rules to allow // secure communication from hosted agents to customer resources (Storage, AI Search, Cosmos DB) @@ -270,12 +317,14 @@ module managedNetwork 'modules-network-secured/managed-network.bicep' = { cosmosDBResourceId: cosmosDB.id aiSearchResourceId: aiSearch.id amplsResourceId: ampls.outputs.amplsResourceId + acrResourceId: enableContainerRegistry ? acr.outputs.acrId : '' } dependsOn: [ aiDependencies // Ensure dependent resources (Storage, CosmosDB, Search) are fully created networkApproverRoleStorage networkApproverRoleCosmos networkApproverRoleSearch + acrContributorRoleAssignment ampls // Ensure AMPLS is created before adding the outbound rule ] } @@ -460,3 +509,20 @@ dependsOn: [ storageContainersRoleAssignment ] } + +// ---- AcrPull Role Assignment ---- +// Grants the project managed identity pull access to the ACR (assigned after project is created) +var acrPullRoleId = '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull built-in role + +resource acrPullRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (enableContainerRegistry) { + name: guid(acrForRoleAssignment.id, acrPullRoleId, resourceGroup().id) + scope: acrForRoleAssignment + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', acrPullRoleId) + principalId: aiProject.outputs.projectPrincipalId + principalType: 'ServicePrincipal' + } + dependsOn: [ + acr + ] +} diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/container-registry.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/container-registry.bicep new file mode 100644 index 000000000..d141d6172 --- /dev/null +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/container-registry.bicep @@ -0,0 +1,145 @@ +/* +Azure Container Registry with Private Endpoint Module +------------------------------------------------------ +This module creates an Azure Container Registry (Premium SKU) with: +1. Private Endpoint in the specified PE subnet +2. Private DNS Zone (privatelink.azurecr.io) — created or referenced from existing +3. VNet link for the DNS zone +4. DNS Zone Group for the Private Endpoint + +Prerequisites: +- Premium SKU is required for Private Endpoint support +- The PE subnet must already exist +*/ + +@description('Name of the Azure Container Registry') +param acrName string + +@description('Azure region for the ACR') +param location string + +@description('Resource ID of the Private Endpoint subnet') +param peSubnetId string + +@description('Resource ID of the Virtual Network') +param vnetId string + +@description('Suffix for unique resource names') +param suffix string + +@description('Resource group name for existing ACR DNS zone. Empty string means create a new zone.') +param existingDnsZoneResourceGroup string = '' + +@description('Subscription ID where existing private DNS zones are located.') +param dnsZonesSubscriptionId string = subscription().subscriptionId + +@description('Optional developer IP CIDR to allowlist for ACR push access (e.g., 203.0.113.0/26 or 10.0.0.0/16). When empty, public access remains disabled.') +param developerIpCidr string = '' + +@description('Principal ID of the project managed identity to grant AcrPull role. When empty, no role assignment is created.') +param projectPrincipalId string = '' + +// ---- ACR Resource ---- +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-07-01' = { + name: acrName + location: location + sku: { + name: 'Premium' + } + properties: { + adminUserEnabled: false + publicNetworkAccess: empty(developerIpCidr) ? 'Disabled' : 'Enabled' + networkRuleBypassOptions: 'AzureServices' + networkRuleSet: empty(developerIpCidr) ? null : { + defaultAction: 'Deny' + ipRules: [ + { + action: 'Allow' + value: developerIpCidr + } + ] + } + } +} + +// ---- Private Endpoint ---- +resource acrPrivateEndpoint 'Microsoft.Network/privateEndpoints@2024-05-01' = { + name: '${acrName}-private-endpoint' + location: location + properties: { + subnet: { id: peSubnetId } + privateLinkServiceConnections: [ + { + name: '${acrName}-private-link-service-connection' + properties: { + privateLinkServiceId: containerRegistry.id + groupIds: [ 'registry' ] + } + } + ] + } +} + +// ---- Private DNS Zone ---- +var acrDnsZoneName = 'privatelink.azurecr.io' + +resource acrPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = if (empty(existingDnsZoneResourceGroup)) { + name: acrDnsZoneName + location: 'global' +} + +resource existingAcrPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' existing = if (!empty(existingDnsZoneResourceGroup)) { + name: acrDnsZoneName + scope: resourceGroup(dnsZonesSubscriptionId, existingDnsZoneResourceGroup) +} + +var acrDnsZoneId = empty(existingDnsZoneResourceGroup) ? acrPrivateDnsZone.id : existingAcrPrivateDnsZone.id + +// ---- VNet Link ---- +resource acrDnsVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2024-06-01' = if (empty(existingDnsZoneResourceGroup)) { + parent: acrPrivateDnsZone + location: 'global' + name: 'acr-${suffix}-link' + properties: { + virtualNetwork: { id: vnetId } + registrationEnabled: false + } +} + +// ---- DNS Zone Group ---- +resource acrDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2024-05-01' = { + parent: acrPrivateEndpoint + name: '${acrName}-dns-group' + properties: { + privateDnsZoneConfigs: [ + { name: '${acrName}-dns-config', properties: { privateDnsZoneId: acrDnsZoneId } } + ] + } + dependsOn: [ + empty(existingDnsZoneResourceGroup) ? acrDnsVnetLink : null + ] +} + +// ---- AcrPull Role Assignment ---- +// Grants the project managed identity pull access to the ACR +var acrPullRoleId = '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull built-in role + +resource acrPullRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(projectPrincipalId)) { + name: guid(containerRegistry.id, projectPrincipalId, acrPullRoleId) + scope: containerRegistry + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', acrPullRoleId) + principalId: projectPrincipalId + principalType: 'ServicePrincipal' + } +} + +// ---- Outputs ---- +@description('Resource ID of the Azure Container Registry') +output acrId string = containerRegistry.id + +@description('Name of the Azure Container Registry') +output acrName string = containerRegistry.name + +@description('Login server URL of the Azure Container Registry') +output acrLoginServer string = containerRegistry.properties.loginServer diff --git a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep index 7b0331385..5708c2a12 100644 --- a/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep +++ b/infrastructure/infrastructure-setup-bicep/18-managed-virtual-network/modules-network-secured/managed-network.bicep @@ -20,6 +20,9 @@ param aiSearchResourceId string @description('Resource ID of the Azure Monitor Private Link Scope for telemetry') param amplsResourceId string +@description('Resource ID of the Azure Container Registry for outbound PE rule. When empty, no ACR outbound rule is created.') +param acrResourceId string = '' + // Reference the existing AI Services account in the same resource group resource aiAccount 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' existing = { name: accountName @@ -120,4 +123,21 @@ resource amplsOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks dependsOn: [aiSearchOutboundRule] } +// Outbound PE rule for Azure Container Registry +// This allows the hosted agent to pull container images from the private ACR +#disable-next-line BCP081 +resource acrOutboundRule 'Microsoft.CognitiveServices/accounts/managedNetworks/outboundRules@2025-10-01-preview' = if (!empty(acrResourceId)) { + parent: managedNetwork + name: 'acr-registry-rule' + properties: { + type: 'PrivateEndpoint' + destination: { + serviceResourceId: acrResourceId + subresourceTarget: 'registry' + } + category: 'UserDefined' + } + dependsOn: [amplsOutboundRule] +} + output managedNetworkSettingsName string = managedNetwork.name From 4b389585c17527e2c95a3e76b65caef91bd092dc Mon Sep 17 00:00:00 2001 From: Ben Thomas <25218250+alliscode@users.noreply.github.com> Date: Fri, 19 Jun 2026 12:41:28 -0700 Subject: [PATCH 2/3] Slim the agent-framework dependencies (#487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Switch hosted-agent samples to agent-framework-foundry (microsoft/agent-framework#6271) Replace the agent-framework meta-package (which pulls in the full agent-framework-core[all] set, including Linux-x86_64-only packages like agent-framework-hyperlight) with the narrow agent-framework-foundry package across all Python hosted-agent samples. Additional runtime deps that no longer come in transitively: - mcp>=1.24.0 for every Responses sample, because agent_framework_foundry_hosting._responses imports `from mcp import McpError` unconditionally - agent-framework-declarative for 09-declarative-customer-support (WorkflowFactory) - mcp>=1.24.0 already noted for MCPStreamableHTTPTool in 04, 06, and 07-teams-activity Verified with fresh-venv installs and import smoke tests covering each unique dependency shape: 01-basic, 05-workflows, 06-files, 07-skills, 09-declarative, invocations/01-basic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Simplify requirements.txt comments in hosted-agent samples Replace the verbose per-file comments about pip resolution failures and agent-framework-core[all] extras with a single concise line explaining the intent: keep dependencies light by referencing only the packages needed. Apply the same style consistently across all 15 sample requirements files (including the pre-existing 11/12/13 comments). Also adds mcp>=1.24.0 to 12-foundry-skills, which was missing it — agent_framework_foundry_hosting._responses imports `from mcp import McpError` unconditionally, so every Responses sample needs it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add upper bound to mcp dependency in hosted-agent samples Pin mcp to `<2,>=1.24.0` (matching the upstream agent-framework-core[all] extra constraint) to prevent a future mcp v2 release with breaking changes from silently breaking the samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove duplicate mcp dependency in 03-mcp/requirements.txt The original file already declared `mcp>=1.24.0,<2`; the agent-framework-foundry switch inadvertently left it in alongside the newly added `mcp<2,>=1.24.0` entry. Drop the duplicate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skipping code-deploy based ci tests. --------- Co-authored-by: alliscode <25218250+alliscode@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../invocations/01-basic/requirements.txt | 2 +- .../responses/01-basic/requirements.txt | 6 ++++-- .../responses/02-tools/requirements.txt | 6 ++++-- .../responses/03-mcp/requirements.txt | 5 +++-- .../responses/04-foundry-toolbox/requirements.txt | 9 +++------ .../responses/05-workflows/requirements.txt | 6 ++++-- .../responses/06-files/requirements.txt | 6 ++++-- .../responses/07-skills/requirements.txt | 4 +++- .../responses/07-teams-activity/requirements.txt | 9 +++------ .../responses/08-observability/requirements.txt | 6 ++++-- .../09-declarative-customer-support/README.md | 2 ++ .../09-declarative-customer-support/requirements.txt | 5 ++++- .../responses/10-downstream-azure/requirements.txt | 4 +++- .../responses/11-azure-search-rag/requirements.txt | 12 ++---------- .../responses/12-foundry-skills/requirements.txt | 9 ++------- .../responses/13-foundry-memory/requirements.txt | 12 ++---------- .../15-optimization-travel-approver/requirements.txt | 4 +++- 17 files changed, 51 insertions(+), 56 deletions(-) diff --git a/samples/python/hosted-agents/agent-framework/invocations/01-basic/requirements.txt b/samples/python/hosted-agents/agent-framework/invocations/01-basic/requirements.txt index f7dc62f3e..0b85d57fa 100644 --- a/samples/python/hosted-agents/agent-framework/invocations/01-basic/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/invocations/01-basic/requirements.txt @@ -1,2 +1,2 @@ -agent-framework +agent-framework-foundry agent-framework-foundry-hosting \ No newline at end of file diff --git a/samples/python/hosted-agents/agent-framework/responses/01-basic/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/01-basic/requirements.txt index a50487247..92b926abf 100644 --- a/samples/python/hosted-agents/agent-framework/responses/01-basic/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/01-basic/requirements.txt @@ -1,2 +1,4 @@ -agent-framework>=1.2.2 -agent-framework-foundry-hosting \ No newline at end of file +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry +agent-framework-foundry-hosting +mcp<2,>=1.24.0 \ No newline at end of file diff --git a/samples/python/hosted-agents/agent-framework/responses/02-tools/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/02-tools/requirements.txt index a50487247..92b926abf 100644 --- a/samples/python/hosted-agents/agent-framework/responses/02-tools/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/02-tools/requirements.txt @@ -1,2 +1,4 @@ -agent-framework>=1.2.2 -agent-framework-foundry-hosting \ No newline at end of file +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry +agent-framework-foundry-hosting +mcp<2,>=1.24.0 \ No newline at end of file diff --git a/samples/python/hosted-agents/agent-framework/responses/03-mcp/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/03-mcp/requirements.txt index f67947c86..92b926abf 100644 --- a/samples/python/hosted-agents/agent-framework/responses/03-mcp/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/03-mcp/requirements.txt @@ -1,3 +1,4 @@ -agent-framework>=1.2.2 +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry agent-framework-foundry-hosting -mcp>=1.24.0,<2 \ No newline at end of file +mcp<2,>=1.24.0 \ No newline at end of file diff --git a/samples/python/hosted-agents/agent-framework/responses/04-foundry-toolbox/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/04-foundry-toolbox/requirements.txt index 1bcb3b467..4d88caef9 100644 --- a/samples/python/hosted-agents/agent-framework/responses/04-foundry-toolbox/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/04-foundry-toolbox/requirements.txt @@ -1,7 +1,4 @@ -# `agent-framework[foundry]` is required because main.py imports -# `from agent_framework.foundry import FoundryChatClient`. In 1.3+ the -# foundry submodule is an optional extra; installing the bare package -# leaves agent_framework.foundry unimportable and the container crashes -# at startup so /readiness never returns 200 -> 424 session_not_ready. -agent-framework[foundry]>=1.2.2 +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry agent-framework-foundry-hosting +mcp<2,>=1.24.0 diff --git a/samples/python/hosted-agents/agent-framework/responses/05-workflows/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/05-workflows/requirements.txt index a50487247..92b926abf 100644 --- a/samples/python/hosted-agents/agent-framework/responses/05-workflows/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/05-workflows/requirements.txt @@ -1,2 +1,4 @@ -agent-framework>=1.2.2 -agent-framework-foundry-hosting \ No newline at end of file +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry +agent-framework-foundry-hosting +mcp<2,>=1.24.0 \ No newline at end of file diff --git a/samples/python/hosted-agents/agent-framework/responses/06-files/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/06-files/requirements.txt index f7dc62f3e..92b926abf 100644 --- a/samples/python/hosted-agents/agent-framework/responses/06-files/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/06-files/requirements.txt @@ -1,2 +1,4 @@ -agent-framework -agent-framework-foundry-hosting \ No newline at end of file +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry +agent-framework-foundry-hosting +mcp<2,>=1.24.0 \ No newline at end of file diff --git a/samples/python/hosted-agents/agent-framework/responses/07-skills/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/07-skills/requirements.txt index 90dcfcd8b..4d88caef9 100644 --- a/samples/python/hosted-agents/agent-framework/responses/07-skills/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/07-skills/requirements.txt @@ -1,2 +1,4 @@ -agent-framework>=1.2.2 +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry agent-framework-foundry-hosting +mcp<2,>=1.24.0 diff --git a/samples/python/hosted-agents/agent-framework/responses/07-teams-activity/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/07-teams-activity/requirements.txt index 1bcb3b467..4d88caef9 100644 --- a/samples/python/hosted-agents/agent-framework/responses/07-teams-activity/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/07-teams-activity/requirements.txt @@ -1,7 +1,4 @@ -# `agent-framework[foundry]` is required because main.py imports -# `from agent_framework.foundry import FoundryChatClient`. In 1.3+ the -# foundry submodule is an optional extra; installing the bare package -# leaves agent_framework.foundry unimportable and the container crashes -# at startup so /readiness never returns 200 -> 424 session_not_ready. -agent-framework[foundry]>=1.2.2 +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry agent-framework-foundry-hosting +mcp<2,>=1.24.0 diff --git a/samples/python/hosted-agents/agent-framework/responses/08-observability/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/08-observability/requirements.txt index f7dc62f3e..92b926abf 100644 --- a/samples/python/hosted-agents/agent-framework/responses/08-observability/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/08-observability/requirements.txt @@ -1,2 +1,4 @@ -agent-framework -agent-framework-foundry-hosting \ No newline at end of file +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry +agent-framework-foundry-hosting +mcp<2,>=1.24.0 \ No newline at end of file diff --git a/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/README.md b/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/README.md index e86830832..52c855eff 100644 --- a/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/README.md +++ b/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/README.md @@ -4,6 +4,8 @@ A realistic **multi-turn** [Agent Framework](https://github.com/microsoft/agent- > Read more about declarative workflows in the [Agent Framework documentation](https://learn.microsoft.com/en-us/agent-framework/workflows/declarative/?pivots=programming-language-python) and about workflow-as-an-agent in the [Workflow as an Agent documentation](https://learn.microsoft.com/en-us/agent-framework/workflows/as-agents?pivots=programming-language-python). +> This sample currently requires using a Docker based deployment. + ## How It Works ### The Workflow diff --git a/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/requirements.txt index 1ed4f3c7d..b711255bc 100644 --- a/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/09-declarative-customer-support/requirements.txt @@ -1,2 +1,5 @@ -agent-framework +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry +agent-framework-declarative agent-framework-foundry-hosting +mcp<2,>=1.24.0 diff --git a/samples/python/hosted-agents/agent-framework/responses/10-downstream-azure/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/10-downstream-azure/requirements.txt index ea62383e5..01d403920 100644 --- a/samples/python/hosted-agents/agent-framework/responses/10-downstream-azure/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/10-downstream-azure/requirements.txt @@ -1,4 +1,6 @@ -agent-framework>=1.2.2 +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry agent-framework-foundry-hosting +mcp<2,>=1.24.0 azure-storage-blob azure-servicebus diff --git a/samples/python/hosted-agents/agent-framework/responses/11-azure-search-rag/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/11-azure-search-rag/requirements.txt index 794b9a67b..c32a049ee 100644 --- a/samples/python/hosted-agents/agent-framework/responses/11-azure-search-rag/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/11-azure-search-rag/requirements.txt @@ -1,13 +1,5 @@ -# Install the narrow Foundry / Azure AI Search subpackages directly required -# by this sample (FoundryChatClient, AzureAISearchContextProvider, -# ResponsesHostServer) instead of the bare ``agent-framework`` meta-package, -# which pulls in the full ``agent-framework-core[all]`` set — including -# Linux-x86_64-only packages such as ``agent-framework-hyperlight`` — and is -# prone to pip resolution failures during the ``python:3.12-slim`` container -# build. +# Use the narrow Foundry / Azure AI Search subpackages to keep dependencies light. agent-framework-foundry agent-framework-azure-ai-search agent-framework-foundry-hosting -# mcp is a transitive dependency of agent-framework-core[all] that is needed -# at runtime but not declared as a required dependency of agent-framework-core. -mcp>=1.24.0 +mcp<2,>=1.24.0 diff --git a/samples/python/hosted-agents/agent-framework/responses/12-foundry-skills/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/12-foundry-skills/requirements.txt index bc4f868e4..f11d86eae 100644 --- a/samples/python/hosted-agents/agent-framework/responses/12-foundry-skills/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/12-foundry-skills/requirements.txt @@ -1,11 +1,6 @@ -# Install the narrow Foundry subpackages directly required by this sample -# (Agent, SkillsProvider, FoundryChatClient, ResponsesHostServer, and -# azure.ai.projects for the skills-download bootstrap) instead of the bare -# ``agent-framework`` meta-package, which pulls in the full -# ``agent-framework-core[all]`` set — including Linux-x86_64-only packages -# such as ``agent-framework-hyperlight`` — and is prone to pip resolution -# failures during the ``python:3.12-slim`` container build. +# Use the narrow Foundry subpackages to keep dependencies light. agent-framework-foundry agent-framework-foundry-hosting +mcp<2,>=1.24.0 azure-ai-projects mcp>=1.24.0 diff --git a/samples/python/hosted-agents/agent-framework/responses/13-foundry-memory/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/13-foundry-memory/requirements.txt index 6c522059a..265575ddd 100644 --- a/samples/python/hosted-agents/agent-framework/responses/13-foundry-memory/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/13-foundry-memory/requirements.txt @@ -1,13 +1,5 @@ -# Install the narrow Foundry subpackages directly required by this sample -# (Agent, FoundryChatClient, FoundryMemoryProvider, ResponsesHostServer, and -# azure.ai.projects for the memory-store provisioning script) instead of the -# bare ``agent-framework`` meta-package, which pulls in the full -# ``agent-framework-core[all]`` set — including Linux-x86_64-only packages -# such as ``agent-framework-hyperlight`` — and is prone to pip resolution -# failures during the ``python:3.12-slim`` container build. +# Use the narrow Foundry subpackages to keep dependencies light. agent-framework-foundry agent-framework-foundry-hosting -# mcp is a transitive dependency of agent-framework-core[all] that is needed -# at runtime but not declared as a required dependency of agent-framework-core. -mcp>=1.24.0 +mcp<2,>=1.24.0 azure-ai-projects diff --git a/samples/python/hosted-agents/agent-framework/responses/15-optimization-travel-approver/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/15-optimization-travel-approver/requirements.txt index 275064846..f43ba2962 100644 --- a/samples/python/hosted-agents/agent-framework/responses/15-optimization-travel-approver/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/15-optimization-travel-approver/requirements.txt @@ -1,4 +1,6 @@ -agent-framework>=1.2.2 +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry agent-framework-foundry-hosting +mcp<2,>=1.24.0 azure-identity>=1.15.0 azure-ai-agentserver-optimization>=1.0.0b1 From 34abedbe1398b6cd2b8ac8c7701c7c3cc0e13896 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Jun 2026 02:50:11 +0530 Subject: [PATCH 3/3] Align remaining hosted-agent dependency files with slim package pattern (#580) --- .../a2a/01-delegation/caller/requirements.txt | 9 +++------ .../a2a/01-delegation/executor/requirements.txt | 3 ++- .../responses/12-foundry-skills/requirements.txt | 1 - .../14-browser-automation-agent/requirements.txt | 2 +- .../16-content-safety-guardrail/requirements.txt | 6 ++++-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/samples/python/hosted-agents/agent-framework/a2a/01-delegation/caller/requirements.txt b/samples/python/hosted-agents/agent-framework/a2a/01-delegation/caller/requirements.txt index bc9d2f5f9..d029dbf93 100644 --- a/samples/python/hosted-agents/agent-framework/a2a/01-delegation/caller/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/a2a/01-delegation/caller/requirements.txt @@ -1,8 +1,5 @@ -# `agent-framework[foundry]` is required because main.py imports -# `from agent_framework.foundry import FoundryChatClient`. In 1.3+ the -# foundry submodule is an optional extra; installing the bare package -# leaves agent_framework.foundry unimportable and the container crashes -# at startup so /readiness never returns 200 -> 424 session_not_ready. -agent-framework[foundry]>=1.2.2 +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry agent-framework-foundry-hosting +mcp<2,>=1.24.0 httpx diff --git a/samples/python/hosted-agents/agent-framework/a2a/01-delegation/executor/requirements.txt b/samples/python/hosted-agents/agent-framework/a2a/01-delegation/executor/requirements.txt index 90dcfcd8b..926e5079f 100644 --- a/samples/python/hosted-agents/agent-framework/a2a/01-delegation/executor/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/a2a/01-delegation/executor/requirements.txt @@ -1,2 +1,3 @@ -agent-framework>=1.2.2 +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry agent-framework-foundry-hosting diff --git a/samples/python/hosted-agents/agent-framework/responses/12-foundry-skills/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/12-foundry-skills/requirements.txt index f11d86eae..265575ddd 100644 --- a/samples/python/hosted-agents/agent-framework/responses/12-foundry-skills/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/12-foundry-skills/requirements.txt @@ -3,4 +3,3 @@ agent-framework-foundry agent-framework-foundry-hosting mcp<2,>=1.24.0 azure-ai-projects -mcp>=1.24.0 diff --git a/samples/python/hosted-agents/agent-framework/responses/14-browser-automation-agent/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/14-browser-automation-agent/requirements.txt index e23a67d9e..f57108c73 100644 --- a/samples/python/hosted-agents/agent-framework/responses/14-browser-automation-agent/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/14-browser-automation-agent/requirements.txt @@ -3,7 +3,7 @@ agent-framework-foundry>=1.2.2,<1.3 agent-framework-foundry-hosting>=1.0.0a260429,<1.1 azure-identity>=1.19.0 httpx>=0.28.0 -mcp>=1.24.0,<2 +mcp<2,>=1.24.0 pydantic>=2.0.0 python-dotenv>=1.0.0 websockets>=14.0 diff --git a/samples/python/hosted-agents/agent-framework/responses/16-content-safety-guardrail/requirements.txt b/samples/python/hosted-agents/agent-framework/responses/16-content-safety-guardrail/requirements.txt index a50487247..92b926abf 100644 --- a/samples/python/hosted-agents/agent-framework/responses/16-content-safety-guardrail/requirements.txt +++ b/samples/python/hosted-agents/agent-framework/responses/16-content-safety-guardrail/requirements.txt @@ -1,2 +1,4 @@ -agent-framework>=1.2.2 -agent-framework-foundry-hosting \ No newline at end of file +# Use the narrow Foundry subpackages to keep dependencies light. +agent-framework-foundry +agent-framework-foundry-hosting +mcp<2,>=1.24.0 \ No newline at end of file