Skip to content

Commit

Permalink
.Net: Add new Semantic-Kernel-Version header to all HTTP requests (#5048
Browse files Browse the repository at this point in the history
)

### Motivation and Context
Closes #5037 

<!-- 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.
-->

### Description

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [ ] The code builds clean without any errors or warnings
- [ ] 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
- [ ] All unit tests pass, and I have added new tests where possible
- [ ] I didn't break anyone 😄
  • Loading branch information
markwallace-microsoft committed Feb 19, 2024
1 parent 052c381 commit 9a8c0be
Show file tree
Hide file tree
Showing 23 changed files with 124 additions and 37 deletions.
4 changes: 2 additions & 2 deletions dotnet/SK-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http", "Http", "{1C19D805-3
src\InternalUtilities\src\Http\HttpClientExtensions.cs = src\InternalUtilities\src\Http\HttpClientExtensions.cs
src\InternalUtilities\src\Http\HttpClientProvider.cs = src\InternalUtilities\src\Http\HttpClientProvider.cs
src\InternalUtilities\src\Http\HttpContentExtensions.cs = src\InternalUtilities\src\Http\HttpContentExtensions.cs
src\InternalUtilities\src\Http\HttpHeaderValues.cs = src\InternalUtilities\src\Http\HttpHeaderValues.cs
src\InternalUtilities\src\Http\HttpHeaderConstant.cs = src\InternalUtilities\src\Http\HttpHeaderConstant.cs
src\InternalUtilities\src\Http\HttpRequest.cs = src\InternalUtilities\src\Http\HttpRequest.cs
EndProjectSection
EndProject
Expand Down Expand Up @@ -219,7 +219,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linq", "Linq", "{607DD6FA-F
src\InternalUtilities\test\Linq\AsyncEnumerable.cs = src\InternalUtilities\test\Linq\AsyncEnumerable.cs
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connectors.AzureAISearch.UnitTests", "src\Connectors\Connectors.AzureAISearch.UnitTests\Connectors.AzureAISearch.UnitTests.csproj", "{BCDD5B96-CCC3-46B9-8217-89CD5885F6A2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.AzureAISearch.UnitTests", "src\Connectors\Connectors.AzureAISearch.UnitTests\Connectors.AzureAISearch.UnitTests.csproj", "{BCDD5B96-CCC3-46B9-8217-89CD5885F6A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public async Task RunAsync()

// Create an HttpClient and include your custom header(s)
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("x-my-custom-header", "My custom value");
httpClient.DefaultRequestHeaders.Add("My-Custom-Header", "My Custom Value");

// Configure OpenAIClient to use the customized HttpClient
var clientOptions = new OpenAIClientOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ private async Task<IList<ReadOnlyMemory<float>>> ExecuteEmbeddingRequestAsync(IL

using var httpRequestMessage = HttpRequest.CreatePostRequest(this.GetRequestUri(), embeddingRequest);

httpRequestMessage.Headers.Add("User-Agent", HttpHeaderValues.UserAgent);
httpRequestMessage.Headers.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
httpRequestMessage.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(HuggingFaceTextEmbeddingGenerationService)));

var response = await this._httpClient.SendWithSuccessCheckAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
var body = await response.Content.ReadAsStringWithExceptionMappingAsync().ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ private async Task<IReadOnlyList<TextContent>> InternalGetTextContentsAsync(stri

using var httpRequestMessage = HttpRequest.CreatePostRequest(this.GetRequestUri(), completionRequest);

httpRequestMessage.Headers.Add("User-Agent", HttpHeaderValues.UserAgent);
httpRequestMessage.Headers.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
httpRequestMessage.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(HuggingFaceTextGenerationService)));
if (!string.IsNullOrEmpty(this._apiKey))
{
httpRequestMessage.Headers.Add("Authorization", $"Bearer {this._apiKey}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ private static SearchClientOptions GetClientOptions()
{
Diagnostics =
{
ApplicationId = HttpHeaderValues.UserAgent,
ApplicationId = HttpHeaderConstant.Values.UserAgent,
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ protected virtual void Dispose(bool disposing)

private static ClientRequestProperties GetClientRequestProperties() => new()
{
Application = HttpHeaderValues.UserAgent,
Application = HttpHeaderConstant.Values.UserAgent,
};

private bool _searchInitialized;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ internal void AddAttribute(string key, string? value)
HttpRequestMessage request,
CancellationToken cancellationToken)
{
request.Headers.Add("User-Agent", HttpHeaderValues.UserAgent);
request.Headers.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
request.Headers.Add("Api-Key", this._apiKey);
request.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(AzureOpenAITextToAudioClient)));

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using System.Threading.Tasks;
using Azure;
using Azure.AI.OpenAI;
using Azure.Core;
using Azure.Core.Pipeline;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
Expand Down Expand Up @@ -696,8 +697,9 @@ internal static OpenAIClientOptions GetOpenAIClientOptions(HttpClient? httpClien
{
OpenAIClientOptions options = new()
{
Diagnostics = { ApplicationId = HttpHeaderValues.UserAgent }
Diagnostics = { ApplicationId = HttpHeaderConstant.Values.UserAgent }
};
options.AddPolicy(new AddHeaderRequestPolicy(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(ClientCore))), HttpPipelinePosition.PerCall);

if (httpClient is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ internal void AddAttribute(string key, string? value)
HttpRequestMessage request,
CancellationToken cancellationToken)
{
request.Headers.Add("User-Agent", HttpHeaderValues.UserAgent);
request.Headers.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
request.Headers.Add("Authorization", $"Bearer {this._apiKey}");
request.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAITextToAudioClient)));

if (!string.IsNullOrWhiteSpace(this._organization))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,9 @@ private void ValidateConfig(AzureOpenAIChatCompletionWithDataConfig config)
HttpRequestMessage request,
CancellationToken cancellationToken = default)
{
request.Headers.Add("User-Agent", HttpHeaderValues.UserAgent);
request.Headers.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
request.Headers.Add("Api-Key", this._config.CompletionApiKey);
request.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(AzureOpenAIChatCompletionWithDataService)));

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ internal async Task<HttpResponseMessage> ExecuteRequestAsync(string url, HttpMet
request.Content = content;
}

request.Headers.Add("User-Agent", HttpHeaderValues.UserAgent);
request.Headers.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
request.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAITextToImageClientCore)));

