src/common/details/azcli/AzCliConsoleGui_CognitiveServicesResourceDeploymentPicker.cs (350 lines of code) (raw):
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
//
using Azure.AI.Details.Common.CLI.ConsoleGui;
namespace Azure.AI.Details.Common.CLI
{
public partial class AzCliConsoleGui
{
public static async Task<(AzCli.CognitiveServicesDeploymentInfo?,bool)> PickOrCreateCognitiveServicesResourceDeployment(bool interactive, bool allowSkipDeployment, string deploymentExtra, string subscriptionId, string groupName, string resourceRegionLocation, string resourceName, string deploymentFilter, string modelFilter)
{
bool createdNew = false;
ConsoleHelpers.WriteLineWithHighlight($"\n`AZURE OPENAI DEPLOYMENT ({deploymentExtra.ToUpper()})`");
var createNewItem = !string.IsNullOrEmpty(deploymentFilter)
? $"(Create `{deploymentFilter}`)"
: interactive ? "(Create new)" : null;
var deployment = await FindCognitiveServicesResourceDeployment(interactive, allowSkipDeployment, deploymentExtra, subscriptionId, groupName, resourceName, deploymentFilter, createNewItem);
if (deployment == null && allowSkipDeployment)
return (null, createdNew);
if (deployment != null && deployment.Value.Name == null)
{
createdNew = true;
deployment = await TryCreateCognitiveServicesResourceDeployment(interactive, deploymentExtra, subscriptionId, groupName, resourceRegionLocation, resourceName, deploymentFilter, modelFilter);
}
if (deployment == null)
{
throw new ApplicationException($"CANCELED: No deployment selected");
}
return (deployment, createdNew);
}
public static async Task<AzCli.CognitiveServicesDeploymentInfo?> FindCognitiveServicesResourceDeployment(bool interactive, bool allowSkipDeployment, string deploymentExtra, string subscriptionId, string groupName, string resourceName, string deploymentFilter, string allowCreateDeploymentOption)
{
var allowCreateDeployment = !string.IsNullOrEmpty(allowCreateDeploymentOption);
var listDeploymentsFunc = async () => await AzCli.ListCognitiveServicesDeployments(subscriptionId, groupName, resourceName, "OpenAI");
var response = await LoginHelpers.GetResponseOnLogin<AzCli.CognitiveServicesDeploymentInfo[]>(interactive, "deployment", listDeploymentsFunc);
var lookForChatCompletionCapable = deploymentExtra.ToLower() == "chat" || deploymentExtra.ToLower() == "evaluation";
var lookForEmbeddingCapable = deploymentExtra.ToLower() == "embeddings";
var lookForRealtimeCapable = deploymentExtra.ToLower() == "realtime";
var deployments = response.Payload
.Where(x => MatchDeploymentFilter(x, deploymentFilter))
.Where(x => !lookForChatCompletionCapable || x.ChatCompletionCapable)
.Where(x => !lookForEmbeddingCapable || x.EmbeddingsCapable)
.Where(x => !lookForRealtimeCapable || x.RealtimeCapable)
.OrderBy(x => x.Name)
.ToList();
var exactMatch = deploymentFilter != null && deployments.Count(x => ExactMatchDeployment(x, deploymentFilter)) == 1;
if (exactMatch) deployments = deployments.Where(x => ExactMatchDeployment(x, deploymentFilter)).ToList();
if (deployments.Count() == 0)
{
if (!allowCreateDeployment)
{
ConsoleHelpers.WriteLineError($"*** No deployments found ***");
return null;
}
else if (!interactive)
{
Console.WriteLine(allowCreateDeploymentOption);
return new AzCli.CognitiveServicesDeploymentInfo();
}
}
else if (deployments.Count() == 1 && (!interactive || exactMatch))
{
var deployment = deployments.First();
DisplayName(deployment);
return deployment;
}
else if (!interactive)
{
ConsoleHelpers.WriteLineError("*** More than 1 deployment found ***");
Console.WriteLine();
DisplayDeployments(deployments, " ");
return null;
}
var scanFor = deploymentExtra.ToLower() switch {
"chat" => "gpt",
"embeddings" => "embedding",
"evaluation" => "gpt",
_ => deploymentExtra.ToLower()
};
var choices = deployments.ToArray();
var select = Array.FindIndex(choices, x => x.Name.Contains(scanFor));
if (allowCreateDeployment && select >= 0) select++;
select = Math.Max(0, select);
return interactive
? ListBoxPickDeployment(choices, allowCreateDeploymentOption, allowSkipDeployment, select)
: null;
}
public static async Task<AzCli.CognitiveServicesDeploymentInfo?> TryCreateCognitiveServicesResourceDeployment(bool interactive, string deploymentExtra, string subscriptionId, string groupName, string resourceRegionLocation, string resourceName, string deploymentName, string modelFilter)
{
ConsoleHelpers.WriteLineWithHighlight($"\n`CREATE DEPLOYMENT ({deploymentExtra.ToUpper()})`");
var deployableModel = await FindDeployableModel(interactive, deploymentExtra, subscriptionId, resourceRegionLocation, modelFilter);
if (deployableModel == null) return null;
var modelName = deployableModel?.Name;
var skuName = deployableModel?.SkuName;
Console.WriteLine($"\rModel: {modelName}");
Console.WriteLine($"Sku: {skuName}");
var modelFormat = "OpenAI";
var modelVersion = deployableModel?.Version;
var scaleCapacity = deployableModel?.DefaultCapacity;
Console.Write("\rName: ");
if (!string.IsNullOrEmpty(deploymentName))
{
Console.WriteLine(deploymentName);
}
else if (interactive)
{
var choices = new string[] {
$"{modelName}-{modelVersion}",
"(Enter custom name)"
};
var pick = ListBoxPicker.PickIndexOf(choices);
deploymentName = pick switch
{
0 => $"{modelName}-{modelVersion}",
1 => AskPromptHelper.AskPrompt("\rName: "),
_ => null
};
if (pick != choices.Length - 1)
{
Console.WriteLine($"\rName: {deploymentName}");
}
}
else
{
deploymentName = $"{modelName}-{modelVersion}";
Console.WriteLine(deploymentName);
}
if (string.IsNullOrEmpty(deploymentName)) return null;
Console.Write("*** CREATING ***");
var response = await AzCli.CreateCognitiveServicesDeployment(subscriptionId, groupName, resourceName, deploymentName, modelName, modelVersion, modelFormat, scaleCapacity, skuName);
Console.Write("\r");
if (string.IsNullOrEmpty(response.Output.StdOutput) && !string.IsNullOrEmpty(response.Output.StdError))
{
throw new ApplicationException($"ERROR: Creating deployment: {response.Output.StdError}");
}
Console.WriteLine("\r*** CREATED *** ");
return response.Payload;
}
private static async Task<AzCli.CognitiveServicesModelInfo?> FindDeployableModel(bool interactive, string deploymentExtra, string subscriptionId, string resourceRegionLocation, string modelFilter)
{
Console.Write("\rModel: *** Loading choices ***");
var models = await AzCli.ListCognitiveServicesModels(subscriptionId, resourceRegionLocation);
var usage = await AzCli.ListCognitiveServicesUsage(subscriptionId, resourceRegionLocation);
if (string.IsNullOrEmpty(models.Output.StdOutput) && !string.IsNullOrEmpty(models.Output.StdError))
{
throw new ApplicationException($"ERROR: Loading models\n{models.Output.StdError}");
}
else if (string.IsNullOrEmpty(usage.Output.StdOutput) && !string.IsNullOrEmpty(usage.Output.StdError))
{
throw new ApplicationException($"ERROR: Loading model usage\n{usage.Output.StdError}");
}
var lookForChatCompletionCapable = deploymentExtra.ToLower() == "chat" || deploymentExtra.ToLower() == "evaluation";
var lookForEmbeddingCapable = deploymentExtra.ToLower() == "embeddings";
var lookForRealtimeCapable = deploymentExtra.ToLower() == "realtime";
var capableModels = models.Payload
.Where(x => !lookForChatCompletionCapable || x.ChatCompletionCapable)
.Where(x => !lookForEmbeddingCapable || x.EmbeddingsCapable)
.Where(x => !lookForRealtimeCapable || x.RealtimeCapable)
.ToArray();
Console.Write("\rModel: ");
var deployableModels = FilterModelsByUsage(capableModels, usage.Payload);
var exactMatch = modelFilter != null && deployableModels.Count(x => ExactMatchModel(x, modelFilter)) == 1;
if (exactMatch) deployableModels = deployableModels.Where(x => ExactMatchModel(x, modelFilter)).ToArray();
if (deployableModels.Count() == 0)
{
ConsoleHelpers.WriteLineError(models.Payload.Count() > 0
? $"*** No matching {deploymentExtra} capable models with capacity found ***"
: "*** No deployable models found ***");
return null;
}
else if (deployableModels.Count() == 1 && (!interactive || exactMatch))
{
var model = deployableModels.First();
Console.WriteLine($"{model.Name} (version {model.Version}) ({model.SkuName}) ({model.UsageName})");
return model;
}
else if (!interactive)
{
ConsoleHelpers.WriteLineError("*** More than 1 deployable model found ***");
Console.WriteLine();
DisplayDeployableModels(deployableModels.ToList(), " ");
return null;
}
var choices = deployableModels.Select(x => x.Name + " (version " + x.Version + ") (" + x.SkuName + ")").ToArray();
var scanFor = deploymentExtra.ToLower() switch
{
"chat" => "gpt",
"embeddings" => "embedding",
_ => deploymentExtra.ToLower()
};
var select = Math.Max(0, Array.FindLastIndex(choices, x => x.Contains(scanFor)));
var index = ListBoxPicker.PickIndexOf(choices, select);
if (index < 0) return null;
return deployableModels[index];
}
private static void DisplayDeployableModels(List<AzCli.CognitiveServicesModelInfo> deployableModels, string prefix)
{
foreach (var deployableModel in deployableModels)
{
Console.Write(prefix);
DisplayNameAndVersionAndSku(deployableModel);
}
Console.WriteLine();
}
private static void DisplayNameAndVersionAndSku(AzCli.CognitiveServicesModelInfo deployableModel)
{
Console.WriteLine($"{deployableModel.Name}-{deployableModel.Version}-{deployableModel.SkuName}");
}
private static bool ExactMatchModel(AzCli.CognitiveServicesModelInfo model, string modelFilter)
{
var displayName = model.Name + "-" + model.Version + "-" + model.UsageName;
return displayName.ToLower() == modelFilter.ToLower();
}
private static AzCli.CognitiveServicesModelInfo[] FilterModelsByUsage(AzCli.CognitiveServicesModelInfo[] models, AzCli.CognitiveServicesUsageInfo[] usage)
{
// if (Program.Debug)
// {
// Console.WriteLine($"\rModel: (found {models.Count()} models) (pre-grouping)\n");
// foreach (var model in models)
// {
// Console.WriteLine($"{model.Name} (version {model.Version}) (sku {model.SkuName}) (usageName {model.UsageName}) capacity={model.DefaultCapacity}");
// }
// Console.WriteLine();
// }
// models = models.GroupBy(x => x.Name + x.Version + x.Format).Select(x => x.First()).ToArray();
if (Program.Debug)
{
Console.WriteLine($"\rModel: (found {models.Count()} models)\n");
foreach (var model in models)
{
Console.WriteLine($"{model.Name} (version {model.Version}) (sku {model.SkuName}) (usageName {model.UsageName}) capacity={model.DefaultCapacity}");
}
Console.WriteLine();
Console.WriteLine($"\rUsage: (found {usage.Count()} usage)\n");
foreach (var use in usage)
{
Console.WriteLine($"{use.Name} current={use.Current} limit={use.Limit}");
}
Console.WriteLine();
}
var filteredKeep = new List<AzCli.CognitiveServicesModelInfo>();
foreach (var model in models)
{
if (!double.TryParse(model.DefaultCapacity, out var defaultCapacityValue))
{
defaultCapacityValue = 1;
}
var checkUsage = usage.Where(x => x.Name.EndsWith(model.Name));
var current = checkUsage.Count() > 0
? checkUsage.Sum(x => double.TryParse(x.Current, out var value) ? value : 0)
: 0;
var limit = checkUsage.Count() > 0
? checkUsage.Sum(x => double.TryParse(x.Limit, out var value) ? value : 0)
: 1;
var available = limit - current;
if (available <= 0) continue;
if (defaultCapacityValue <= available)
{
filteredKeep.Add(model);
continue;
}
var newDefault = available - 1;
if (newDefault < 1) newDefault = 1;
filteredKeep.Add(new AzCli.CognitiveServicesModelInfo()
{
Name = model.Name,
Version = model.Version,
Format = model.Format,
DefaultCapacity = newDefault.ToString(),
ChatCompletionCapable = model.ChatCompletionCapable,
EmbeddingsCapable = model.EmbeddingsCapable,
SkuName = model.SkuName,
UsageName = model.UsageName
});
}
if (filteredKeep.Count() <= models.Count())
{
var filteredDidntKeep = new List<string>();
foreach (var model in models)
{
if (filteredKeep.Any(x => x.Name == model.Name && x.Version == model.Version && x.Format == model.Format)) continue;
filteredDidntKeep.Add($"{model.Name} (version {model.Version})");
}
filteredDidntKeep.Sort();
if (filteredDidntKeep.Count() > 0)
{
Console.WriteLine($"\rModel: (excluded {filteredDidntKeep.Count()} models with zero remaining quota)\n");
foreach (var model in filteredDidntKeep)
{
ConsoleHelpers.WriteLineWithHighlight($" `#e_;*** EXCLUDED: {model} ***`");
}
Console.WriteLine();
}
}
if (Program.Debug)
{
Console.WriteLine($"\rModel: ({filteredKeep.Count()} models with remaining quota)\n");
foreach (var model in filteredKeep)
{
Console.WriteLine($"{model.Name} (version {model.Version}) (sku {model.SkuName}) (usageName {model.UsageName}) capacity={model.DefaultCapacity}");
}
Console.WriteLine();
}
return filteredKeep.ToArray();
}
private static AzCli.CognitiveServicesDeploymentInfo? ListBoxPickDeployment(AzCli.CognitiveServicesDeploymentInfo[] deployments, string p0, bool allowSkipDeployment = false, int select = 0)
{
var list = deployments.Select(x => !string.IsNullOrEmpty(x.ModelName)
? $"{x.Name} ({x.ModelName})"
: $"{x.Name}").ToList();
var hasP0 = !string.IsNullOrEmpty(p0);
if (hasP0) list.Insert(0, p0);
if (allowSkipDeployment)
{
list.Add("(Skip)");
}
var picked = ListBoxPicker.PickIndexOf(list.ToArray(), select);
if (picked < 0)
{
Console.WriteLine();
return null;
}
if (hasP0 && picked == 0)
{
Console.WriteLine(p0);
return new AzCli.CognitiveServicesDeploymentInfo();
}
if (allowSkipDeployment && picked == list.Count - 1)
{
Console.WriteLine("(Skip)");
return null;
}
if (hasP0) picked--;
Console.WriteLine($"{deployments[picked].Name}");
return deployments[picked];
}
private static bool ExactMatchDeployment(AzCli.CognitiveServicesDeploymentInfo deployment, string deploymentFilter)
{
return !string.IsNullOrEmpty(deploymentFilter) && deployment.Name.ToLower() == deploymentFilter;
}
private static bool MatchDeploymentFilter(AzCli.CognitiveServicesDeploymentInfo deployment, string deploymentFilter)
{
if (deploymentFilter != null && ExactMatchDeployment(deployment, deploymentFilter))
{
return true;
}
var name = deployment.Name.ToLower();
return (string.IsNullOrEmpty(deploymentFilter) || name.Contains(deploymentFilter) || StringHelpers.ContainsAllCharsInOrder(name, deploymentFilter));
}
private static void DisplayDeployments(List<AzCli.CognitiveServicesDeploymentInfo> deployments, string prefix)
{
foreach (var deployment in deployments)
{
Console.Write(prefix);
DisplayName(deployment);
}
}
private static void DisplayName(AzCli.CognitiveServicesDeploymentInfo deployment)
{
Console.Write($"{deployment.Name}");
Console.WriteLine(new string(' ', 20));
}
}
}