-
Notifications
You must be signed in to change notification settings - Fork 577
/
Copy pathAzureOpenAIExtensions.cs
196 lines (173 loc) · 9.57 KB
/
AzureOpenAIExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Azure;
using Azure.Provisioning;
using Azure.Provisioning.CognitiveServices;
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
{
internal const string DefaultConfigSectionName = "Aspire:Azure:AI:OpenAI";
/// <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>
/// <remarks>
/// By default references to the Azure OpenAI resource will be assigned the following roles:
///
/// - <see cref="CognitiveServicesBuiltInRole.CognitiveServicesOpenAIContributor"/>
///
/// These can be replaced by calling <see cref="WithRoleAssignments{T}(IResourceBuilder{T}, IResourceBuilder{AzureOpenAIResource}, CognitiveServicesBuiltInRole[])"/>.
/// </remarks>
public static IResourceBuilder<AzureOpenAIResource> AddAzureOpenAI(this IDistributedApplicationBuilder builder, [ResourceName] string name)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);
builder.AddAzureProvisioning();
var configureInfrastructure = (AzureResourceInfrastructure infrastructure) =>
{
var cogServicesAccount = AzureProvisioningResource.CreateExistingOrNewProvisionableResource(infrastructure,
(identifier, name) =>
{
var resource = CognitiveServicesAccount.FromExisting(identifier);
resource.Name = name;
return resource;
},
(infrastructure) => new CognitiveServicesAccount(infrastructure.AspireResource.GetBicepIdentifier())
{
Kind = "OpenAI",
Sku = new CognitiveServicesSku()
{
Name = "S0"
},
Properties = new CognitiveServicesAccountProperties()
{
CustomSubDomainName = ToLower(Take(Concat(infrastructure.AspireResource.Name, GetUniqueString(GetResourceGroup().Id)), 24)),
PublicNetworkAccess = ServiceAccountPublicNetworkAccess.Enabled,
// Disable local auth for AOAI since managed identity is used
DisableLocalAuth = true
},
Tags = { { "aspire-resource-name", infrastructure.AspireResource.Name } }
});
infrastructure.Add(new ProvisioningOutput("connectionString", typeof(string))
{
Value = Interpolate($"Endpoint={cogServicesAccount.Properties.Endpoint}")
});
// We need to output name to externalize role assignments.
infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = cogServicesAccount.Name });
var resource = (AzureOpenAIResource)infrastructure.AspireResource;
CognitiveServicesAccountDeployment? dependency = null;
var cdkDeployments = new List<CognitiveServicesAccountDeployment>();
foreach (var deployment in resource.Deployments)
{
var cdkDeployment = new CognitiveServicesAccountDeployment(Infrastructure.NormalizeBicepIdentifier(deployment.Name))
{
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
}
};
infrastructure.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 resource = new AzureOpenAIResource(name, configureInfrastructure);
return builder.AddResource(resource)
.WithDefaultRoleAssignments(CognitiveServicesBuiltInRole.GetBuiltInRoleName,
CognitiveServicesBuiltInRole.CognitiveServicesOpenAIContributor);
}
/// <summary>
/// Adds an Azure OpenAI Deployment to the <see cref="AzureOpenAIResource"/> resource. 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)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(deployment);
builder.Resource.AddDeployment(deployment);
return builder;
}
/// <summary>
/// Injects the environment variables from the source <see cref="AzureOpenAIResource" /> into the destination resource, using the source resource's name as the connection string name (if not overridden).
/// The format of the connection environment variable will be "ConnectionStrings__{sourceResourceName}={connectionString}".
/// Each deployment will be injected using the format "Aspire__Azure__AI__OpenAI__{sourceResourceName}__Models__{deploymentName}={modelName}".
/// </summary>
/// <typeparam name="TDestination">The destination resource.</typeparam>
/// <param name="builder">The resource where connection string will be injected.</param>
/// <param name="source">The resource from which to extract the connection string.</param>
/// <param name="resourceName">An override of the source resource's name for the connection string. The resulting connection string will be "ConnectionStrings__connectionName" if this is not null.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<TDestination> WithReference<TDestination>(this IResourceBuilder<TDestination> builder, IResourceBuilder<AzureOpenAIResource> source, string? resourceName = null)
where TDestination : IResourceWithEnvironment
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(source);
var resource = source.Resource;
resourceName ??= resource.Name;
builder.WithReference((IResourceBuilder<IResourceWithConnectionString>)source, resourceName);
return builder.WithEnvironment(context =>
{
foreach (var deployment in resource.Deployments)
{
var variableName = $"ASPIRE__AZURE__AI__OPENAI__{resourceName}__MODELS__{deployment.Name}";
context.EnvironmentVariables[variableName] = deployment.ModelName;
}
});
}
/// <summary>
/// Assigns the specified roles to the given resource, granting it the necessary permissions
/// on the target Azure OpenAI resource. This replaces the default role assignments for the resource.
/// </summary>
/// <param name="builder">The resource to which the specified roles will be assigned.</param>
/// <param name="target">The target Azure OpenAI resource.</param>
/// <param name="roles">The built-in Cognitive Services roles to be assigned.</param>
/// <returns>The updated <see cref="IResourceBuilder{T}"/> with the applied role assignments.</returns>
/// <example>
/// Assigns the CognitiveServicesOpenAIUser role to the 'Projects.Api' project.
/// <code lang="csharp">
/// var builder = DistributedApplication.CreateBuilder(args);
///
/// var openai = builder.AddAzureOpenAI("openai");
///
/// var api = builder.AddProject<Projects.Api>("api")
/// .WithRoleAssignments(openai, CognitiveServicesBuiltInRole.CognitiveServicesOpenAIUser)
/// .WithReference(openai);
/// </code>
/// </example>
public static IResourceBuilder<T> WithRoleAssignments<T>(
this IResourceBuilder<T> builder,
IResourceBuilder<AzureOpenAIResource> target,
params CognitiveServicesBuiltInRole[] roles)
where T : IResource
{
return builder.WithRoleAssignments(target, CognitiveServicesBuiltInRole.GetBuiltInRoleName, roles);
}
}