From 14f38116484d833929402be1db7cbd00e2c0bef3 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:00:38 +0100 Subject: [PATCH] .Net: Add Azure OpenAI Agent Model Samples (#80) * Improved Merge logic * Add modularization for Model Samples and Azure OpenAI * Address PR feedback * Warning fix * Address PR comments --- dotnet/Directory.Packages.props | 3 + dotnet/samples/GettingStarted/AgentSample.cs | 32 +++++++- .../GettingStarted/GettingStarted.csproj | 2 + ...entAgent_With_AzureOpenAIChatCompletion.cs | 56 +++++++++++++ .../ChatClientAgent_With_OpenAIAssistant.cs | 8 +- ...atClientAgent_With_OpenAIChatCompletion.cs | 10 +-- .../GettingStarted/Steps/Step01_Running.cs | 44 ++++++----- .../GettingStarted/Steps/Step02_UsingTools.cs | 36 +++++---- .../ChatCompletion/ChatClientAgent.cs | 78 +++++++++++-------- .../src/Shared/Samples/TestConfiguration.cs | 66 ++++++++++------ 10 files changed, 235 insertions(+), 100 deletions(-) create mode 100644 dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index a295476d40..eb2578453b 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -5,6 +5,9 @@ true + + + diff --git a/dotnet/samples/GettingStarted/AgentSample.cs b/dotnet/samples/GettingStarted/AgentSample.cs index 3ba34ac3fa..12cf9fa7af 100644 --- a/dotnet/samples/GettingStarted/AgentSample.cs +++ b/dotnet/samples/GettingStarted/AgentSample.cs @@ -1,5 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. +using System.ClientModel; +using Azure.AI.OpenAI; +using Azure.Identity; using Microsoft.Extensions.AI; using Microsoft.Shared.Samples; using OpenAI; @@ -8,8 +11,35 @@ namespace GettingStarted; public class AgentSample(ITestOutputHelper output) : BaseSample(output) { - protected IChatClient GetOpenAIChatClient() + /// + /// Represents the available providers for instances. + /// + public enum ChatClientProviders + { + OpenAI, + AzureOpenAI, + } + + protected IChatClient GetChatClient(ChatClientProviders provider) + { + return provider switch + { + ChatClientProviders.OpenAI => GetOpenAIChatClient(), + ChatClientProviders.AzureOpenAI => GetAzureOpenAIChatClient(), + _ => throw new NotSupportedException($"Provider {provider} is not supported.") + }; + } + + private IChatClient GetOpenAIChatClient() => new OpenAIClient(TestConfiguration.OpenAI.ApiKey) .GetChatClient(TestConfiguration.OpenAI.ChatModelId) .AsIChatClient(); + + private IChatClient GetAzureOpenAIChatClient() + => ((TestConfiguration.AzureOpenAI.ApiKey is null) + // Use Azure CLI credentials if API key is not provided. + ? new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new AzureCliCredential()) + : new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey))) + .GetChatClient(TestConfiguration.AzureOpenAI.DeploymentName) + .AsIChatClient(); } diff --git a/dotnet/samples/GettingStarted/GettingStarted.csproj b/dotnet/samples/GettingStarted/GettingStarted.csproj index 908c71111b..ad71ab05d8 100644 --- a/dotnet/samples/GettingStarted/GettingStarted.csproj +++ b/dotnet/samples/GettingStarted/GettingStarted.csproj @@ -15,6 +15,8 @@ + + diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs new file mode 100644 index 0000000000..c3da844e96 --- /dev/null +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_AzureOpenAIChatCompletion.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.ClientModel; +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents; +using Microsoft.Extensions.AI; +using Microsoft.Shared.Samples; + +namespace Providers; + +/// +/// End-to-end sample showing how to use with Azure OpenAI Chat Completion. +/// +public sealed class ChatClientAgent_With_AzureOpenAIChatCompletion(ITestOutputHelper output) : AgentSample(output) +{ + private const string JokerName = "Joker"; + private const string JokerInstructions = "You are good at telling jokes."; + + [Fact] + public async Task RunWithChatCompletion() + { + // Get the chat client to use for the agent. + using var chatClient = ((TestConfiguration.AzureOpenAI.ApiKey is null) + // Use Azure CLI credentials if API key is not provided. + ? new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new AzureCliCredential()) + : new AzureOpenAIClient(TestConfiguration.AzureOpenAI.Endpoint, new ApiKeyCredential(TestConfiguration.AzureOpenAI.ApiKey))) + .GetChatClient(TestConfiguration.AzureOpenAI.DeploymentName) + .AsIChatClient(); + + // Define the agent + ChatClientAgent agent = + new(chatClient, new() + { + Name = JokerName, + Instructions = JokerInstructions, + }); + + // Start a new thread for the agent conversation. + AgentThread thread = agent.GetNewThread(); + + // Respond to user input + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); + + // Local function to invoke agent and display the conversation messages for the thread. + async Task RunAgentAsync(string input) + { + this.WriteUserMessage(input); + + var response = await agent.RunAsync(input, thread); + + this.WriteResponseOutput(response); + } + } +} diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs index 148c71b605..650f727d5c 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIAssistant.cs @@ -10,7 +10,7 @@ using OpenAI; namespace Providers; /// -/// Shows how to use with Open AI Assistants. +/// End-to-end sample showing how to use with OpenAI Assistants. /// public sealed class ChatClientAgent_With_OpenAIAssistant(ITestOutputHelper output) : AgentSample(output) { @@ -45,11 +45,11 @@ public sealed class ChatClientAgent_With_OpenAIAssistant(ITestOutputHelper outpu AgentThread thread = agent.GetNewThread(); // Respond to user input - await InvokeAgentAsync("Tell me a joke about a pirate."); - await InvokeAgentAsync("Now add some emojis to the joke."); + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); // Local function to invoke agent and display the conversation messages for the thread. - async Task InvokeAgentAsync(string input) + async Task RunAgentAsync(string input) { this.WriteUserMessage(input); diff --git a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion.cs b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion.cs index b2ba8c6982..84062d47f8 100644 --- a/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion.cs +++ b/dotnet/samples/GettingStarted/Providers/ChatClientAgent_With_OpenAIChatCompletion.cs @@ -8,7 +8,7 @@ using OpenAI; namespace Providers; /// -/// Shows how to use with Open AI Chat Completion. +/// End-to-end sample showing how to use with OpenAI Chat Completion. /// public sealed class ChatClientAgent_With_OpenAIChatCompletion(ITestOutputHelper output) : AgentSample(output) { @@ -16,7 +16,7 @@ public sealed class ChatClientAgent_With_OpenAIChatCompletion(ITestOutputHelper private const string JokerInstructions = "You are good at telling jokes."; [Fact] - public async Task RunWithOpenAIAssistant() + public async Task RunWithChatCompletion() { // Get the chat client to use for the agent. using var chatClient = new OpenAIClient(TestConfiguration.OpenAI.ApiKey) @@ -35,11 +35,11 @@ public sealed class ChatClientAgent_With_OpenAIChatCompletion(ITestOutputHelper AgentThread thread = agent.GetNewThread(); // Respond to user input - await InvokeAgentAsync("Tell me a joke about a pirate."); - await InvokeAgentAsync("Now add some emojis to the joke."); + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); // Local function to invoke agent and display the conversation messages for the thread. - async Task InvokeAgentAsync(string input) + async Task RunAgentAsync(string input) { this.WriteUserMessage(input); diff --git a/dotnet/samples/GettingStarted/Steps/Step01_Running.cs b/dotnet/samples/GettingStarted/Steps/Step01_Running.cs index abc27459db..9f5fca31db 100644 --- a/dotnet/samples/GettingStarted/Steps/Step01_Running.cs +++ b/dotnet/samples/GettingStarted/Steps/Step01_Running.cs @@ -22,11 +22,13 @@ public sealed class Step01_Running(ITestOutputHelper output) : AgentSample(outpu /// Demonstrate the usage of where each invocation is /// a unique interaction with no conversation history between them. /// - [Fact] - public async Task RunWithoutThread() + [Theory] + [InlineData(ChatClientProviders.OpenAI)] + [InlineData(ChatClientProviders.AzureOpenAI)] + public async Task RunWithoutThread(ChatClientProviders provider) { // Get the chat client to use for the agent. - using var chatClient = base.GetOpenAIChatClient(); + using var chatClient = base.GetChatClient(provider); // Define the agent ChatClientAgent agent = @@ -37,12 +39,12 @@ public sealed class Step01_Running(ITestOutputHelper output) : AgentSample(outpu }); // Respond to user input - await InvokeAgentAsync("Fortune favors the bold."); - await InvokeAgentAsync("I came, I saw, I conquered."); - await InvokeAgentAsync("Practice makes perfect."); + await RunAgentAsync("Fortune favors the bold."); + await RunAgentAsync("I came, I saw, I conquered."); + await RunAgentAsync("Practice makes perfect."); // Local function to invoke agent and display the conversation messages. - async Task InvokeAgentAsync(string input) + async Task RunAgentAsync(string input) { this.WriteUserMessage(input); @@ -54,11 +56,13 @@ public sealed class Step01_Running(ITestOutputHelper output) : AgentSample(outpu /// /// Demonstrate the usage of where a conversation history is maintained. /// - [Fact] - public async Task RunWithConversationThread() + [Theory] + [InlineData(ChatClientProviders.OpenAI)] + [InlineData(ChatClientProviders.AzureOpenAI)] + public async Task RunWithConversationThread(ChatClientProviders provider) { // Get the chat client to use for the agent. - using var chatClient = base.GetOpenAIChatClient(); + using var chatClient = base.GetChatClient(provider); // Define the agent ChatClientAgent agent = @@ -72,11 +76,11 @@ public sealed class Step01_Running(ITestOutputHelper output) : AgentSample(outpu AgentThread thread = agent.GetNewThread(); // Respond to user input - await InvokeAgentAsync("Tell me a joke about a pirate."); - await InvokeAgentAsync("Now add some emojis to the joke."); + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); // Local function to invoke agent and display the conversation messages for the thread. - async Task InvokeAgentAsync(string input) + async Task RunAgentAsync(string input) { this.WriteUserMessage(input); @@ -90,11 +94,13 @@ public sealed class Step01_Running(ITestOutputHelper output) : AgentSample(outpu /// Demonstrate the usage of in streaming mode, /// where a conversation is maintained by the . /// - [Fact] - public async Task StreamingRunWithConversationThread() + [Theory] + [InlineData(ChatClientProviders.OpenAI)] + [InlineData(ChatClientProviders.AzureOpenAI)] + public async Task StreamingRunWithConversationThread(ChatClientProviders provider) { // Get the chat client to use for the agent. - using var chatClient = base.GetOpenAIChatClient(); + using var chatClient = base.GetChatClient(provider); // Define the agent ChatClientAgent agent = @@ -108,11 +114,11 @@ public sealed class Step01_Running(ITestOutputHelper output) : AgentSample(outpu AgentThread thread = agent.GetNewThread(); // Respond to user input - await InvokeAgentAsync("Tell me a joke about a pirate."); - await InvokeAgentAsync("Now add some emojis to the joke."); + await RunAgentAsync("Tell me a joke about a pirate."); + await RunAgentAsync("Now add some emojis to the joke."); // Local function to invoke agent and display the conversation messages. - async Task InvokeAgentAsync(string input) + async Task RunAgentAsync(string input) { this.WriteUserMessage(input); diff --git a/dotnet/samples/GettingStarted/Steps/Step02_UsingTools.cs b/dotnet/samples/GettingStarted/Steps/Step02_UsingTools.cs index a653819591..a97299927a 100644 --- a/dotnet/samples/GettingStarted/Steps/Step02_UsingTools.cs +++ b/dotnet/samples/GettingStarted/Steps/Step02_UsingTools.cs @@ -8,11 +8,13 @@ namespace Steps; public sealed class Step02_UsingTools(ITestOutputHelper output) : AgentSample(output) { - [Fact] - public async Task RunningWithTools() + [Theory] + [InlineData(ChatClientProviders.OpenAI)] + [InlineData(ChatClientProviders.AzureOpenAI)] + public async Task RunningWithTools(ChatClientProviders provider) { // Get the chat client to use for the agent. - using var chatClient = base.GetOpenAIChatClient(); + using var chatClient = base.GetChatClient(provider); // Define the agent var menuTools = new MenuTools(); @@ -35,12 +37,12 @@ public sealed class Step02_UsingTools(ITestOutputHelper output) : AgentSample(ou var thread = agent.GetNewThread(); // Respond to user input, invoking functions where appropriate. - await InvokeAgentAsync("Hello"); - await InvokeAgentAsync("What is the special soup and its price?"); - await InvokeAgentAsync("What is the special drink and its price?"); - await InvokeAgentAsync("Thank you"); + await RunAgentAsync("Hello"); + await RunAgentAsync("What is the special soup and its price?"); + await RunAgentAsync("What is the special drink and its price?"); + await RunAgentAsync("Thank you"); - async Task InvokeAgentAsync(string input) + async Task RunAgentAsync(string input) { this.WriteUserMessage(input); var response = await agent.RunAsync(input, thread); @@ -48,11 +50,13 @@ public sealed class Step02_UsingTools(ITestOutputHelper output) : AgentSample(ou } } - [Fact] - public async Task StreamingRunWithTools() + [Theory] + [InlineData(ChatClientProviders.OpenAI)] + [InlineData(ChatClientProviders.AzureOpenAI)] + public async Task StreamingRunWithTools(ChatClientProviders provider) { // Get the chat client to use for the agent. - using var chatClient = base.GetOpenAIChatClient(); + using var chatClient = base.GetChatClient(provider); // Define the agent var menuTools = new MenuTools(); @@ -75,12 +79,12 @@ public sealed class Step02_UsingTools(ITestOutputHelper output) : AgentSample(ou var thread = agent.GetNewThread(); // Respond to user input, invoking functions where appropriate. - await InvokeAgentAsync("Hello"); - await InvokeAgentAsync("What is the special soup and its price?"); - await InvokeAgentAsync("What is the special drink and its price?"); - await InvokeAgentAsync("Thank you"); + await RunAgentAsync("Hello"); + await RunAgentAsync("What is the special soup and its price?"); + await RunAgentAsync("What is the special drink and its price?"); + await RunAgentAsync("Thank you"); - async Task InvokeAgentAsync(string input) + async Task RunAgentAsync(string input) { this.WriteUserMessage(input); await foreach (var update in agent.RunStreamingAsync(input, thread)) diff --git a/dotnet/src/Microsoft.Agents/ChatCompletion/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents/ChatCompletion/ChatClientAgent.cs index 1c521863c8..bb765626eb 100644 --- a/dotnet/src/Microsoft.Agents/ChatCompletion/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents/ChatCompletion/ChatClientAgent.cs @@ -117,10 +117,10 @@ public sealed class ChatClientAgent : Agent AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - Throw.IfNull(messages); + var inputMessages = Throw.IfNull(messages); (ChatClientAgentThread chatClientThread, ChatOptions? chatOptions, List threadMessages) = - await this.PrepareThreadAndMessagesAsync(thread, messages, options, cancellationToken).ConfigureAwait(false); + await this.PrepareThreadAndMessagesAsync(thread, inputMessages, options, cancellationToken).ConfigureAwait(false); int messageCount = threadMessages.Count; var agentName = this.GetAgentName(); @@ -158,7 +158,7 @@ public sealed class ChatClientAgent : Agent this.UpdateThreadWithTypeAndConversationId(chatClientThread, chatResponse.ConversationId); // To avoid inconsistent state we only notify the thread of the input messages if no error occurs after the initial request. - await this.NotifyThreadOfNewMessagesAsync(chatClientThread, messages, cancellationToken).ConfigureAwait(false); + await this.NotifyThreadOfNewMessagesAsync(chatClientThread, inputMessages, cancellationToken).ConfigureAwait(false); await this.NotifyThreadOfNewMessagesAsync(chatClientThread, chatResponseMessages, cancellationToken).ConfigureAwait(false); if (options?.OnIntermediateMessages is not null) @@ -198,28 +198,33 @@ public sealed class ChatClientAgent : Agent } // If both are present, we need to merge them. - // The merge strategy will prioritize the request options over the agent options, // and will fill the blanks with agent options where the request options were not set. - - // Merge only the additional properties from the agent if they are not already set in the request options. - if (requestChatOptions.AdditionalProperties is not null && this._agentOptions.ChatOptions.AdditionalProperties is not null) - { - foreach (var property in this._agentOptions.ChatOptions.AdditionalProperties.Keys) - { - requestChatOptions.AdditionalProperties.TryAdd(property, this._agentOptions.ChatOptions.AdditionalProperties[property]); - } - } - else - { - requestChatOptions.AdditionalProperties ??= this._agentOptions.ChatOptions.AdditionalProperties; - } requestChatOptions.AllowMultipleToolCalls ??= this._agentOptions.ChatOptions.AllowMultipleToolCalls; requestChatOptions.ConversationId ??= this._agentOptions.ChatOptions.ConversationId; requestChatOptions.FrequencyPenalty ??= this._agentOptions.ChatOptions.FrequencyPenalty; requestChatOptions.MaxOutputTokens ??= this._agentOptions.ChatOptions.MaxOutputTokens; requestChatOptions.ModelId ??= this._agentOptions.ChatOptions.ModelId; requestChatOptions.PresencePenalty ??= this._agentOptions.ChatOptions.PresencePenalty; + requestChatOptions.ResponseFormat ??= this._agentOptions.ChatOptions.ResponseFormat; + requestChatOptions.Seed ??= this._agentOptions.ChatOptions.Seed; + requestChatOptions.Temperature ??= this._agentOptions.ChatOptions.Temperature; + requestChatOptions.TopP ??= this._agentOptions.ChatOptions.TopP; + requestChatOptions.TopK ??= this._agentOptions.ChatOptions.TopK; + requestChatOptions.ToolMode ??= this._agentOptions.ChatOptions.ToolMode; + + // Merge only the additional properties from the agent if they are not already set in the request options. + if (requestChatOptions.AdditionalProperties is not null && this._agentOptions.ChatOptions.AdditionalProperties is not null) + { + foreach (var propertyKey in this._agentOptions.ChatOptions.AdditionalProperties.Keys) + { + requestChatOptions.AdditionalProperties.TryAdd(propertyKey, this._agentOptions.ChatOptions.AdditionalProperties[propertyKey]); + } + } + else + { + requestChatOptions.AdditionalProperties ??= this._agentOptions.ChatOptions.AdditionalProperties?.Clone(); + } // Chain the raw representation factory from the request options with the agent's factory if available. if (this._agentOptions.ChatOptions.RawRepresentationFactory is { } agentFactory) @@ -229,41 +234,52 @@ public sealed class ChatClientAgent : Agent : agentFactory; } - requestChatOptions.ResponseFormat ??= this._agentOptions.ChatOptions.ResponseFormat; - requestChatOptions.Seed ??= this._agentOptions.ChatOptions.Seed; - // We concatenate the request stop sequences with the agent's stop sequences when available. if (this._agentOptions.ChatOptions.StopSequences is { Count: not 0 }) { if (requestChatOptions.StopSequences is null || requestChatOptions.StopSequences.Count == 0) { // If the request stop sequences are not set or empty, we use the agent's stop sequences directly. - requestChatOptions.StopSequences = this._agentOptions.ChatOptions.StopSequences.ToArray(); + requestChatOptions.StopSequences = [.. this._agentOptions.ChatOptions.StopSequences]; + } + else if (requestChatOptions.StopSequences is List requestStopSequences) + { + // If the request stop sequences are set, we concatenate them with the agent's stop sequences. + requestStopSequences.AddRange(this._agentOptions.ChatOptions.StopSequences); } else { // If both agent's and request's stop sequences are set, we concatenate them. - requestChatOptions.StopSequences = [.. requestChatOptions.StopSequences, .. this._agentOptions.ChatOptions.StopSequences]; + foreach (string stopSequence in this._agentOptions.ChatOptions.StopSequences) + { + requestChatOptions.StopSequences.Add(stopSequence); + } } } - requestChatOptions.Temperature ??= this._agentOptions.ChatOptions.Temperature; - requestChatOptions.TopP ??= this._agentOptions.ChatOptions.TopP; - requestChatOptions.TopK ??= this._agentOptions.ChatOptions.TopK; - requestChatOptions.ToolMode ??= this._agentOptions.ChatOptions.ToolMode; - // We concatenate the request tools with the agent's tools when available. if (this._agentOptions.ChatOptions.Tools is { Count: not 0 }) { if (requestChatOptions.Tools is not { Count: > 0 }) { - // If the request tools are not set or empty, we use the agent's tools directly. - requestChatOptions.Tools = this._agentOptions.ChatOptions.Tools; + // If the request tools are not set or empty, we use the agent's tools. + requestChatOptions.Tools = [.. this._agentOptions.ChatOptions.Tools]; } else { - // If the both agent's and request's tools are set, we concatenate all tools. - requestChatOptions.Tools = [.. requestChatOptions.Tools, .. this._agentOptions.ChatOptions.Tools]; + if (requestChatOptions.Tools is List requestTools) + { + // If the request tools are set, we concatenate them with the agent's tools. + requestTools.AddRange(this._agentOptions.ChatOptions.Tools); + } + else + { + // If the both agent's and request's tools are set, we concatenate all tools. + foreach (var tool in this._agentOptions.ChatOptions.Tools) + { + requestChatOptions.Tools.Add(tool); + } + } } } diff --git a/dotnet/src/Shared/Samples/TestConfiguration.cs b/dotnet/src/Shared/Samples/TestConfiguration.cs index 945fece430..58c831d079 100644 --- a/dotnet/src/Shared/Samples/TestConfiguration.cs +++ b/dotnet/src/Shared/Samples/TestConfiguration.cs @@ -5,17 +5,42 @@ using Microsoft.Extensions.Configuration; namespace Microsoft.Shared.Samples; +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + /// -/// Provides a centralized configuration management system for accessing application settings. +/// Provides access to application configuration settings. /// public sealed class TestConfiguration { - private readonly IConfigurationRoot _configRoot; - private static TestConfiguration? s_instance; + /// Gets the configuration settings for the OpenAI integration. + public static OpenAIConfig OpenAI => LoadSection(); - private TestConfiguration(IConfigurationRoot configRoot) + /// Gets the configuration settings for the Azure OpenAI integration. + public static AzureOpenAIConfig AzureOpenAI => LoadSection(); + + /// Represents the configuration settings required to interact with the OpenAI service. + public class OpenAIConfig { - this._configRoot = configRoot; + /// Gets or sets the identifier for the chat completion model used in the application. + public string ChatModelId { get; set; } + + /// Gets or sets the API key used for authentication with the OpenAI service. + public string ApiKey { get; set; } + } + + /// + /// Represents the configuration settings required to interact with the Azure OpenAI service. + /// + public class AzureOpenAIConfig + { + /// Gets the URI endpoint used to connect to the service. + public Uri Endpoint { get; set; } + + /// Gets or sets the name of the deployment. + public string DeploymentName { get; set; } + + /// Gets or sets the API key used for authentication with the OpenAI service. + public string? ApiKey { get; set; } } /// @@ -27,15 +52,19 @@ public sealed class TestConfiguration s_instance = new TestConfiguration(configRoot); } + #region Private Members + private readonly IConfigurationRoot _configRoot; + private static TestConfiguration? s_instance; + + private TestConfiguration(IConfigurationRoot configRoot) + { + this._configRoot = configRoot; + } + /// /// Provides access to the configuration root for the application. /// - public static IConfigurationRoot? ConfigurationRoot => s_instance?._configRoot; - - /// - /// Gets the configuration settings for the OpenAI integration. - /// - public static OpenAIConfig OpenAI => LoadSection(); + private static IConfigurationRoot? ConfigurationRoot => s_instance?._configRoot; /// /// Retrieves a configuration section based on the specified key. @@ -43,7 +72,7 @@ public sealed class TestConfiguration /// The key identifying the configuration section to retrieve. Cannot be null or empty. /// The corresponding to the specified key. /// Thrown if the configuration root is not initialized or the specified key does not correspond to a valid section. - public static IConfigurationSection GetSection(string caller) + private static IConfigurationSection GetSection(string caller) { return s_instance?._configRoot.GetSection(caller) ?? throw new InvalidOperationException(caller); @@ -66,16 +95,5 @@ public sealed class TestConfiguration throw new InvalidOperationException(caller); } - /// Represents the configuration settings required to interact with the OpenAI service. - public class OpenAIConfig - { - /// Gets or sets the identifier for the chat completion model used in the application. - public string? ChatModelId { get; set; } - - /// Gets or sets the identifier for the embedding model used in the application. - public string? EmbeddingModelId { get; set; } - - /// Gets or sets the API key used for authentication with the OpenAI service. - public string? ApiKey { get; set; } - } + #endregion }