.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
This commit is contained in:
Roger Barreto
2025-06-18 12:00:38 +00:00
committed by GitHub
parent e6bfc51367
commit 14f3811648
10 changed files with 235 additions and 100 deletions
@@ -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<ChatMessage> 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<string> 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<AITool> 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);
}
}
}
}
+42 -24
View File
@@ -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.
/// <summary>
/// Provides a centralized configuration management system for accessing application settings.
/// Provides access to application configuration settings.
/// </summary>
public sealed class TestConfiguration
{
private readonly IConfigurationRoot _configRoot;
private static TestConfiguration? s_instance;
/// <summary>Gets the configuration settings for the OpenAI integration.</summary>
public static OpenAIConfig OpenAI => LoadSection<OpenAIConfig>();
private TestConfiguration(IConfigurationRoot configRoot)
/// <summary>Gets the configuration settings for the Azure OpenAI integration.</summary>
public static AzureOpenAIConfig AzureOpenAI => LoadSection<AzureOpenAIConfig>();
/// <summary>Represents the configuration settings required to interact with the OpenAI service.</summary>
public class OpenAIConfig
{
this._configRoot = configRoot;
/// <summary>Gets or sets the identifier for the chat completion model used in the application.</summary>
public string ChatModelId { get; set; }
/// <summary>Gets or sets the API key used for authentication with the OpenAI service.</summary>
public string ApiKey { get; set; }
}
/// <summary>
/// Represents the configuration settings required to interact with the Azure OpenAI service.
/// </summary>
public class AzureOpenAIConfig
{
/// <summary>Gets the URI endpoint used to connect to the service.</summary>
public Uri Endpoint { get; set; }
/// <summary>Gets or sets the name of the deployment.</summary>
public string DeploymentName { get; set; }
/// <summary>Gets or sets the API key used for authentication with the OpenAI service.</summary>
public string? ApiKey { get; set; }
}
/// <summary>
@@ -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;
}
/// <summary>
/// Provides access to the configuration root for the application.
/// </summary>
public static IConfigurationRoot? ConfigurationRoot => s_instance?._configRoot;
/// <summary>
/// Gets the configuration settings for the OpenAI integration.
/// </summary>
public static OpenAIConfig OpenAI => LoadSection<OpenAIConfig>();
private static IConfigurationRoot? ConfigurationRoot => s_instance?._configRoot;
/// <summary>
/// Retrieves a configuration section based on the specified key.
@@ -43,7 +72,7 @@ public sealed class TestConfiguration
/// <param name="caller">The key identifying the configuration section to retrieve. Cannot be null or empty.</param>
/// <returns>The <see cref="IConfigurationSection"/> corresponding to the specified key.</returns>
/// <exception cref="InvalidOperationException">Thrown if the configuration root is not initialized or the specified key does not correspond to a valid section.</exception>
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);
}
/// <summary>Represents the configuration settings required to interact with the OpenAI service.</summary>
public class OpenAIConfig
{
/// <summary>Gets or sets the identifier for the chat completion model used in the application.</summary>
public string? ChatModelId { get; set; }
/// <summary>Gets or sets the identifier for the embedding model used in the application.</summary>
public string? EmbeddingModelId { get; set; }
/// <summary>Gets or sets the API key used for authentication with the OpenAI service.</summary>
public string? ApiKey { get; set; }
}
#endregion
}