Skip to content

Commit

Permalink
.Net Hugging Face HttpClient Connector Update (#5075)
Browse files Browse the repository at this point in the history
### Motivation and Context

Resolves Partially #4947 

Update Hugging Face connector segregating the responsibilities of the
TextGenerationService, TextEmbeddingService, HttpClient, JsonParsing,
EndPointResolving.

### Description

This enables paving the way to support additional Hugging Face
modalities

---------

Co-authored-by: Krzysztof Kasprowicz <60486987+Krzysztof318@users.noreply.github.com>
  • Loading branch information
RogerBarreto and Krzysztof318 committed Feb 23, 2024
1 parent f9771c6 commit 316ff5d
Show file tree
Hide file tree
Showing 36 changed files with 3,954 additions and 319 deletions.
9 changes: 9 additions & 0 deletions dotnet/SK-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linq", "Linq", "{607DD6FA-F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.AzureAISearch.UnitTests", "src\Connectors\Connectors.AzureAISearch.UnitTests\Connectors.AzureAISearch.UnitTests.csproj", "{BCDD5B96-CCC3-46B9-8217-89CD5885F6A2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.HuggingFace.UnitTests", "src\Connectors\Connectors.HuggingFace.UnitTests\Connectors.HuggingFace.UnitTests.csproj", "{1F96837A-61EC-4C8F-904A-07BEBD05FDEE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -508,6 +510,12 @@ Global
{BCDD5B96-CCC3-46B9-8217-89CD5885F6A2}.Publish|Any CPU.Build.0 = Debug|Any CPU
{BCDD5B96-CCC3-46B9-8217-89CD5885F6A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCDD5B96-CCC3-46B9-8217-89CD5885F6A2}.Release|Any CPU.Build.0 = Release|Any CPU
{1F96837A-61EC-4C8F-904A-07BEBD05FDEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F96837A-61EC-4C8F-904A-07BEBD05FDEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F96837A-61EC-4C8F-904A-07BEBD05FDEE}.Publish|Any CPU.ActiveCfg = Debug|Any CPU
{1F96837A-61EC-4C8F-904A-07BEBD05FDEE}.Publish|Any CPU.Build.0 = Debug|Any CPU
{1F96837A-61EC-4C8F-904A-07BEBD05FDEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F96837A-61EC-4C8F-904A-07BEBD05FDEE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -582,6 +590,7 @@ Global
{EB2C141A-AE5F-4080-8790-13EB16323CEF} = {958AD708-F048-4FAF-94ED-D2F2B92748B9}
{607DD6FA-FA0D-45E6-80BA-22A373609E89} = {5C246969-D794-4EC3-8E8F-F90D4D166420}
{BCDD5B96-CCC3-46B9-8217-89CD5885F6A2} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C}
{1F96837A-61EC-4C8F-904A-07BEBD05FDEE} = {1B4CBDE0-10C2-4E7D-9CD0-FE7586C96ED1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83}
Expand Down
23 changes: 23 additions & 0 deletions dotnet/samples/KernelSyntaxExamples/Example20_HuggingFace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Embeddings;
using xRetry;
using Xunit;
using Xunit.Abstractions;

#pragma warning disable CA1861 // Avoid constant arrays as arguments

namespace Examples;

// The following example shows how to use Semantic Kernel with HuggingFace API.
Expand Down Expand Up @@ -32,6 +36,25 @@ public async Task RunInferenceApiExampleAsync()
WriteLine(result.GetValue<string>());
}

[RetryFact(typeof(HttpOperationException))]
public async Task RunInferenceApiEmbeddingAsync()
{
this.WriteLine("\n======= Hugging Face Inference API - Embedding Example ========\n");

Kernel kernel = Kernel.CreateBuilder()
.AddHuggingFaceTextEmbeddingGeneration(
model: TestConfiguration.HuggingFace.EmbeddingModelId,
apiKey: TestConfiguration.HuggingFace.ApiKey)
.Build();

var embeddingGenerator = kernel.GetRequiredService<ITextEmbeddingGenerationService>();

// Generate embeddings for each chunk.
var embeddings = await embeddingGenerator.GenerateEmbeddingsAsync(new[] { "John: Hello, how are you?\nRoger: Hey, I'm Roger!" });

this.WriteLine($"Generated {embeddings.Count} embeddings for the provided text");
}

/// <summary>
/// This example uses HuggingFace Llama 2 model and local HTTP server from Semantic Kernel repository.
/// How to setup local HTTP server: <see href="https://github.com/microsoft/semantic-kernel/blob/main/samples/apps/hugging-face-http-server/README.md"/>.
Expand Down
1 change: 1 addition & 0 deletions dotnet/samples/KernelSyntaxExamples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ dotnet user-secrets set "KeyVault:TenantId" "..."
dotnet user-secrets set "HuggingFace:ApiKey" "..."
dotnet user-secrets set "HuggingFace:ModelId" "..."
dotnet user-secrets set "HuggingFace:EmbeddingModelId" "facebook/bart-base"
dotnet user-secrets set "Pinecone:ApiKey" "..."
dotnet user-secrets set "Pinecone:Environment" "..."
Expand Down
1 change: 1 addition & 0 deletions dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public class HuggingFaceConfig
{
public string ApiKey { get; set; }
public string ModelId { get; set; }
public string EmbeddingModelId { get; set; }
}

public class PineconeConfig
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Suppressing errors for Test projects under dotnet folder
[*.cs]
dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave
dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member
dotnet_diagnostic.IDE1006.severity = warning # Naming rule violations

resharper_convert_constructor_to_member_initializers_highlighting = false # Disable highlighting for "Convert constructor to member initializers" quick-fix
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>SemanticKernel.Connectors.HuggingFace.UnitTests</AssemblyName>
<RootNamespace>SemanticKernel.Connectors.HuggingFace.UnitTests</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>12</LangVersion>
<RollForward>LatestMajor</RollForward>
<IsTestProject>true</IsTestProject>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<IsPackable>false</IsPackable>
<NoWarn>CA2007,CA1806,CA1869,CA1861,IDE0300,VSTHRD111,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0005,SKEXP0010,SKEXP0011,SKEXP0012,SKEXP0013,SKEXP0014,SKEXP0015,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0052</NoWarn>
</PropertyGroup>

<ItemGroup>
<!-- Use newest available compiler to permit LangVersion 12. -->
<!-- This can be removed once we no longer target the .NET 6 SDK in CI. -->
<PackageReference Include="Microsoft.Net.Compilers.Toolset" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Numerics.Tensors" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(RepoRoot)/dotnet/src/InternalUtilities/test/AssertExtensions.cs" Link="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\SemanticKernel.Core\SemanticKernel.Core.csproj" />
<ProjectReference Include="..\Connectors.HuggingFace\Connectors.HuggingFace.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="TestData\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="OpenAI\TestData\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.HuggingFace;
using Xunit;

namespace SemanticKernel.Connectors.HuggingFace.UnitTests.Core;

public class HuggingFacePromptExecutionSettingsTests
{
[Fact]
public void FromExecutionSettingsWhenAlreadyHuggingFaceShouldReturnSameAsync()
{
// Arrange
var executionSettings = new HuggingFacePromptExecutionSettings();

// Act
var huggingFaceExecutionSettings = HuggingFacePromptExecutionSettings.FromExecutionSettings(executionSettings);

// Assert
Assert.Same(executionSettings, huggingFaceExecutionSettings);
}

[Fact]
public void FromExecutionSettingsWhenNullShouldReturnDefaultAsync()
{
// Arrange
HuggingFacePromptExecutionSettings? executionSettings = null;

// Act
var huggingFaceExecutionSettings = HuggingFacePromptExecutionSettings.FromExecutionSettings(executionSettings);

// Assert
Assert.Equal(HuggingFacePromptExecutionSettings.DefaultTextMaxTokens, huggingFaceExecutionSettings.MaxTokens);
}

[Fact]
public void FromExecutionSettingsWhenSerializedHasPropertiesShouldPopulateSpecialized()
{
string jsonSettings = """
{
"temperature": 0.5,
"top_k": 50,
"max_tokens": 100,
"max_time": 10.0,
"top_p": 0.9,
"repetition_penalty": 1.0,
"use_cache": true,
"results_per_prompt": 1,
"wait_for_model": false
}
""";

var executionSettings = JsonSerializer.Deserialize<PromptExecutionSettings>(jsonSettings);
var huggingFaceExecutionSettings = HuggingFacePromptExecutionSettings.FromExecutionSettings(executionSettings);

Assert.Equal(0.5, huggingFaceExecutionSettings.Temperature);
Assert.Equal(50, huggingFaceExecutionSettings.TopK);
Assert.Equal(100, huggingFaceExecutionSettings.MaxTokens);
Assert.Equal(10.0, huggingFaceExecutionSettings.MaxTime);
Assert.Equal(0.9, huggingFaceExecutionSettings.TopP);
Assert.Equal(1.0, huggingFaceExecutionSettings.RepetitionPenalty);
Assert.True(huggingFaceExecutionSettings.UseCache);
Assert.Equal(1, huggingFaceExecutionSettings.ResultsPerPrompt);
Assert.False(huggingFaceExecutionSettings.WaitForModel);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SemanticKernel.Connectors.HuggingFace.UnitTests;

internal sealed class HttpMessageHandlerStub : DelegatingHandler
{
public HttpRequestHeaders? RequestHeaders { get; private set; }

public HttpContentHeaders? ContentHeaders { get; private set; }

public byte[]? RequestContent { get; private set; }

public Uri? RequestUri { get; private set; }

public HttpMethod? Method { get; private set; }

public HttpResponseMessage ResponseToReturn { get; set; }

public HttpMessageHandlerStub()
{
this.ResponseToReturn = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
this.ResponseToReturn.Content = new StringContent("{}", Encoding.UTF8, "application/json");
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
this.Method = request.Method;
this.RequestUri = request.RequestUri;
this.RequestHeaders = request.Headers;
if (request.Content is not null)
{
#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods; overload doesn't exist on .NET Framework
this.RequestContent = await request.Content.ReadAsByteArrayAsync();
#pragma warning restore CA2016
}

this.ContentHeaders = request.Content?.Headers;

return await Task.FromResult(this.ResponseToReturn);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.HuggingFace;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.TextGeneration;
using Xunit;

namespace SemanticKernel.Connectors.HuggingFace.UnitTests;

public class HuggingFaceKernelBuilderExtensionsTests
{
[Fact]
public void AddHuggingFaceTextGenerationCreatesService()
{
var builder = Kernel.CreateBuilder();
builder.AddHuggingFaceTextGeneration("model");

var kernel = builder.Build();
var service = kernel.GetRequiredService<ITextGenerationService>();

Assert.NotNull(kernel);
Assert.NotNull(service);
Assert.IsType<HuggingFaceTextGenerationService>(service);
}

[Fact]
public void AddHuggingFaceTextGenerationToServiceCollection()
{
var services = new ServiceCollection();
services.AddHuggingFaceTextGeneration("model");

var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetRequiredService<ITextGenerationService>();

Assert.NotNull(service);
Assert.IsType<HuggingFaceTextGenerationService>(service);
}

[Fact]
public void AddHuggingFaceTextEmbeddingGenerationCreatesService()
{
var builder = Kernel.CreateBuilder();
builder.AddHuggingFaceTextEmbeddingGeneration("model");

var kernel = builder.Build();
var service = kernel.GetRequiredService<ITextEmbeddingGenerationService>();

Assert.NotNull(kernel);
Assert.NotNull(service);
Assert.IsType<HuggingFaceTextEmbeddingGenerationService>(service);
}

[Fact]
public void AddHuggingFaceTextEmbeddingsGenerationToServiceCollection()
{
var services = new ServiceCollection();
services.AddHuggingFaceTextEmbeddingGeneration("model");

var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetRequiredService<ITextEmbeddingGenerationService>();

Assert.NotNull(service);
Assert.IsType<HuggingFaceTextEmbeddingGenerationService>(service);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Moq;
using Moq.Protected;

namespace SemanticKernel.Connectors.UnitTests.HuggingFace;
namespace SemanticKernel.Connectors.HuggingFace.UnitTests;

/// <summary>
/// Helper for HuggingFace test purposes.
Expand All @@ -20,7 +20,7 @@ internal static class HuggingFaceTestHelper
/// <param name="fileName">Name of the file with test response.</param>
internal static string GetTestResponse(string fileName)
{
return File.ReadAllText($"./HuggingFace/TestData/{fileName}");
return File.ReadAllText($"./TestData/{fileName}");
}

/// <summary>
Expand Down

0 comments on commit 316ff5d

Please sign in to comment.