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
3 changes: 2 additions & 1 deletion dotnet/SK-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5C246969-D
ProjectSection(SolutionItems) = preProject
src\InternalUtilities\test\AssertExtensions.cs = src\InternalUtilities\test\AssertExtensions.cs
src\InternalUtilities\test\HttpMessageHandlerStub.cs = src\InternalUtilities\test\HttpMessageHandlerStub.cs
src\Connectors\Connectors.OpenAIV2.UnitTests\Utils\MoqExtensions.cs = src\Connectors\Connectors.OpenAIV2.UnitTests\Utils\MoqExtensions.cs
src\InternalUtilities\test\MultipleHttpMessageHandlerStub.cs = src\InternalUtilities\test\MultipleHttpMessageHandlerStub.cs
src\InternalUtilities\test\TestInternalUtilities.props = src\InternalUtilities\test\TestInternalUtilities.props
EndProjectSection
Expand Down Expand Up @@ -324,7 +325,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestsV2", "src\I
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.AzureOpenAI", "src\Connectors\Connectors.AzureOpenAI\Connectors.AzureOpenAI.csproj", "{6744272E-8326-48CE-9A3F-6BE227A5E777}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connectors.AzureOpenAI.UnitTests", "src\Connectors\Connectors.AzureOpenAI.UnitTests\Connectors.AzureOpenAI.UnitTests.csproj", "{DB219924-208B-4CDD-8796-EE424689901E}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.AzureOpenAI.UnitTests", "src\Connectors\Connectors.AzureOpenAI.UnitTests\Connectors.AzureOpenAI.UnitTests.csproj", "{DB219924-208B-4CDD-8796-EE424689901E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@
</ItemGroup>

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

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

<ItemGroup>
<Using Include="SemanticKernel.Connectors.OpenAI.UnitTests"/>
</ItemGroup>

<ItemGroup>
<None Update="TestData\text-embeddings-multiple-response.txt">
Expand All @@ -44,6 +47,9 @@
<None Update="TestData\text-embeddings-response.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestData\text-to-image-response.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Http;
using Microsoft.SemanticKernel.Services;
using Moq;
using OpenAI;
using Xunit;
Expand All @@ -23,7 +24,7 @@ public void ItCanBeInstantiatedAndPropertiesSetAsExpected()
{
// Act
var logger = new Mock<ILogger<ClientCoreTests>>().Object;
var openAIClient = new OpenAIClient(new ApiKeyCredential("key"));
var openAIClient = new OpenAIClient("key");

var clientCoreModelConstructor = new ClientCore("model1", "apiKey");
var clientCoreOpenAIClientConstructor = new ClientCore("model1", openAIClient, logger: logger);
Expand Down Expand Up @@ -67,6 +68,8 @@ public void ItUsesEndpointAsExpected(string? clientBaseAddress, string? provided

// Assert
Assert.Equal(endpoint ?? client?.BaseAddress ?? new Uri("https://api.openai.com/v1"), clientCore.Endpoint);
Assert.True(clientCore.Attributes.ContainsKey(AIServiceExtensions.EndpointKey));
Assert.Equal(endpoint?.ToString() ?? client?.BaseAddress?.ToString() ?? "https://api.openai.com/v1", clientCore.Attributes[AIServiceExtensions.EndpointKey]);

client?.Dispose();
}
Expand Down Expand Up @@ -142,7 +145,7 @@ public async Task ItDoNotAddSemanticKernelHeadersWhenOpenAIClientIsProvidedAsync
var clientCore = new ClientCore(
modelId: "model",
openAIClient: new OpenAIClient(
new ApiKeyCredential("test"),
"test",
new OpenAIClientOptions()
{
Transport = new HttpClientPipelineTransport(client),
Expand Down Expand Up @@ -185,4 +188,65 @@ public void ItAddAttributesButDoesNothingIfNullOrEmpty(string? value)
Assert.Equal(value, clientCore.Attributes["key"]);
}
}

[Fact]
public void ItAddModelIdAttributeAsExpected()
{
// Arrange
var expectedModelId = "modelId";

// Act
var clientCore = new ClientCore(expectedModelId, "apikey");
var clientCoreBreakingGlass = new ClientCore(expectedModelId, new OpenAIClient(" "));

// Assert
Assert.True(clientCore.Attributes.ContainsKey(AIServiceExtensions.ModelIdKey));
Assert.True(clientCoreBreakingGlass.Attributes.ContainsKey(AIServiceExtensions.ModelIdKey));
Assert.Equal(expectedModelId, clientCore.Attributes[AIServiceExtensions.ModelIdKey]);
Assert.Equal(expectedModelId, clientCoreBreakingGlass.Attributes[AIServiceExtensions.ModelIdKey]);
}

[Fact]
public void ItAddOrNotOrganizationIdAttributeWhenProvided()
{
// Arrange
var expectedOrganizationId = "organizationId";

// Act
var clientCore = new ClientCore("modelId", "apikey", expectedOrganizationId);
var clientCoreWithoutOrgId = new ClientCore("modelId", "apikey");

// Assert
Assert.True(clientCore.Attributes.ContainsKey(ClientCore.OrganizationKey));
Assert.Equal(expectedOrganizationId, clientCore.Attributes[ClientCore.OrganizationKey]);
Assert.False(clientCoreWithoutOrgId.Attributes.ContainsKey(ClientCore.OrganizationKey));
}

[Fact]
public void ItThrowsIfModelIdIsNotProvided()
{
// Act & Assert
Assert.Throws<ArgumentException>(() => new ClientCore(" ", "apikey"));
Assert.Throws<ArgumentException>(() => new ClientCore("", "apikey"));
Assert.Throws<ArgumentNullException>(() => new ClientCore(null!));
}

[Fact]
public void ItThrowsWhenNotUsingCustomEndpointAndApiKeyIsNotProvided()
{
// Act & Assert
Assert.Throws<ArgumentException>(() => new ClientCore("modelId", " "));
Assert.Throws<ArgumentException>(() => new ClientCore("modelId", ""));
Assert.Throws<ArgumentNullException>(() => new ClientCore("modelId", apiKey: null!));
}

[Fact]
public void ItDoesNotThrowWhenUsingCustomEndpointAndApiKeyIsNotProvided()
{
// Act & Assert
ClientCore? clientCore = null;
clientCore = new ClientCore("modelId", " ", endpoint: new Uri("http://localhost"));
clientCore = new ClientCore("modelId", "", endpoint: new Uri("http://localhost"));
clientCore = new ClientCore("modelId", apiKey: null!, endpoint: new Uri("http://localhost"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.TextToImage;
using OpenAI;
using Xunit;

namespace SemanticKernel.Connectors.OpenAI.UnitTests.Extensions;

public class KernelBuilderExtensionsTests
{
[Fact]
public void ItCanAddTextEmbeddingGenerationService()
{
// Arrange
var sut = Kernel.CreateBuilder();

// Act
var service = sut.AddOpenAITextEmbeddingGeneration("model", "key")
.Build()
.GetRequiredService<ITextEmbeddingGenerationService>();

// Assert
Assert.Equal("model", service.Attributes[AIServiceExtensions.ModelIdKey]);
}

[Fact]
public void ItCanAddTextEmbeddingGenerationServiceWithOpenAIClient()
{
// Arrange
var sut = Kernel.CreateBuilder();

// Act
var service = sut.AddOpenAITextEmbeddingGeneration("model", new OpenAIClient("key"))
.Build()
.GetRequiredService<ITextEmbeddingGenerationService>();

// Assert
Assert.Equal("model", service.Attributes[AIServiceExtensions.ModelIdKey]);
}

[Fact]
public void ItCanAddTextToImageService()
{
// Arrange
var sut = Kernel.CreateBuilder();

// Act
var service = sut.AddOpenAITextToImage("model", "key")
.Build()
.GetRequiredService<ITextToImageService>();

// Assert
Assert.Equal("model", service.Attributes[AIServiceExtensions.ModelIdKey]);
}

[Fact]
public void ItCanAddTextToImageServiceWithOpenAIClient()
{
// Arrange
var sut = Kernel.CreateBuilder();

// Act
var service = sut.AddOpenAITextToImage("model", new OpenAIClient("key"))
.Build()
.GetRequiredService<ITextToImageService>();

// Assert
Assert.Equal("model", service.Attributes[AIServiceExtensions.ModelIdKey]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.Services;
using Microsoft.SemanticKernel.TextToImage;
using OpenAI;
using Xunit;

namespace SemanticKernel.Connectors.OpenAI.UnitTests.Extensions;

public class ServiceCollectionExtensionsTests
{
[Fact]
public void ItCanAddTextEmbeddingGenerationService()
{
// Arrange
var sut = new ServiceCollection();

// Act
var service = sut.AddOpenAITextEmbeddingGeneration("model", "key")
.BuildServiceProvider()
.GetRequiredService<ITextEmbeddingGenerationService>();

// Assert
Assert.Equal("model", service.Attributes[AIServiceExtensions.ModelIdKey]);
}

[Fact]
public void ItCanAddTextEmbeddingGenerationServiceWithOpenAIClient()
{
// Arrange
var sut = new ServiceCollection();

// Act
var service = sut.AddOpenAITextEmbeddingGeneration("model", new OpenAIClient("key"))
.BuildServiceProvider()
.GetRequiredService<ITextEmbeddingGenerationService>();

// Assert
Assert.Equal("model", service.Attributes[AIServiceExtensions.ModelIdKey]);
}

[Fact]
public void ItCanAddImageToTextService()
{
// Arrange
var sut = new ServiceCollection();

// Act
var service = sut.AddOpenAITextToImage("model", "key")
.BuildServiceProvider()
.GetRequiredService<ITextToImageService>();

// Assert
Assert.Equal("model", service.Attributes[AIServiceExtensions.ModelIdKey]);
}

[Fact]
public void ItCanAddImageToTextServiceWithOpenAIClient()
{
// Arrange
var sut = new ServiceCollection();

// Act
var service = sut.AddOpenAITextToImage("model", new OpenAIClient("key"))
.BuildServiceProvider()
.GetRequiredService<ITextToImageService>();

// Assert
Assert.Equal("model", service.Attributes[AIServiceExtensions.ModelIdKey]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Services;
using Moq;
using OpenAI;
using Xunit;

namespace SemanticKernel.Connectors.OpenAI.UnitTests.Services;

/// <summary>
/// Unit tests for <see cref="OpenAITextEmbeddingGenerationService"/> class.
/// </summary>
public class OpenAITextEmbeddingGenerationServiceTests
{
[Fact]
Expand Down Expand Up @@ -43,8 +49,9 @@ public async Task ItGetEmbeddingsAsyncReturnsEmptyWhenProvidedDataIsEmpty()
}

[Fact]
public async Task IGetEmbeddingsAsyncReturnsEmptyWhenProvidedDataIsWhitespace()
public async Task GetEmbeddingsAsyncReturnsEmptyWhenProvidedDataIsWhitespace()
{
// Arrange
using HttpMessageHandlerStub handler = new()
{
ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
Expand All @@ -54,7 +61,6 @@ public async Task IGetEmbeddingsAsyncReturnsEmptyWhenProvidedDataIsWhitespace()
};
using HttpClient client = new(handler);

// Arrange
var sut = new OpenAITextEmbeddingGenerationService("model", "apikey", httpClient: client);

// Act
Expand All @@ -68,6 +74,7 @@ public async Task IGetEmbeddingsAsyncReturnsEmptyWhenProvidedDataIsWhitespace()
[Fact]
public async Task ItThrowsIfNumberOfResultsDiffersFromInputsAsync()
{
// Arrange
using HttpMessageHandlerStub handler = new()
{
ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
Expand All @@ -77,10 +84,38 @@ public async Task ItThrowsIfNumberOfResultsDiffersFromInputsAsync()
};
using HttpClient client = new(handler);

// Arrange
var sut = new OpenAITextEmbeddingGenerationService("model", "apikey", httpClient: client);

// Act & Assert
await Assert.ThrowsAsync<KernelException>(async () => await sut.GenerateEmbeddingsAsync(["test"], null, CancellationToken.None));
}

[Fact]
public async Task GetEmbeddingsDoesLogActionAsync()
{
// Arrange
using HttpMessageHandlerStub handler = new()
{
ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(File.ReadAllText("./TestData/text-embeddings-response.txt"))
}
};
using HttpClient client = new(handler);

var modelId = "dall-e-2";
var logger = new Mock<ILogger<OpenAITextEmbeddingGenerationService>>();
logger.Setup(l => l.IsEnabled(It.IsAny<LogLevel>())).Returns(true);

var mockLoggerFactory = new Mock<ILoggerFactory>();
mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny<string>())).Returns(logger.Object);

var sut = new OpenAITextEmbeddingGenerationService(modelId, "apiKey", httpClient: client, loggerFactory: mockLoggerFactory.Object);

// Act
await sut.GenerateEmbeddingsAsync(["description"]);

// Assert
logger.VerifyLog(LogLevel.Information, $"Action: {nameof(OpenAITextEmbeddingGenerationService.GenerateEmbeddingsAsync)}. OpenAI Model ID: {modelId}.", Times.Once());
}
}
Loading