Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,12 @@ _site
# Yarn
.yarn
.yarnrc.yml

# Python Environments
.env
.venv
.myenv
env/
venv/
myvenv/
ENV/
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,13 @@
<ProjectReference Include="..\..\SemanticKernel\SemanticKernel.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="HuggingFace\TestData\completion_test_response.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="HuggingFace\TestData\embeddings_test_response.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

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

using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;

namespace SemanticKernel.Connectors.UnitTests.HuggingFace;

/// <summary>
/// Helper for HuggingFace test purposes.
/// </summary>
internal static class HuggingFaceTestHelper
{
/// <summary>
/// Reads test response from file for mocking purposes.
/// </summary>
/// <param name="fileName">Name of the file with test response.</param>
internal static string GetTestResponse(string fileName)
{
return File.ReadAllText($"./HuggingFace/TestData/{fileName}");
}

/// <summary>
/// Returns mocked instance of <see cref="HttpClientHandler"/>.
/// </summary>
/// <param name="httpResponseMessage">Message to return for mocked <see cref="HttpClientHandler"/>.</param>
internal static HttpClientHandler GetHttpClientHandlerMock(HttpResponseMessage httpResponseMessage)
{
var httpClientHandler = new Mock<HttpClientHandler>();

httpClientHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);

return httpClientHandler.Object;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{
"generated_text": "This is test completion response"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"data": [
{
"embedding": [
-0.08541165292263031,
0.08639130741357803,
-0.12805694341659546,
-0.2877824902534485,
0.2114177942276001,
-0.29374566674232483,
-0.10496602207422256,
0.009402364492416382
],
"index": 0,
"object": "embedding"
}
],
"object": "list",
"usage": {
"prompt_tokens": 15,
"total_tokens": 15
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Connectors.HuggingFace.TextCompletion;
using Xunit;

namespace SemanticKernel.Connectors.UnitTests.HuggingFace.TextCompletion;

/// <summary>
/// Unit tests for <see cref="HuggingFaceTextCompletion"/> class.
/// </summary>
public class HuggingFaceTextCompletionTests : IDisposable
{
private const string Endpoint = "http://localhost:5000/completions";
private const string Model = "gpt2";

private readonly HttpResponseMessage _response = new()
{
StatusCode = HttpStatusCode.OK,
};

/// <summary>
/// Verifies that <see cref="HuggingFaceTextCompletion.CompleteAsync(string, CompleteRequestSettings, CancellationToken)"/>
/// returns expected completed text without errors.
/// </summary>
[Fact]
public async Task ItReturnsCompletionCorrectlyAsync()
{
// Arrange
const string prompt = "This is test";
CompleteRequestSettings requestSettings = new();

using var service = this.CreateService(HuggingFaceTestHelper.GetTestResponse("completion_test_response.json"));

// Act
var completion = await service.CompleteAsync(prompt, requestSettings);

// Assert
Assert.Equal("This is test completion response", completion);
}

/// <summary>
/// Initializes <see cref="HuggingFaceTextCompletion"/> with mocked <see cref="HttpClientHandler"/>.
/// </summary>
/// <param name="testResponse">Test response for <see cref="HttpClientHandler"/> to return.</param>
private HuggingFaceTextCompletion CreateService(string testResponse)
{
this._response.Content = new StringContent(testResponse);

var httpClientHandler = HuggingFaceTestHelper.GetHttpClientHandlerMock(this._response);

return new HuggingFaceTextCompletion(new Uri(Endpoint), Model, httpClientHandler);
}

public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this._response.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Connectors.HuggingFace.TextEmbedding;
using Xunit;

namespace SemanticKernel.Connectors.UnitTests.HuggingFace.TextEmbedding;

/// <summary>
/// Unit tests for <see cref="HuggingFaceTextEmbeddingGeneration"/> class.
/// </summary>
public class HuggingFaceEmbeddingGenerationTests : IDisposable
{
private const string Endpoint = "http://localhost:5000/embeddings";
private const string Model = "gpt2";

private readonly HttpResponseMessage _response = new()
{
StatusCode = HttpStatusCode.OK,
};

/// <summary>
/// Verifies that <see cref="HuggingFaceTextEmbeddingGeneration.GenerateEmbeddingsAsync(IList{string})"/>
/// returns expected list of generated embeddings without errors.
/// </summary>
[Fact]
public async Task ItReturnsEmbeddingsCorrectlyAsync()
{
// Arrange
const int expectedEmbeddingCount = 1;
const int expectedVectorCount = 8;
List<string> data = new() { "test_string_1", "test_string_2", "test_string_3" };

using var service = this.CreateService(HuggingFaceTestHelper.GetTestResponse("embeddings_test_response.json"));

// Act
var embeddings = await service.GenerateEmbeddingsAsync(data);

// Assert
Assert.NotNull(embeddings);
Assert.Equal(expectedEmbeddingCount, embeddings.Count);
Assert.Equal(expectedVectorCount, embeddings.First().Count);
}

/// <summary>
/// Initializes <see cref="HuggingFaceTextEmbeddingGeneration"/> with mocked <see cref="HttpClientHandler"/>.
/// </summary>
/// <param name="testResponse">Test response for <see cref="HttpClientHandler"/> to return.</param>
private HuggingFaceTextEmbeddingGeneration CreateService(string testResponse)
{
this._response.Content = new StringContent(testResponse);

var httpClientHandler = HuggingFaceTestHelper.GetHttpClientHandlerMock(this._response);

return new HuggingFaceTextEmbeddingGeneration(new Uri(Endpoint), Model, httpClientHandler);
}

public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this._response.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.SemanticKernel.AI.TextCompletion;
using Microsoft.SemanticKernel.Connectors.HuggingFace.TextCompletion;
using Xunit;

namespace SemanticKernel.IntegrationTests.Connectors.HuggingFace.TextCompletion;

/// <summary>
/// Integration tests for <see cref="HuggingFaceTextCompletion"/>.
/// </summary>
public sealed class HuggingFaceTextCompletionTests
{
private const string Endpoint = "http://localhost:5000/completions";
private const string Model = "gpt2";

private readonly IConfigurationRoot _configuration;

public HuggingFaceTextCompletionTests()
{
// Load configuration
this._configuration = new ConfigurationBuilder()
.AddJsonFile(path: "testsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables()
.Build();
}

[Fact(Skip = "This test is for manual verification.")]
public async Task HuggingFaceLocalAndRemoteTextCompletionAsync()
{
// Arrange
const string input = "This is test";

using var huggingFaceLocal = new HuggingFaceTextCompletion(new Uri(Endpoint), Model);
using var huggingFaceRemote = new HuggingFaceTextCompletion(this.GetApiKey(), Model);

// Act
var localResponse = await huggingFaceLocal.CompleteAsync(input, new CompleteRequestSettings()).ConfigureAwait(false);
var remoteResponse = await huggingFaceRemote.CompleteAsync(input, new CompleteRequestSettings()).ConfigureAwait(false);

// Assert
Assert.NotNull(localResponse);
Assert.NotNull(remoteResponse);

Assert.StartsWith(input, localResponse, StringComparison.InvariantCulture);
Assert.StartsWith(input, remoteResponse, StringComparison.InvariantCulture);
}

private string GetApiKey()
{
return this._configuration.GetSection("HuggingFace:ApiKey").Get<string>()!;
}
}
8 changes: 7 additions & 1 deletion dotnet/src/SemanticKernel.IntegrationTests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
1. **Azure OpenAI**: go to the [Azure OpenAI Quickstart](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/quickstart)
and deploy an instance of Azure OpenAI, deploy a model like "text-davinci-003" find your Endpoint and API key.
2. **OpenAI**: go to [OpenAI](https://openai.com/api/) to register and procure your API key.
3. **Azure Bing Web Search API**: go to [Bing Web Seach API](https://www.microsoft.com/en-us/bing/apis/bing-web-search-api)
3. **HuggingFace API key**: see https://huggingface.co/docs/huggingface_hub/guides/inference for details.
4. **Azure Bing Web Search API**: go to [Bing Web Seach API](https://www.microsoft.com/en-us/bing/apis/bing-web-search-api)
and select `Try Now` to get started.

## Setup
Expand Down Expand Up @@ -42,6 +43,9 @@ For example:
"Endpoint": "https://contoso.openai.azure.com/",
"ApiKey": "...."
},
"HuggingFace": {
"ApiKey": ""
},
"Bing": {
"ApiKey": "...."
}
Expand All @@ -58,6 +62,7 @@ For example:
export AzureOpenAI__DeploymentName="azure-text-davinci-003"
export AzureOpenAIEmbeddings__DeploymentName="azure-text-embedding-ada-002"
export AzureOpenAI__Endpoint="https://contoso.openai.azure.com/"
export HuggingFace__ApiKey="...."
export Bing__ApiKey="...."
```

Expand All @@ -69,5 +74,6 @@ For example:
$env:AzureOpenAI__DeploymentName = "azure-text-davinci-003"
$env:AzureOpenAIEmbeddings__DeploymentName = "azure-text-embedding-ada-002"
$env:AzureOpenAI__Endpoint = "https://contoso.openai.azure.com/"
$env:HuggingFace__ApiKey = "...."
$env:Bing__ApiKey = "...."
```
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@
</Content>
</ItemGroup>

<ItemGroup>
<Folder Include="Connectors\HuggingFace" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"Endpoint": "",
"ApiKey": ""
},
"HuggingFace": {
"ApiKey": ""
},
"Bing": {
"ApiKey": ""
}
Expand Down
Loading