Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.Net Agents - Refine client provider/factory #10616

Merged
merged 2 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions dotnet/src/Agents/AzureAI/AzureAIAgent.ClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Net.Http;
using Azure.AI.Projects;
using Azure.Core;
using Azure.Core.Pipeline;
using Microsoft.SemanticKernel.Http;

namespace Microsoft.SemanticKernel.Agents.AzureAI;

/// <summary>
/// Provides an <see cref="AIProjectClient"/> for use by <see cref="AzureAIAgent"/>.
/// </summary>
public sealed partial class AzureAIAgent : KernelAgent
{
/// <summary>
/// Produces a <see cref="AIProjectClient"/>.
/// </summary>
/// <param name="connectionString">The Azure AI Foundry project connection string, in the form `endpoint;subscription_id;resource_group_name;project_name`.</param>
/// <param name="credential"> A credential used to authenticate to an Azure Service.</param>
/// <param name="httpClient">A custom <see cref="HttpClient"/> for HTTP requests.</param>
public static AIProjectClient CreateAzureAIClient(
string connectionString,
TokenCredential credential,
HttpClient? httpClient = null)
{
Verify.NotNullOrWhiteSpace(connectionString, nameof(connectionString));
Verify.NotNull(credential, nameof(credential));

AIProjectClientOptions clientOptions = CreateAzureClientOptions(httpClient);

return new AIProjectClient(connectionString, credential, clientOptions);
}

private static AIProjectClientOptions CreateAzureClientOptions(HttpClient? httpClient)
{
AIProjectClientOptions options =
new()
{
Diagnostics = {
ApplicationId = HttpHeaderConstant.Values.UserAgent,
}
};

options.AddPolicy(new SemanticKernelHeadersPolicy(), HttpPipelinePosition.PerCall);

if (httpClient is not null)
{
options.Transport = new HttpClientTransport(httpClient);
// Disable retry policy if and only if a custom HttpClient is provided.
options.RetryPolicy = new RetryPolicy(maxRetries: 0);
}

return options;
}

private class SemanticKernelHeadersPolicy : HttpPipelineSynchronousPolicy
{
public override void OnSendingRequest(HttpMessage message)
{
message.Request.Headers.Add(
HttpHeaderConstant.Names.SemanticKernelVersion,
HttpHeaderConstant.Values.GetAssemblyVersion(typeof(AzureAIAgent)));
}
}
}
2 changes: 1 addition & 1 deletion dotnet/src/Agents/AzureAI/AzureAIAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace Microsoft.SemanticKernel.Agents.AzureAI;
/// <summary>
/// Provides a specialized <see cref="KernelAgent"/> based on an Azure AI agent.
/// </summary>
public sealed class AzureAIAgent : KernelAgent
public sealed partial class AzureAIAgent : KernelAgent
{
/// <summary>
/// Provides tool definitions used when associating a file attachment to an input message:
Expand Down
122 changes: 122 additions & 0 deletions dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.ClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Net.Http;
using System.Threading;
using Azure.AI.OpenAI;
using Azure.Core;
using Microsoft.SemanticKernel.Http;
using OpenAI;

namespace Microsoft.SemanticKernel.Agents.OpenAI;

public sealed partial class OpenAIAssistantAgent : KernelAgent
{
/// <summary>
/// Specifies a key that avoids an exception from OpenAI Client when a custom endpoint is provided without an API key.
/// </summary>
private const string SingleSpaceKey = " ";

/// <summary>
/// Produces an <see cref="AzureOpenAIClient"/>.
/// </summary>
/// <param name="apiKey">The API key.</param>
/// <param name="endpoint">The service endpoint.</param>
/// <param name="httpClient">A custom <see cref="HttpClient"/> for HTTP requests.</param>
public static AzureOpenAIClient CreateAzureOpenAIClient(ApiKeyCredential apiKey, Uri endpoint, HttpClient? httpClient = null)
{
Verify.NotNull(apiKey, nameof(apiKey));
Verify.NotNull(endpoint, nameof(endpoint));

AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(httpClient);

return new AzureOpenAIClient(endpoint, apiKey!, clientOptions);
}

/// <summary>
/// Produces an <see cref="AzureOpenAIClient"/>.
/// </summary>
/// <param name="credential">The credentials.</param>
/// <param name="endpoint">The service endpoint.</param>
/// <param name="httpClient">A custom <see cref="HttpClient"/> for HTTP requests.</param>
public static AzureOpenAIClient CreateAzureOpenAIClient(TokenCredential credential, Uri endpoint, HttpClient? httpClient = null)
{
Verify.NotNull(credential, nameof(credential));
Verify.NotNull(endpoint, nameof(endpoint));

AzureOpenAIClientOptions clientOptions = CreateAzureClientOptions(httpClient);

return new AzureOpenAIClient(endpoint, credential, clientOptions);
}

/// <summary>
/// Produces an <see cref="OpenAIClient"/>.
/// </summary>
/// <param name="endpoint">An optional endpoint.</param>
/// <param name="httpClient">A custom <see cref="HttpClient"/> for HTTP requests.</param>
public static OpenAIClient CreateOpenAIClient(Uri? endpoint = null, HttpClient? httpClient = null)
{
OpenAIClientOptions clientOptions = CreateOpenAIClientOptions(endpoint, httpClient);
return new OpenAIClient(new ApiKeyCredential(SingleSpaceKey), clientOptions);
}

/// <summary>
/// Produces an <see cref="OpenAIClient"/>.
/// </summary>
/// <param name="apiKey">The API key.</param>
/// <param name="endpoint">An optional endpoint.</param>
/// <param name="httpClient">A custom <see cref="HttpClient"/> for HTTP requests.</param>
public static OpenAIClient CreateOpenAIClient(ApiKeyCredential apiKey, Uri? endpoint = null, HttpClient? httpClient = null)
{
OpenAIClientOptions clientOptions = CreateOpenAIClientOptions(endpoint, httpClient);
return new OpenAIClient(apiKey, clientOptions);
}

private static AzureOpenAIClientOptions CreateAzureClientOptions(HttpClient? httpClient)
{
AzureOpenAIClientOptions options = new()
{
UserAgentApplicationId = HttpHeaderConstant.Values.UserAgent
};

ConfigureClientOptions(httpClient, options);

return options;
}

private static OpenAIClientOptions CreateOpenAIClientOptions(Uri? endpoint, HttpClient? httpClient)
{
OpenAIClientOptions options = new()
{
UserAgentApplicationId = HttpHeaderConstant.Values.UserAgent,
Endpoint = endpoint ?? httpClient?.BaseAddress,
};

ConfigureClientOptions(httpClient, options);

return options;
}

private static void ConfigureClientOptions(HttpClient? httpClient, ClientPipelineOptions options)
{
options.AddPolicy(CreateRequestHeaderPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIAssistantAgent))), PipelinePosition.PerCall);

