chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# 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
|
||||
@@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyName>SemanticKernel.Connectors.InMemory.UnitTests</AssemblyName>
|
||||
<RootNamespace>SemanticKernel.Connectors.InMemory.UnitTests</RootNamespace>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>$(NoWarn);CA2007,CA1806,CA1869,CA1861,IDE0300,VSTHRD111,SKEXP0001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<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>
|
||||
</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)"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\VectorData\InMemory\InMemory.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\SemanticKernel.Core\SemanticKernel.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using Xunit;
|
||||
|
||||
namespace SemanticKernel.Connectors.InMemory.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="InMemoryServiceCollectionExtensions"/> class.
|
||||
/// </summary>
|
||||
public class InMemoryServiceCollectionExtensionsTests
|
||||
{
|
||||
private readonly IServiceCollection _serviceCollection;
|
||||
|
||||
public InMemoryServiceCollectionExtensionsTests()
|
||||
{
|
||||
this._serviceCollection = new ServiceCollection();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddVectorStoreRegistersClass()
|
||||
{
|
||||
// Act.
|
||||
this._serviceCollection.AddInMemoryVectorStore();
|
||||
|
||||
// Assert.
|
||||
var serviceProvider = this._serviceCollection.BuildServiceProvider();
|
||||
var vectorStore = serviceProvider.GetRequiredService<VectorStore>();
|
||||
Assert.NotNull(vectorStore);
|
||||
Assert.IsType<InMemoryVectorStore>(vectorStore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddVectorStoreRecordCollectionRegistersClass()
|
||||
{
|
||||
// Act.
|
||||
this._serviceCollection.AddInMemoryVectorStoreRecordCollection<string, TestRecord>("testcollection");
|
||||
|
||||
// Assert.
|
||||
this.AssertVectorStoreRecordCollectionCreated();
|
||||
}
|
||||
|
||||
private void AssertVectorStoreRecordCollectionCreated()
|
||||
{
|
||||
var serviceProvider = this._serviceCollection.BuildServiceProvider();
|
||||
|
||||
var collection = serviceProvider.GetRequiredService<VectorStoreCollection<string, TestRecord>>();
|
||||
Assert.NotNull(collection);
|
||||
Assert.IsType<InMemoryCollection<string, TestRecord>>(collection);
|
||||
|
||||
var vectorizedSearch = serviceProvider.GetRequiredService<IVectorSearchable<TestRecord>>();
|
||||
Assert.NotNull(vectorizedSearch);
|
||||
Assert.IsType<InMemoryCollection<string, TestRecord>>(vectorizedSearch);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
|
||||
private sealed class TestRecord
|
||||
#pragma warning restore CA1812 // Avoid uninstantiated internal classes
|
||||
{
|
||||
[VectorStoreKey]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
[VectorStoreVector(4)]
|
||||
public ReadOnlyMemory<float> Vector { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using Xunit;
|
||||
|
||||
namespace SemanticKernel.Connectors.InMemory.UnitTests;
|
||||
|
||||
public class InMemoryVectorStoreExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task SerializeAndDeserializeCollectionRoundtripWorks()
|
||||
{
|
||||
// Arrange
|
||||
using var vectorStore = new InMemoryVectorStore();
|
||||
var collectionName = "test-collection";
|
||||
var collection = vectorStore.GetCollection<Guid, TestRecord>(collectionName);
|
||||
|
||||
var record1 = new TestRecord
|
||||
{
|
||||
Key = Guid.NewGuid(),
|
||||
Text = "First record",
|
||||
Embedding = new ReadOnlyMemory<float>(new float[] { 0.1f, 0.2f, 0.3f })
|
||||
};
|
||||
var record2 = new TestRecord
|
||||
{
|
||||
Key = Guid.NewGuid(),
|
||||
Text = "Second record",
|
||||
Embedding = new ReadOnlyMemory<float>(new float[] { 0.4f, 0.5f, 0.6f })
|
||||
};
|
||||
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
await collection.UpsertAsync(new[] { record1, record2 });
|
||||
|
||||
// Act
|
||||
using var memStream = new MemoryStream();
|
||||
await vectorStore.SerializeCollectionAsJsonAsync<Guid, TestRecord>(collectionName, memStream);
|
||||
memStream.Position = 0;
|
||||
|
||||
// Simulate loading into a new store
|
||||
using var newVectorStore = new InMemoryVectorStore();
|
||||
var deserializedCollection = await newVectorStore.DeserializeCollectionFromJsonAsync<Guid, TestRecord>(memStream);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserializedCollection);
|
||||
var loadedRecord1 = await deserializedCollection.GetAsync(record1.Key);
|
||||
var loadedRecord2 = await deserializedCollection.GetAsync(record2.Key);
|
||||
|
||||
Assert.NotNull(loadedRecord1);
|
||||
Assert.NotNull(loadedRecord2);
|
||||
Assert.Equal(record1.Text, loadedRecord1.Text);
|
||||
Assert.Equal(record2.Text, loadedRecord2.Text);
|
||||
Assert.Equal(record1.Embedding, loadedRecord1.Embedding);
|
||||
Assert.Equal(record2.Embedding, loadedRecord2.Embedding);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SerializeAndDeserializeCollectionRoundtripWithBuiltInEmbeddingGenerationWorks()
|
||||
{
|
||||
// Arrange
|
||||
using var vectorStore = new InMemoryVectorStore(new() { EmbeddingGenerator = new FakeEmbeddingGenerator() });
|
||||
var collectionName = "test-collection";
|
||||
var collection = vectorStore.GetCollection<Guid, TestRecordAutoEmbed>(collectionName);
|
||||
|
||||
var record1 = new TestRecordAutoEmbed
|
||||
{
|
||||
Key = Guid.NewGuid(),
|
||||
Text = "First record",
|
||||
};
|
||||
var record2 = new TestRecordAutoEmbed
|
||||
{
|
||||
Key = Guid.NewGuid(),
|
||||
Text = "Second record",
|
||||
};
|
||||
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
await collection.UpsertAsync(new[] { record1, record2 });
|
||||
|
||||
// Act
|
||||
using var memStream = new MemoryStream();
|
||||
await vectorStore.SerializeCollectionAsJsonAsync<Guid, TestRecordAutoEmbed>(collectionName, memStream);
|
||||
memStream.Position = 0;
|
||||
|
||||
// Simulate loading into a new store
|
||||
using var newVectorStore = new InMemoryVectorStore(new() { EmbeddingGenerator = new FakeEmbeddingGenerator() });
|
||||
var deserializedCollection = await newVectorStore.DeserializeCollectionFromJsonAsync<Guid, TestRecordAutoEmbed>(memStream);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserializedCollection);
|
||||
var loadedRecord1 = await deserializedCollection.GetAsync(record1.Key);
|
||||
var loadedRecord2 = await deserializedCollection.GetAsync(record2.Key);
|
||||
|
||||
Assert.NotNull(loadedRecord1);
|
||||
Assert.NotNull(loadedRecord2);
|
||||
Assert.Equal(record1.Text, loadedRecord1.Text);
|
||||
Assert.Equal(record2.Text, loadedRecord2.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeserializeCollectionFromJsonAsyncThrowsOnInvalidJson()
|
||||
{
|
||||
using var vectorStore = new InMemoryVectorStore();
|
||||
using var memStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes("{ invalid json }"));
|
||||
|
||||
await Assert.ThrowsAsync<JsonException>(async () =>
|
||||
{
|
||||
await vectorStore.DeserializeCollectionFromJsonAsync<Guid, TestRecord>(memStream);
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class TestRecord
|
||||
{
|
||||
[VectorStoreKey]
|
||||
public Guid Key { get; init; }
|
||||
|
||||
[VectorStoreData]
|
||||
public string Text { get; init; } = string.Empty;
|
||||
|
||||
[VectorStoreVector(3)]
|
||||
public ReadOnlyMemory<float> Embedding { get; init; }
|
||||
}
|
||||
|
||||
private sealed class TestRecordAutoEmbed
|
||||
{
|
||||
[VectorStoreKey]
|
||||
public Guid Key { get; init; }
|
||||
|
||||
[VectorStoreData]
|
||||
public string Text { get; init; } = string.Empty;
|
||||
|
||||
[VectorStoreVector(3)]
|
||||
public string Embedding => this.Text;
|
||||
}
|
||||
|
||||
private sealed class FakeEmbeddingGenerator() : IEmbeddingGenerator<string, Embedding<float>>
|
||||
{
|
||||
public Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(
|
||||
IEnumerable<string> values,
|
||||
EmbeddingGenerationOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var results = new GeneratedEmbeddings<Embedding<float>>();
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
results.Add(new Embedding<float>(new float[] { 0.1f, 0.2f, 0.3f }));
|
||||
}
|
||||
|
||||
return Task.FromResult(results);
|
||||
}
|
||||
|
||||
public object? GetService(Type serviceType, object? serviceKey = null)
|
||||
=> null;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Microsoft.SemanticKernel.Connectors.InMemory;
|
||||
using Xunit;
|
||||
|
||||
namespace SemanticKernel.Connectors.InMemory.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="InMemoryVectorStore"/> class.
|
||||
/// </summary>
|
||||
public class InMemoryVectorStoreTests
|
||||
{
|
||||
private const string TestCollectionName = "testcollection";
|
||||
|
||||
[Fact]
|
||||
public void GetCollectionReturnsCollection()
|
||||
{
|
||||
// Arrange.
|
||||
using var sut = new InMemoryVectorStore();
|
||||
|
||||
// Act.
|
||||
var actual = sut.GetCollection<string, SinglePropsModel<string>>(TestCollectionName);
|
||||
|
||||
// Assert.
|
||||
Assert.NotNull(actual);
|
||||
Assert.IsType<InMemoryCollection<string, SinglePropsModel<string>>>(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCollectionReturnsCollectionWithNonStringKey()
|
||||
{
|
||||
// Arrange.
|
||||
using var sut = new InMemoryVectorStore();
|
||||
|
||||
// Act.
|
||||
var actual = sut.GetCollection<int, SinglePropsModel<int>>(TestCollectionName);
|
||||
|
||||
// Assert.
|
||||
Assert.NotNull(actual);
|
||||
Assert.IsType<InMemoryCollection<int, SinglePropsModel<int>>>(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetCollectionDoesNotAllowADifferentDataTypeThanPreviouslyUsedAsync()
|
||||
{
|
||||
// Arrange.
|
||||
using var sut = new InMemoryVectorStore();
|
||||
var stringKeyCollection = sut.GetCollection<string, SinglePropsModel<string>>(TestCollectionName);
|
||||
await stringKeyCollection.EnsureCollectionExistsAsync();
|
||||
|
||||
// Act and assert.
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => sut.GetCollection<string, SecondModel>(TestCollectionName));
|
||||
Assert.Equal($"Collection '{TestCollectionName}' already exists and with data type 'SinglePropsModel`1' so cannot be re-created with data type 'SecondModel'.", exception.Message);
|
||||
}
|
||||
|
||||
#pragma warning disable CA1812 // Classes are used as generic arguments
|
||||
private sealed class SinglePropsModel<TKey>
|
||||
{
|
||||
[VectorStoreKey]
|
||||
public required TKey Key { get; set; }
|
||||
|
||||
[VectorStoreData]
|
||||
public string Data { get; set; } = string.Empty;
|
||||
|
||||
[VectorStoreVector(4)]
|
||||
public ReadOnlyMemory<float>? Vector { get; set; }
|
||||
|
||||
public string? NotAnnotated { get; set; }
|
||||
}
|
||||
|
||||
private sealed class SecondModel
|
||||
{
|
||||
[VectorStoreKey]
|
||||
public required int Key { get; set; }
|
||||
|
||||
[VectorStoreData]
|
||||
public string Data { get; set; } = string.Empty;
|
||||
}
|
||||
#pragma warning restore CA1812
|
||||
}
|
||||
Reference in New Issue
Block a user