this.RequestCreated?.Invoke(this, request);

var response = await this._httpClient.SendWithSuccessCheckAsync(request, cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,9 @@ private async Task<TModel> ExecutePostRequestAsync<TModel>(string url, HttpConte

private void AddRequestHeaders(HttpRequestMessage request)
{
request.Headers.Add("User-Agent", HttpHeaderValues.UserAgent);
request.Headers.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
request.Headers.Add("Authorization", $"Bearer {this._apiKey}");
request.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(OpenAIFileService)));

if (!string.IsNullOrEmpty(this._organization))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ private static OpenAIClientOptions GetClientOptions(HttpClient? httpClient, stri

var options = new OpenAIClientOptions(version)
{
Diagnostics = { ApplicationId = HttpHeaderValues.UserAgent }
Diagnostics = { ApplicationId = HttpHeaderConstant.Values.UserAgent }
};

if (httpClient != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public async Task ItCanCreateCollectionAsync()
.Verify(client => client.ExecuteControlCommandAsync(
DatabaseName,
It.Is<string>(s => s.StartsWith($".create table {CollectionName}")),
It.Is<ClientRequestProperties>(crp => string.Equals(crp.Application, HttpHeaderValues.UserAgent, StringComparison.Ordinal))
It.Is<ClientRequestProperties>(crp => string.Equals(crp.Application, HttpHeaderConstant.Values.UserAgent, StringComparison.Ordinal))
), Times.Once());
}

Expand All @@ -87,7 +87,7 @@ public async Task ItCanDeleteCollectionAsync()
.Verify(client => client.ExecuteControlCommandAsync(
DatabaseName,
It.Is<string>(s => s.StartsWith($".drop table {CollectionName}")),
It.Is<ClientRequestProperties>(crp => string.Equals(crp.Application, HttpHeaderValues.UserAgent, StringComparison.Ordinal))
It.Is<ClientRequestProperties>(crp => string.Equals(crp.Application, HttpHeaderConstant.Values.UserAgent, StringComparison.Ordinal))
), Times.Once());
}

Expand Down
2 changes: 1 addition & 1 deletion dotnet/src/Functions/Functions.OpenApi/DocumentLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal static class DocumentLoader
CancellationToken cancellationToken)
{
using var request = new HttpRequestMessage(HttpMethod.Get, uri.ToString());
request.Headers.UserAgent.Add(ProductInfoHeaderValue.Parse(userAgent ?? HttpHeaderValues.UserAgent));
request.Headers.UserAgent.Add(ProductInfoHeaderValue.Parse(userAgent ?? HttpHeaderConstant.Values.UserAgent));

if (authCallback is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public class OpenApiFunctionExecutionParameters
this.HttpClient = httpClient;
this.AuthCallback = authCallback;
this.ServerUrlOverride = serverUrlOverride;
this.UserAgent = userAgent ?? HttpHeaderValues.UserAgent;
this.UserAgent = userAgent ?? HttpHeaderConstant.Values.UserAgent;
this.IgnoreNonCompliantErrors = ignoreNonCompliantErrors;
this.EnableDynamicPayload = enableDynamicOperationPayload;
this.EnablePayloadNamespacing = enablePayloadNamespacing;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ internal sealed class RestApiOperationRunner
bool enablePayloadNamespacing = false)
{
this._httpClient = httpClient;
this._userAgent = userAgent ?? HttpHeaderValues.UserAgent;
this._userAgent = userAgent ?? HttpHeaderConstant.Values.UserAgent;
this._enableDynamicPayload = enableDynamicPayload;
this._enablePayloadNamespacing = enablePayloadNamespacing;

Expand Down Expand Up @@ -162,7 +162,8 @@ internal sealed class RestApiOperationRunner

requestMessage.Headers.Add("User-Agent", !string.IsNullOrWhiteSpace(this._userAgent)
? this._userAgent
: HttpHeaderValues.UserAgent);
: HttpHeaderConstant.Values.UserAgent);
requestMessage.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(RestApiOperationRunner)));

