|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Azure.Provisioning;
using Azure.Provisioning.CognitiveServices;
using Azure.Provisioning.Expressions;
using static Azure.Provisioning.Expressions.BicepFunction;
namespace Aspire.Hosting;
/// <summary>
/// Provides extension methods for adding the Azure OpenAI resources to the application model.
/// </summary>
public static class AzureOpenAIExtensions
{
/// <summary>
/// Adds an Azure OpenAI resource to the application model.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<AzureOpenAIResource> AddAzureOpenAI(this IDistributedApplicationBuilder builder, string name)
{
#pragma warning disable AZPROVISION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
return builder.AddAzureOpenAI(name, null);
#pragma warning restore AZPROVISION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}
/// <summary>
/// Adds an Azure OpenAI resource to the application model.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="configureResource">Callback to configure the underlying <see cref="global::Azure.Provisioning.CognitiveServices.CognitiveServicesAccount"/> resource.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
[Experimental("AZPROVISION001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")]
public static IResourceBuilder<AzureOpenAIResource> AddAzureOpenAI(this IDistributedApplicationBuilder builder, string name, Action<IResourceBuilder<AzureOpenAIResource>, ResourceModuleConstruct, CognitiveServicesAccount, IEnumerable<CognitiveServicesAccountDeployment>>? configureResource)
{
builder.AddAzureProvisioning();
var configureConstruct = (ResourceModuleConstruct construct) =>
{
var cogServicesAccount = new CognitiveServicesAccount(name)
{
Kind = "OpenAI",
Sku = new CognitiveServicesSku()
{
Name = "S0"
},
Properties = new CognitiveServicesAccountProperties()
{
CustomSubDomainName = ToLower(Take(Concat(name, GetUniqueString(GetResourceGroup().Id)), 24)),
PublicNetworkAccess = ServiceAccountPublicNetworkAccess.Enabled,
// Disable local auth for AOAI since managed identity is used
DisableLocalAuth = true
},
Tags = { { "aspire-resource-name", construct.Resource.Name } }
};
construct.Add(cogServicesAccount);
construct.Add(new BicepOutput("connectionString", typeof(string))
{
Value = new InterpolatedString(
"Endpoint={0}",
[
new MemberExpression(
new MemberExpression(
new IdentifierExpression(cogServicesAccount.ResourceName),
"properties"),
"endpoint")
])
// TODO This should be
// Value = BicepFunction.Interpolate($"Endpoint={cogServicesAccount.Endpoint}")
});
construct.Add(cogServicesAccount.AssignRole(CognitiveServicesBuiltInRole.CognitiveServicesOpenAIContributor, construct.PrincipalTypeParameter, construct.PrincipalIdParameter));
var resource = (AzureOpenAIResource)construct.Resource;
CognitiveServicesAccountDeployment? dependency = null;
var cdkDeployments = new List<CognitiveServicesAccountDeployment>();
foreach (var deployment in resource.Deployments)
{
var cdkDeployment = new CognitiveServicesAccountDeployment(deployment.Name, cogServicesAccount.ResourceVersion)
{
Name = deployment.Name,
Parent = cogServicesAccount,
Properties = new CognitiveServicesAccountDeploymentProperties()
{
Model = new CognitiveServicesAccountDeploymentModel()
{
Name = deployment.ModelName,
Version = deployment.ModelVersion,
Format = "OpenAI"
}
},
Sku = new CognitiveServicesSku()
{
Name = deployment.SkuName,
Capacity = deployment.SkuCapacity
}
};
construct.Add(cdkDeployment);
cdkDeployments.Add(cdkDeployment);
// Subsequent deployments need an explicit dependency on the previous one
// to ensure they are not created in parallel. This is equivalent to @batchSize(1)
// which can't be defined with the CDK
if (dependency != null)
{
cdkDeployment.DependsOn.Add(dependency);
}
dependency = cdkDeployment;
}
var resourceBuilder = builder.CreateResourceBuilder(resource);
configureResource?.Invoke(resourceBuilder, construct, cogServicesAccount, cdkDeployments);
};
var resource = new AzureOpenAIResource(name, configureConstruct);
return builder.AddResource(resource)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalId)
.WithParameter(AzureBicepResource.KnownParameters.PrincipalType)
.WithManifestPublishingCallback(resource.WriteToManifest);
}
/// <summary>
/// Adds an Azure OpenAI Deployment resource to the application model. This resource requires an <see cref="AzureOpenAIResource"/> to be added to the application model.
/// </summary>
/// <param name="builder">The Azure OpenAI resource builder.</param>
/// <param name="deployment">The deployment to add.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<AzureOpenAIResource> AddDeployment(this IResourceBuilder<AzureOpenAIResource> builder, AzureOpenAIDeployment deployment)
{
builder.Resource.AddDeployment(deployment);
return builder;
}
}
|