Skip to content

Commit 3434190

Browse files
iangithubrogerbarretowestey-m
authored
.Net: Fix #13183: .NET — Kernel.AddOpenAIChatClient throws an error when us… (#13198)
### Motivation and Context 1. Why is this change required? When using AddOpenAIChatClient with a custom endpoint parameter but without providing a custom httpClient, the code would create an HttpClient without setting its BaseAddress property. This mismatch between the HttpClient configuration and the OpenAIClientOptions.Endpoint setting causes SSL/TLS handshake failures. 2. What problem does it solve? This PR fixes the SSL connection error (System.ClientModel.ClientResultException: The SSL connection could not be established) that occurs when users call: ``` var kernel = Kernel.CreateBuilder() .AddOpenAIChatClient( modelId: "model-name", apiKey: "api-key", endpoint: new Uri("https://custom-endpoint.com") ) .Build(); ``` 3. What scenario does it contribute to? This enables users to easily connect to OpenAI-compatible endpoints (such as Azure OpenAI, local LLM servers, or other OpenAI-compatible APIs) without having to manually create and configure an HttpClient instance. Fixes SSL connection failures when using custom endpoints with the default HttpClient. 4. Related Issue: #13183 ### Description Changes made: Fixed invalid GetOpenAIClientOptions call in the first overload (lines 40-77): Removed the unused GetOpenAIClientOptions call at lines 56-59 Added missing endpoint and orgId parameters to the GetOpenAIClientOptions call used in OpenAIClient construction Fixed SSL issue in the third overload with custom endpoint (lines 125-170): Added logic to ensure the HttpClient has the correct BaseAddress when using a custom endpoint When no custom httpClient is provided and the default client has no BaseAddress, creates a new HttpClient with BaseAddress set to the provided endpoint This ensures consistency between the HttpClient.BaseAddress and OpenAIClientOptions.Endpoint, preventing SSL certificate validation failures Verified AddOpenAIEmbeddingGenerator methods: Confirmed both overloads are correctly implemented with no similar issues The root cause was that HttpClientPipelineTransport uses the HttpClient for making requests, but when the HttpClient.BaseAddress is null and the endpoint is only set in OpenAIClientOptions.Endpoint, the SSL/TLS handshake fails due to hostname mismatch during certificate validation. The fix ensures that when a custom endpoint is provided, the HttpClient is properly configured with the matching BaseAddress, allowing SSL/TLS to validate the certificate correctly. ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [V ] The code builds clean without any errors or warnings - [ V] 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 - [V ] All unit tests pass, and I have added new tests where possible - [V ] I didn't break anyone 😄 --------- Co-authored-by: Roger Barreto <[email protected]> Co-authored-by: westey <[email protected]>
1 parent cf9a5f2 commit 3434190

File tree

1 file changed

+29
-7
lines changed

1 file changed

+29
-7
lines changed

dotnet/src/Connectors/Connectors.OpenAI/Extensions/OpenAIServiceCollectionExtensions.DependencyInjection.cs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,12 @@ IChatClient Factory(IServiceProvider serviceProvider, object? _)
5353
{
5454
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
5555

56-
ClientCore.GetOpenAIClientOptions(
57-
endpoint: null,
58-
orgId: orgId,
59-
httpClient: HttpClientProvider.GetHttpClient(httpClient, serviceProvider));
60-
6156
var builder = new OpenAIClient(
6257
credential: new ApiKeyCredential(apiKey ?? SingleSpace),
63-
options: ClientCore.GetOpenAIClientOptions(HttpClientProvider.GetHttpClient(httpClient, serviceProvider)))
58+
options: ClientCore.GetOpenAIClientOptions(
59+
httpClient: HttpClientProvider.GetHttpClient(httpClient, serviceProvider),
60+
endpoint: null,
61+
orgId: orgId))
6462
.GetChatClient(modelId)
6563
.AsIChatClient()
6664
.AsBuilder()
@@ -153,10 +151,34 @@ IChatClient Factory(IServiceProvider serviceProvider, object? _)
153151
{
154152
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
155153

154+
// Get or create HttpClient with proper BaseAddress for the endpoint
155+
HttpClient innerHttpClient;
156+
if (httpClient is not null)
157+
{
158+
innerHttpClient = httpClient;
159+
}
160+
else
161+
{
162+
var defaultClient = HttpClientProvider.GetHttpClient(serviceProvider);
163+
// If using default client and it doesn't have BaseAddress set or BaseAddress doesn't match the endpoint, create one with the endpoint
164+
if (defaultClient.BaseAddress is null || defaultClient.BaseAddress != endpoint)
165+
{
166+
Verify.NotNull(endpoint);
167+
168+
// A new one needs to be created as we can't cross boundaries and modify an existing client
169+
innerHttpClient = HttpClientProvider.GetHttpClient();
170+
innerHttpClient.BaseAddress = endpoint;
171+
}
172+
else
173+
{
174+
innerHttpClient = defaultClient;
175+
}
176+
}
177+
156178
var builder = new OpenAIClient(
157179
credential: new ApiKeyCredential(apiKey ?? SingleSpace),
158180
options: ClientCore.GetOpenAIClientOptions(
159-
httpClient: HttpClientProvider.GetHttpClient(httpClient, serviceProvider),
181+
httpClient: innerHttpClient,
160182
endpoint: endpoint,
161183
orgId: orgId))
162184
.GetChatClient(modelId)

0 commit comments

Comments
 (0)