if (headers != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public async Task ItShouldAddHeadersToHttpRequestAsync()

// Assert - 13 headers: 12 from the test and the User-Agent added internally
Assert.NotNull(this._httpMessageHandlerStub.RequestHeaders);
Assert.Equal(13, this._httpMessageHandlerStub.RequestHeaders.Count());
Assert.Equal(14, this._httpMessageHandlerStub.RequestHeaders.Count());

Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "User-Agent" && h.Value.Contains("fake-agent"));
Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "X-HS-1" && h.Value.Contains("fake-header-value"));
Expand All @@ -241,6 +241,7 @@ public async Task ItShouldAddHeadersToHttpRequestAsync()
Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "X-HD-1" && h.Value.Contains("2023-12-06T11:53:36Z"));
Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "X-HD-2" && h.Value.Contains("2023-12-06T11:53:36Z"));
Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "X-HD-3" && h.Value.Contains("2023-12-06T11:53:36-02:00"));
Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "Semantic-Kernel-Version");
}

[Fact]
Expand Down Expand Up @@ -279,10 +280,11 @@ public async Task ItShouldAddUserAgentHeaderToHttpRequestIfConfiguredAsync()

// Assert
Assert.NotNull(this._httpMessageHandlerStub.RequestHeaders);
Assert.Equal(2, this._httpMessageHandlerStub.RequestHeaders.Count());
Assert.Equal(3, this._httpMessageHandlerStub.RequestHeaders.Count());

Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "fake-header" && h.Value.Contains("fake-header-value"));
Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "User-Agent" && h.Value.Contains("fake-user-agent"));
Assert.Contains(this._httpMessageHandlerStub.RequestHeaders, h => h.Key == "Semantic-Kernel-Version");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -480,6 +482,39 @@ public async Task ChatSystemPromptIsNotIgnoredAsync()
Assert.Contains("I don't know", result.ToString(), StringComparison.OrdinalIgnoreCase);
}

[Fact]
public async Task SemanticKernelVersionHeaderIsSentAsync()
{
// Arrange
var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get<AzureOpenAIConfiguration>();
Assert.NotNull(azureOpenAIConfiguration);
Assert.NotNull(azureOpenAIConfiguration.ChatDeploymentName);
Assert.NotNull(azureOpenAIConfiguration.ApiKey);
Assert.NotNull(azureOpenAIConfiguration.Endpoint);
Assert.NotNull(azureOpenAIConfiguration.ServiceId);

using var defaultHandler = new HttpClientHandler();
using var httpHeaderHandler = new HttpHeaderHandler(defaultHandler);
using var httpClient = new HttpClient(httpHeaderHandler);
this._kernelBuilder.Services.AddSingleton<ILoggerFactory>(this._logger);
var builder = this._kernelBuilder;
builder.AddAzureOpenAIChatCompletion(
deploymentName: azureOpenAIConfiguration.ChatDeploymentName,
modelId: azureOpenAIConfiguration.ChatModelId,
endpoint: azureOpenAIConfiguration.Endpoint,
apiKey: azureOpenAIConfiguration.ApiKey,
serviceId: azureOpenAIConfiguration.ServiceId,
httpClient: httpClient);
Kernel target = builder.Build();

// Act
var result = await target.InvokePromptAsync("Where is the most famous fish market in Seattle, Washington, USA?");

// Assert
Assert.NotNull(httpHeaderHandler.RequestHeaders);
Assert.True(httpHeaderHandler.RequestHeaders.TryGetValues("Semantic-Kernel-Version", out var values));
}