if (httpClient is not null)
{
options.Transport = new HttpClientPipelineTransport(httpClient);
options.RetryPolicy = new ClientRetryPolicy(maxRetries: 0); // Disable retry policy if and only if a custom HttpClient is provided.
options.NetworkTimeout = Timeout.InfiniteTimeSpan; // Disable default timeout
}
}

private static GenericActionPipelinePolicy CreateRequestHeaderPolicy(string headerName, string headerValue)
=>
new((message) =>
{
if (message?.Request?.Headers?.TryGetValue(headerName, out string? _) == false)
{
message.Request.Headers.Set(headerName, headerValue);
}
});
}
2 changes: 1 addition & 1 deletion dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Microsoft.SemanticKernel.Agents.OpenAI;
/// <summary>
/// Represents a <see cref="KernelAgent"/> specialization based on Open AI Assistant / GPT.
/// </summary>
public sealed class OpenAIAssistantAgent : KernelAgent
public sealed partial class OpenAIAssistantAgent : KernelAgent
{
/// <summary>
/// The metadata key that identifies code-interpreter content.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ public abstract class BaseAssistantTest : BaseAgentsTest<OpenAIClient>
{
protected BaseAssistantTest(ITestOutputHelper output) : base(output)
{
var clientProvider =
this.Client =
this.UseOpenAIConfig ?
OpenAIClientProvider.ForOpenAI(new ApiKeyCredential(this.ApiKey ?? throw new ConfigurationNotFoundException("OpenAI:ApiKey"))) :
OpenAIAssistantAgent.CreateOpenAIClient(new ApiKeyCredential(this.ApiKey ?? throw new ConfigurationNotFoundException("OpenAI:ApiKey"))) :
!string.IsNullOrWhiteSpace(this.ApiKey) ?
OpenAIClientProvider.ForAzureOpenAI(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) :
OpenAIClientProvider.ForAzureOpenAI(new AzureCliCredential(), new Uri(this.Endpoint!));
OpenAIAssistantAgent.CreateAzureOpenAIClient(new ApiKeyCredential(this.ApiKey), new Uri(this.Endpoint!)) :
OpenAIAssistantAgent.CreateAzureOpenAIClient(new AzureCliCredential(), new Uri(this.Endpoint!));

this.Client = clientProvider.Client;
this.AssistantClient = clientProvider.AssistantClient;
this.AssistantClient = this.Client.GetAssistantClient();
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ public abstract class BaseAzureAgentTest : BaseAgentsTest<AIProjectClient>
{
protected BaseAzureAgentTest(ITestOutputHelper output) : base(output)
{
var clientProvider = AzureAIClientProvider.FromConnectionString(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());

this.Client = clientProvider.Client;
this.AgentsClient = clientProvider.AgentsClient;
this.Client = AzureAIAgent.CreateAzureAIClient(TestConfiguration.AzureAI.ConnectionString, new AzureCliCredential());
this.AgentsClient = this.Client.GetAgentsClient();
}

/// <inheritdoc/>
Expand Down
Loading