diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs index 708243bb37..57bb5017c7 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlanner.cs @@ -1,4 +1,5 @@ using Azure.AI.OpenAI; +using Azure.Core; using Microsoft.Bot.Builder; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -43,20 +44,30 @@ public class AssistantsPlanner : IPlanner public AssistantsPlanner(AssistantsPlannerOptions options, ILoggerFactory? loggerFactory = null) { Verify.ParamNotNull(options); - Verify.ParamNotNull(options.ApiKey, "AssistantsPlannerOptions.ApiKey"); Verify.ParamNotNull(options.AssistantId, "AssistantsPlannerOptions.AssistantId"); - _options = new AssistantsPlannerOptions(options.ApiKey, options.AssistantId) - { - Organization = options.Organization, - PollingInterval = options.PollingInterval ?? DEFAULT_POLLING_INTERVAL - }; + options.PollingInterval = options.PollingInterval ?? DEFAULT_POLLING_INTERVAL; + + _options = options; _logger = loggerFactory == null ? NullLogger.Instance : loggerFactory.CreateLogger>(); - _client = _CreateClient(options.ApiKey, options.Endpoint); + + if (options.TokenCredential != null) + { + Verify.ParamNotNull(options.Endpoint, "AssistantsPlannerOptions.Endpoint"); + _client = _CreateClient(options.TokenCredential, options.Endpoint!); + } + else if (options.ApiKey != null) + { + _client = _CreateClient(options.ApiKey, options.Endpoint); + } + else + { + throw new ArgumentException("Either `AssistantsPlannerOptions.ApiKey` or `AssistantsPlannerOptions.TokenCredential` should be set."); + } } /// - /// Static helper method for programmatically creating an assistant. + /// Static helper method for programatically creating an assistant. /// /// OpenAI or Azure OpenAI API key. /// Definition of the assistant to create. @@ -76,6 +87,28 @@ public static async Task CreateAssistantAsync(string apiKey, Assistan return await client.CreateAssistantAsync(model, request, cancellationToken); } + /// + /// Static helper method for programatically creating an assistant. + /// + /// Azure token credential to authenticate requests + /// Definition of the assistant to create. + /// The underlying LLM model. + /// Azure OpenAI API Endpoint. + /// A cancellation token that can be used by other objects + /// or threads to receive notice of cancellation. + /// The created assistant. + public static async Task CreateAssistantAsync(TokenCredential tokenCredential, AssistantCreationOptions request, string model, string endpoint, CancellationToken cancellationToken = default) + { + Verify.ParamNotNull(tokenCredential); + Verify.ParamNotNull(request); + Verify.ParamNotNull(model); + Verify.ParamNotNull(endpoint); + + AssistantClient client = _CreateClient(tokenCredential, endpoint); + + return await client.CreateAssistantAsync(model, request, cancellationToken); + } + /// public async Task BeginTaskAsync(ITurnContext turnContext, TState turnState, AI ai, CancellationToken cancellationToken) { @@ -334,6 +367,15 @@ internal static AssistantClient _CreateClient(string apiKey, string? endpoint = return new AssistantClient(apiKey); } } + + internal static AssistantClient _CreateClient(TokenCredential tokenCredential, string endpoint) + { + Verify.ParamNotNull(tokenCredential); + Verify.ParamNotNull(endpoint); + + AzureOpenAIClient azureOpenAI = new(new Uri(endpoint), tokenCredential); + return azureOpenAI.GetAssistantClient(); + } } } #pragma warning restore OPENAI001 diff --git a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlannerOptions.cs b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlannerOptions.cs index 50273c9202..ab240d8add 100644 --- a/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlannerOptions.cs +++ b/dotnet/packages/Microsoft.TeamsAI/Microsoft.TeamsAI/AI/Planners/AssistantsPlannerOptions.cs @@ -1,4 +1,5 @@ -using Microsoft.Teams.AI.Utilities; +using Azure.Core; +using Microsoft.Teams.AI.Utilities; // Assistants API is currently in beta and is subject to change. #pragma warning disable IDE0130 // Namespace does not match folder structure @@ -13,13 +14,18 @@ public class AssistantsPlannerOptions /// /// OpenAI API key or Azure OpenAI API key. /// - public string ApiKey { get; set; } + public string? ApiKey { get; set; } /// /// Optional. Azure OpenAI Endpoint. /// public string? Endpoint { get; set; } + /// + /// Optional. The token credential to use when making requests to Azure OpenAI. + /// + public TokenCredential? TokenCredential { get; set; } + /// /// The Assistant ID. /// @@ -51,5 +57,21 @@ public AssistantsPlannerOptions(string apiKey, string assistantId, string? endpo AssistantId = assistantId; Endpoint = endpoint; } + + /// + /// Create an instance of the AsssistantsPlannerOptions class. + /// + /// The token credential object. This can be set to DefaultAzureCredential to use managed identity auth. + /// The Assistant ID. + /// Optional. The Azure OpenAI Endpoint + public AssistantsPlannerOptions(TokenCredential tokenCredential, string assistantId, string? endpoint = null) + { + Verify.ParamNotNull(tokenCredential); + Verify.ParamNotNull(assistantId); + + TokenCredential = tokenCredential; + AssistantId = assistantId; + Endpoint = endpoint; + } } } diff --git a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj index 3eef0e49f1..22429ecfb8 100644 --- a/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj +++ b/dotnet/samples/06.assistants.a.mathBot/MathBot.csproj @@ -12,6 +12,7 @@ + diff --git a/dotnet/samples/06.assistants.a.mathBot/Program.cs b/dotnet/samples/06.assistants.a.mathBot/Program.cs index 5da03db9eb..069dfaca6c 100644 --- a/dotnet/samples/06.assistants.a.mathBot/Program.cs +++ b/dotnet/samples/06.assistants.a.mathBot/Program.cs @@ -5,9 +5,11 @@ using Microsoft.Teams.AI.AI; using Microsoft.Teams.AI.AI.Planners.Experimental; using Microsoft.Teams.AI.AI.Planners; - using MathBot; using OpenAI.Assistants; +using Azure.Core; +using Azure.Identity; +using System.Runtime.CompilerServices; var builder = WebApplication.CreateBuilder(args); @@ -17,19 +19,29 @@ // Load configuration var config = builder.Configuration.Get()!; -var isAzureCredentialsSet = config.Azure != null && !string.IsNullOrEmpty(config.Azure.OpenAIApiKey) && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint); +var isAzureCredentialsSet = config.Azure != null && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint); var isOpenAICredentialsSet = config.OpenAI != null && !string.IsNullOrEmpty(config.OpenAI.ApiKey); -string apiKey = ""; +string? apiKey = null; +TokenCredential? tokenCredential = null; string? endpoint = null; string? assistantId = ""; // If both credentials are set then the Azure credentials will be used. if (isAzureCredentialsSet) { - apiKey = config.Azure!.OpenAIApiKey!; - endpoint = config.Azure.OpenAIEndpoint; + endpoint = config.Azure!.OpenAIEndpoint; assistantId = config.Azure.OpenAIAssistantId; + + if (config.Azure!.OpenAIApiKey != string.Empty) + { + apiKey = config.Azure!.OpenAIApiKey!; + } + else + { + // Using managed identity authentication + tokenCredential = new DefaultAzureCredential(); + } } else if (isOpenAICredentialsSet) { @@ -45,13 +57,24 @@ // Missing Assistant ID, create new Assistant if (string.IsNullOrEmpty(assistantId)) { - Console.WriteLine("No Assistant ID configured, creating new Assistant..."); - AssistantCreationOptions assistantCreateParams = new() + AssistantCreationOptions assistantCreationOptions = new() { Name = "Math Tutor", Instructions = "You are a personal math tutor. Write and run code to answer math questions." }; - assistantCreateParams.Tools.Add(new CodeInterpreterToolDefinition()); + + assistantCreationOptions.Tools.Add(new CodeInterpreterToolDefinition()); + + string newAssistantId = ""; + if (apiKey != null) + { + newAssistantId = AssistantsPlanner.CreateAssistantAsync(apiKey, assistantCreationOptions, "gpt-4o-mini", endpoint).Result.Id; + } + else + { + // use token credential for authentication + newAssistantId = AssistantsPlanner.CreateAssistantAsync(tokenCredential!, assistantCreationOptions, "gpt-4o-mini", endpoint!).Result.Id; + } string newAssistantId = AssistantsPlanner.CreateAssistantAsync(apiKey, assistantCreateParams, "gpt-4", endpoint).Result.Id; Console.WriteLine($"Created a new assistant with an ID of: {newAssistantId}"); @@ -77,7 +100,20 @@ builder.Services.AddSingleton(sp => sp.GetService()!); builder.Services.AddSingleton(); -builder.Services.AddSingleton(_ => new AssistantsPlannerOptions(apiKey, assistantId) { Endpoint = endpoint }); +builder.Services.AddSingleton(_ => { + if (apiKey != null) + { + return new AssistantsPlannerOptions(apiKey, assistantId, endpoint); + } + else if (tokenCredential != null) + { + return new AssistantsPlannerOptions(tokenCredential, assistantId, endpoint); + } + else + { + throw new ArgumentException("The `apiKey` or `tokenCredential` needs to be set"); + } +}); // Create the Application. builder.Services.AddTransient(sp => diff --git a/dotnet/samples/06.assistants.a.mathBot/teamsapp.yml b/dotnet/samples/06.assistants.a.mathBot/teamsapp.yml index f5500c8d9a..ab23f50fba 100644 --- a/dotnet/samples/06.assistants.a.mathBot/teamsapp.yml +++ b/dotnet/samples/06.assistants.a.mathBot/teamsapp.yml @@ -95,3 +95,4 @@ deploy: # You can replace it with an existing Azure Resource ID or other # custom environment variable. resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} +projectId: 562123aa-6256-4018-bd3f-ca91d1cbd4d9 diff --git a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj index 98a9170020..8b8c5d6f0a 100644 --- a/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj +++ b/dotnet/samples/06.assistants.b.orderBot/OrderBot.csproj @@ -13,6 +13,7 @@ + diff --git a/dotnet/samples/06.assistants.b.orderBot/Program.cs b/dotnet/samples/06.assistants.b.orderBot/Program.cs index d8a3f602ab..6cc07d97be 100644 --- a/dotnet/samples/06.assistants.b.orderBot/Program.cs +++ b/dotnet/samples/06.assistants.b.orderBot/Program.cs @@ -8,6 +8,9 @@ using OrderBot; using OrderBot.Models; using OpenAI.Assistants; +using Azure.Core; +using Azure.Identity; +using System.Runtime.CompilerServices; var builder = WebApplication.CreateBuilder(args); @@ -17,19 +20,28 @@ // Load configuration var config = builder.Configuration.Get()!; -var isAzureCredentialsSet = config.Azure != null && !string.IsNullOrEmpty(config.Azure.OpenAIApiKey) && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint); +var isAzureCredentialsSet = config.Azure != null && !string.IsNullOrEmpty(config.Azure.OpenAIEndpoint); var isOpenAICredentialsSet = config.OpenAI != null && !string.IsNullOrEmpty(config.OpenAI.ApiKey); -string apiKey = ""; +string? apiKey = null; +TokenCredential? tokenCredential = null; string? endpoint = null; string? assistantId = ""; // If both credentials are set then the Azure credentials will be used. if (isAzureCredentialsSet) { - apiKey = config.Azure!.OpenAIApiKey!; - endpoint = config.Azure.OpenAIEndpoint; + endpoint = config.Azure!.OpenAIEndpoint; assistantId = config.Azure.OpenAIAssistantId; + + if (config.Azure!.OpenAIApiKey != string.Empty) + { + apiKey = config.Azure!.OpenAIApiKey!; + } else + { + // Using managed identity authentication + tokenCredential = new DefaultAzureCredential(); + } } else if (isOpenAICredentialsSet) { @@ -59,7 +71,16 @@ assistantCreationOptions.Tools.Add(new FunctionToolDefinition("place_order", "Creates or updates a food order.", new BinaryData(OrderParameters.GetSchema()))); - string newAssistantId = AssistantsPlanner.CreateAssistantAsync(apiKey, assistantCreationOptions, "gpt-4", endpoint).Result.Id; + string newAssistantId = ""; + if (apiKey != null) + { + newAssistantId = AssistantsPlanner.CreateAssistantAsync(apiKey, assistantCreationOptions, "gpt-4o-mini", endpoint).Result.Id; + } + else + { + // use token credential for authentication + newAssistantId = AssistantsPlanner.CreateAssistantAsync(tokenCredential!, assistantCreationOptions, "gpt-4o-mini", endpoint!).Result.Id; + } Console.WriteLine($"Created a new assistant with an ID of: {newAssistantId}"); Console.WriteLine("Copy and save above ID, and set `OpenAI:AssistantId` in appsettings.Development.json."); @@ -70,8 +91,8 @@ // Prepare Configuration for ConfigurationBotFrameworkAuthentication builder.Configuration["MicrosoftAppType"] = "MultiTenant"; -builder.Configuration["MicrosoftAppId"] = config.BOT_ID; -builder.Configuration["MicrosoftAppPassword"] = config.BOT_PASSWORD; +builder.Configuration["MicrosoftAppId"] = ""; // config.BOT_ID; +builder.Configuration["MicrosoftAppPassword"] = ""; // config.BOT_PASSWORD; // Create the Bot Framework Authentication to be used with the Bot Adapter. builder.Services.AddSingleton(); @@ -84,7 +105,18 @@ builder.Services.AddSingleton(sp => sp.GetService()!); builder.Services.AddSingleton(); -builder.Services.AddSingleton(_ => new AssistantsPlannerOptions(apiKey, assistantId, endpoint)); +builder.Services.AddSingleton(_ => { + if (apiKey != null) + { + return new AssistantsPlannerOptions(apiKey, assistantId, endpoint); + } else if (tokenCredential != null) + { + return new AssistantsPlannerOptions(tokenCredential, assistantId, endpoint); + } else + { + throw new ArgumentException("The `apiKey` or `tokenCredential` needs to be set"); + } +}); // Create the Application. builder.Services.AddTransient(sp => diff --git a/dotnet/samples/06.assistants.b.orderBot/teamsapp.yml b/dotnet/samples/06.assistants.b.orderBot/teamsapp.yml index d58fda35a7..958037b284 100644 --- a/dotnet/samples/06.assistants.b.orderBot/teamsapp.yml +++ b/dotnet/samples/06.assistants.b.orderBot/teamsapp.yml @@ -95,3 +95,4 @@ deploy: # You can replace it with an existing Azure Resource ID or other # custom environment variable. resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}} +projectId: e9c61418-40b8-4559-914f-6267cf343d93