#region internals

private readonly XunitLogger<Kernel> _logger;
Expand Down Expand Up @@ -573,5 +608,21 @@ private void ConfigureAzureOpenAIChatAsText(IKernelBuilder kernelBuilder)
serviceId: azureOpenAIConfiguration.ServiceId);
}

private sealed class HttpHeaderHandler : DelegatingHandler
{
public System.Net.Http.Headers.HttpRequestHeaders? RequestHeaders { get; private set; }

public HttpHeaderHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.RequestHeaders = request.Headers;
return await base.SendAsync(request, cancellationToken);
}
}

#endregion
}
34 changes: 34 additions & 0 deletions dotnet/src/InternalUtilities/src/Http/HttpHeaderConstant.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.SemanticKernel.Http;

/// <summary>Provides HTTP header names and values for common purposes.</summary>
[ExcludeFromCodeCoverage]
internal static class HttpHeaderConstant
{
public static class Names
{
/// <summary>HTTP header name to use to include the Semantic Kernel package version in all HTTP requests issued by Semantic Kernel.</summary>
public static string SemanticKernelVersion => "Semantic-Kernel-Version";
}

public static class Values
{
/// <summary>User agent string to use for all HTTP requests issued by Semantic Kernel.</summary>
public static string UserAgent => "Semantic-Kernel";

/// <summary>
/// Gets the version of the <see cref="System.Reflection.Assembly"/> in which the specific type is declared.
/// </summary>
/// <param name="type">Type for which the assembly version is returned.</param>
public static string GetAssemblyVersion(Type type)
{
#pragma warning disable CS8602 // Dereference of a possibly null reference. Impacts Milvus connector package because it targets net6.0 and netstandard2.0
return type.Assembly.GetName().Version.ToString();
#pragma warning restore CS8602 // Dereference of a possibly null reference.
}
}
}
13 changes: 0 additions & 13 deletions dotnet/src/InternalUtilities/src/Http/HttpHeaderValues.cs

This file was deleted.

3 changes: 2 additions & 1 deletion dotnet/src/Plugins/Plugins.Core/HttpPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ public HttpPlugin() : this(null)
private async Task<string> SendRequestAsync(string uri, HttpMethod method, HttpContent? requestContent, CancellationToken cancellationToken)
{
using var request = new HttpRequestMessage(method, uri) { Content = requestContent };
request.Headers.Add("User-Agent", HttpHeaderValues.UserAgent);
request.Headers.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
request.Headers.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(HttpPlugin)));
using var response = await this._client.SendWithSuccessCheckAsync(request, cancellationToken).ConfigureAwait(false);
return await response.Content.ReadAsStringWithExceptionMappingAsync().ConfigureAwait(false);
}
Expand Down
3 changes: 2 additions & 1 deletion dotnet/src/Plugins/Plugins.Web/Bing/BingConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public BingConnector(string apiKey, HttpClient httpClient, ILoggerFactory? logge
this._apiKey = apiKey;
this._logger = loggerFactory?.CreateLogger(typeof(BingConnector)) ?? NullLogger.Instance;
this._httpClient = httpClient;
this._httpClient.DefaultRequestHeaders.Add("User-Agent", HttpHeaderValues.UserAgent);
this._httpClient.DefaultRequestHeaders.Add("User-Agent", HttpHeaderConstant.Values.UserAgent);
this._httpClient.DefaultRequestHeaders.Add(HttpHeaderConstant.Names.SemanticKernelVersion, HttpHeaderConstant.Values.GetAssemblyVersion(typeof(BingConnector)));
}

/// <inheritdoc/>
Expand Down

0 comments on commit 9a8c0be

Please sign in to comment.