-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
.Net Agents - Refine client provider/factory (#10616)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Move away from the "client-provider" pattern and make client factory more discoverable. Fixes: #10582 ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Expose ability to create an SDK client as a static factory methods on the agent. This is more discoverable than poking around for the client-provider and aligns with the Python approach. - Organized factory code in a separate file from the core agent abstractions. - Updated samples ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
- Loading branch information
Showing
6 changed files
with
196 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.ClientFactory.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters