Skip to content

Commit

Permalink
Merge branch 'main' into users/markwallace/10571
Browse files Browse the repository at this point in the history
  • Loading branch information
crickman authored Feb 20, 2025
2 parents 05418eb + 82aafd3 commit 87c849d
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 12 deletions.
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

0 comments on commit 87c849d

Please sign in to comment.