chore: import upstream snapshot with attribution
This commit is contained in:
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests.ModelTests;
|
||||
|
||||
public class PineconeBasicModelTests(PineconeBasicModelTests.Fixture fixture)
|
||||
: BasicModelTests<string>(fixture), IClassFixture<PineconeBasicModelTests.Fixture>
|
||||
{
|
||||
public override async Task GetAsync_with_filter_and_OrderBy()
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<NotSupportedException>(base.GetAsync_with_filter_and_OrderBy);
|
||||
Assert.Equal("Pinecone does not support ordering.", exception.Message);
|
||||
}
|
||||
|
||||
public override async Task GetAsync_with_filter_and_multiple_OrderBys()
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<NotSupportedException>(base.GetAsync_with_filter_and_multiple_OrderBys);
|
||||
Assert.Equal("Pinecone does not support ordering.", exception.Message);
|
||||
}
|
||||
|
||||
public override async Task GetAsync_with_filter_and_OrderBy_and_Skip()
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<NotSupportedException>(base.GetAsync_with_filter_and_OrderBy_and_Skip);
|
||||
Assert.Equal("Pinecone does not support ordering.", exception.Message);
|
||||
}
|
||||
|
||||
public new class Fixture : BasicModelTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests.ModelTests;
|
||||
|
||||
public class PineconeDynamicModelTests(PineconeDynamicModelTests.Fixture fixture)
|
||||
: DynamicModelTests<string>(fixture), IClassFixture<PineconeDynamicModelTests.Fixture>
|
||||
{
|
||||
public override async Task GetAsync_with_filter_and_OrderBy()
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<NotSupportedException>(base.GetAsync_with_filter_and_OrderBy);
|
||||
Assert.Equal("Pinecone does not support ordering.", exception.Message);
|
||||
}
|
||||
|
||||
public override async Task GetAsync_with_filter_and_multiple_OrderBys()
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<NotSupportedException>(base.GetAsync_with_filter_and_multiple_OrderBys);
|
||||
Assert.Equal("Pinecone does not support ordering.", exception.Message);
|
||||
}
|
||||
|
||||
public override async Task GetAsync_with_filter_and_OrderBy_and_Skip()
|
||||
{
|
||||
var exception = await Assert.ThrowsAsync<NotSupportedException>(base.GetAsync_with_filter_and_OrderBy_and_Skip);
|
||||
Assert.Equal("Pinecone does not support ordering.", exception.Message);
|
||||
}
|
||||
|
||||
public new class Fixture : DynamicModelTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests.ModelTests;
|
||||
|
||||
public class PineconeNoDataModelTests(PineconeNoDataModelTests.Fixture fixture)
|
||||
: NoDataModelTests<string>(fixture), IClassFixture<PineconeNoDataModelTests.Fixture>
|
||||
{
|
||||
public new class Fixture : NoDataModelTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- We don't test Full Framework because to does not support Http 2.0 which seems to be enforced by the local emulator. -->
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Pinecone.ConformanceTests</RootNamespace>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
|
||||
<NoWarn>$(NoWarn);CA2007,SKEXP0001,VSTHRD111;CS1685</NoWarn>
|
||||
<NoWarn>$(NoWarn);MEVD9001</NoWarn> <!-- Microsoft.Extensions.VectorData experimental provider-facing APIs -->
|
||||
<UserSecretsId>b7762d10-e29b-4bb1-8b74-b6d69a667dd4</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
|
||||
<PackageReference Include="Testcontainers" />
|
||||
<PackageReference Include="Pinecone.Client" />
|
||||
<PackageReference Include="Humanizer" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\VectorData\Pinecone\Pinecone.csproj" />
|
||||
<ProjectReference Include="..\VectorData.ConformanceTests\VectorData.ConformanceTests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests;
|
||||
|
||||
public class PineconeAllSupportedTypesTests(PineconeFixture fixture) : IClassFixture<PineconeFixture>
|
||||
{
|
||||
[ConditionalFact]
|
||||
public async Task AllTypesBatchGetAsync()
|
||||
{
|
||||
var collection = fixture.TestStore.DefaultVectorStore.GetCollection<string, PineconeAllTypes>("all-types", PineconeAllTypes.GetRecordDefinition());
|
||||
await collection.EnsureCollectionExistsAsync();
|
||||
|
||||
List<PineconeAllTypes> records =
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = "all-types-1",
|
||||
BoolProperty = true,
|
||||
NullableBoolProperty = false,
|
||||
StringProperty = "string prop 1",
|
||||
NullableStringProperty = "nullable prop 1",
|
||||
IntProperty = 1,
|
||||
NullableIntProperty = 10,
|
||||
LongProperty = 100L,
|
||||
NullableLongProperty = 1000L,
|
||||
FloatProperty = 10.5f,
|
||||
NullableFloatProperty = 100.5f,
|
||||
DoubleProperty = 23.75d,
|
||||
NullableDoubleProperty = 233.75d,
|
||||
StringArray = ["one", "two"],
|
||||
NullableStringArray = ["five", "six"],
|
||||
StringList = ["eleven", "twelve"],
|
||||
NullableStringList = ["fifteen", "sixteen"],
|
||||
Embedding = new ReadOnlyMemory<float>([1.5f, 2.5f, 3.5f, 4.5f, 5.5f, 6.5f, 7.5f, 8.5f])
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = "all-types-2",
|
||||
BoolProperty = false,
|
||||
NullableBoolProperty = null,
|
||||
StringProperty = "string prop 2",
|
||||
NullableStringProperty = null,
|
||||
IntProperty = 2,
|
||||
NullableIntProperty = null,
|
||||
LongProperty = 200L,
|
||||
NullableLongProperty = null,
|
||||
FloatProperty = 20.5f,
|
||||
NullableFloatProperty = null,
|
||||
DoubleProperty = 43.75,
|
||||
NullableDoubleProperty = null,
|
||||
StringArray = [],
|
||||
NullableStringArray = null,
|
||||
StringList = [],
|
||||
NullableStringList = null,
|
||||
Embedding = new ReadOnlyMemory<float>([10.5f, 20.5f, 30.5f, 40.5f, 50.5f, 60.5f, 70.5f, 80.5f])
|
||||
}
|
||||
];
|
||||
|
||||
await collection.UpsertAsync(records);
|
||||
|
||||
var allTypes = await collection.GetAsync(records.Select(r => r.Id), new RecordRetrievalOptions { IncludeVectors = true }).ToListAsync();
|
||||
|
||||
var allTypes1 = allTypes.Single(x => x.Id == records[0].Id);
|
||||
var allTypes2 = allTypes.Single(x => x.Id == records[1].Id);
|
||||
|
||||
records[0].AssertEqual(allTypes1);
|
||||
records[1].AssertEqual(allTypes2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests;
|
||||
|
||||
public class PineconeCollectionManagementTests(PineconeFixture fixture)
|
||||
: CollectionManagementTests<string>(fixture), IClassFixture<PineconeFixture>;
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.SemanticKernel.Connectors.Pinecone;
|
||||
using VectorData.ConformanceTests;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests;
|
||||
|
||||
public class PineconeDependencyInjectionTests
|
||||
: DependencyInjectionTests<PineconeVectorStore, PineconeCollection<string, DependencyInjectionTests<string>.Record>, string, DependencyInjectionTests<string>.Record>
|
||||
{
|
||||
private const string ApiKey = "Fake API Key";
|
||||
private static readonly ClientOptions s_clientOptions = new() { MaxRetries = 1 };
|
||||
|
||||
protected override string CollectionName => "lowercase";
|
||||
|
||||
protected override void PopulateConfiguration(ConfigurationManager configuration, object? serviceKey = null)
|
||||
=> configuration.AddInMemoryCollection(
|
||||
[
|
||||
new(CreateConfigKey("Pinecone", serviceKey, "ApiKey"), ApiKey),
|
||||
]);
|
||||
|
||||
private static string ApiKeyProvider(IServiceProvider sp, object? serviceKey = null)
|
||||
=> sp.GetRequiredService<IConfiguration>().GetRequiredSection(CreateConfigKey("Pinecone", serviceKey, "ApiKey")).Value!;
|
||||
|
||||
private static ClientOptions ClientOptionsProvider(IServiceProvider sp, object? serviceKey = null) => s_clientOptions;
|
||||
|
||||
public override IEnumerable<Func<IServiceCollection, object?, string, ServiceLifetime, IServiceCollection>> CollectionDelegates
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return (services, serviceKey, name, lifetime) => serviceKey is null
|
||||
? services
|
||||
.AddSingleton<PineconeClient>(sp => new PineconeClient(ApiKey))
|
||||
.AddPineconeCollection<Record>(name, lifetime: lifetime)
|
||||
: services
|
||||
.AddSingleton<PineconeClient>(sp => new PineconeClient(ApiKey))
|
||||
.AddKeyedPineconeCollection<Record>(serviceKey, name, lifetime: lifetime);
|
||||
|
||||
yield return (services, serviceKey, name, lifetime) => serviceKey is null
|
||||
? services.AddPineconeCollection<Record>(
|
||||
name, ApiKey, lifetime: lifetime)
|
||||
: services.AddKeyedPineconeCollection<Record>(
|
||||
serviceKey, name, ApiKey, lifetime: lifetime);
|
||||
|
||||
yield return (services, serviceKey, name, lifetime) => serviceKey is null
|
||||
? services.AddPineconeCollection<Record>(
|
||||
name, sp => new PineconeClient(ApiKeyProvider(sp), ClientOptionsProvider(sp)), lifetime: lifetime)
|
||||
: services.AddKeyedPineconeCollection<Record>(
|
||||
serviceKey, name, sp => new PineconeClient(ApiKeyProvider(sp, serviceKey), ClientOptionsProvider(sp, serviceKey)), lifetime: lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Func<IServiceCollection, object?, ServiceLifetime, IServiceCollection>> StoreDelegates
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return (services, serviceKey, lifetime) => serviceKey is null
|
||||
? services.AddPineconeVectorStore(
|
||||
ApiKey, s_clientOptions, lifetime: lifetime)
|
||||
: services.AddKeyedPineconeVectorStore(
|
||||
serviceKey, ApiKey, s_clientOptions, lifetime: lifetime);
|
||||
|
||||
yield return (services, serviceKey, lifetime) => serviceKey is null
|
||||
? services
|
||||
.AddSingleton<PineconeClient>(sp => new PineconeClient(ApiKey))
|
||||
.AddPineconeVectorStore(lifetime: lifetime)
|
||||
: services
|
||||
.AddSingleton<PineconeClient>(sp => new PineconeClient(ApiKey))
|
||||
.AddKeyedPineconeVectorStore(serviceKey, lifetime: lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApiKeyCantBeNullOrEmpty()
|
||||
{
|
||||
IServiceCollection services = new ServiceCollection();
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddPineconeVectorStore(apiKey: null!));
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddKeyedPineconeVectorStore(serviceKey: "notNull", apiKey: null!));
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddPineconeCollection<Record>(
|
||||
name: "notNull", apiKey: null!));
|
||||
Assert.Throws<ArgumentException>(() => services.AddPineconeCollection<Record>(
|
||||
name: "notNull", apiKey: ""));
|
||||
Assert.Throws<ArgumentNullException>(() => services.AddKeyedPineconeCollection<Record>(
|
||||
serviceKey: "notNull", name: "notNull", apiKey: null!));
|
||||
Assert.Throws<ArgumentException>(() => services.AddKeyedPineconeCollection<Record>(
|
||||
serviceKey: "notNull", name: "notNull", apiKey: ""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests;
|
||||
|
||||
public class PineconeDistanceFunctionTests(PineconeDistanceFunctionTests.Fixture fixture)
|
||||
: DistanceFunctionTests<string>(fixture), IClassFixture<PineconeDistanceFunctionTests.Fixture>
|
||||
{
|
||||
public override Task CosineDistance() => Assert.ThrowsAsync<NotSupportedException>(base.CosineDistance);
|
||||
public override Task EuclideanDistance() => Assert.ThrowsAsync<NotSupportedException>(base.EuclideanDistance);
|
||||
public override Task HammingDistance() => Assert.ThrowsAsync<NotSupportedException>(base.HammingDistance);
|
||||
public override Task ManhattanDistance() => Assert.ThrowsAsync<NotSupportedException>(base.ManhattanDistance);
|
||||
public override Task NegativeDotProductSimilarity() => Assert.ThrowsAsync<NotSupportedException>(base.NegativeDotProductSimilarity);
|
||||
|
||||
protected override async Task Test(
|
||||
string distanceFunction,
|
||||
double expectedExactMatchScore,
|
||||
double expectedOppositeScore,
|
||||
double expectedOrthogonalScore,
|
||||
int[] resultOrder)
|
||||
{
|
||||
await base.Test(
|
||||
distanceFunction,
|
||||
expectedExactMatchScore,
|
||||
expectedOppositeScore,
|
||||
expectedOrthogonalScore,
|
||||
resultOrder);
|
||||
|
||||
// The Pinecone emulator needs some extra time to spawn a new index service
|
||||
// that uses a different distance function.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
public new class Fixture() : DistanceFunctionTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests;
|
||||
|
||||
public class PineconeEmbeddingGenerationTests(PineconeEmbeddingGenerationTests.StringVectorFixture stringVectorFixture, PineconeEmbeddingGenerationTests.RomOfFloatVectorFixture romOfFloatVectorFixture)
|
||||
: EmbeddingGenerationTests<string>(stringVectorFixture, romOfFloatVectorFixture), IClassFixture<PineconeEmbeddingGenerationTests.StringVectorFixture>, IClassFixture<PineconeEmbeddingGenerationTests.RomOfFloatVectorFixture>
|
||||
{
|
||||
// Overriding since Pinecone requires collection names to only contain ASCII lowercase letters, digits and dashes.
|
||||
public override async Task SearchAsync_string_without_generator_throws()
|
||||
{
|
||||
// The database doesn't support embedding generation, and no client-side generator has been configured at any level,
|
||||
// so SearchAsync should throw.
|
||||
var collection = stringVectorFixture.GetCollection<RawRecord>(stringVectorFixture.TestStore.DefaultVectorStore, stringVectorFixture.CollectionName + "-without-generator");
|
||||
|
||||
var exception = await Assert.ThrowsAsync<NotSupportedException>(() => collection.SearchAsync("foo", top: 1).ToListAsync().AsTask());
|
||||
|
||||
Assert.StartsWith(
|
||||
"A value of type 'string' was passed to 'SearchAsync', but that isn't a supported vector type by your provider and no embedding generator was configured. The supported vector types are:",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
public new class StringVectorFixture : EmbeddingGenerationTests<string>.StringVectorFixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
|
||||
public override VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGenerator)
|
||||
=> PineconeTestStore.Instance.GetVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
|
||||
|
||||
public override Func<IServiceCollection, IServiceCollection>[] DependencyInjectionStoreRegistrationDelegates =>
|
||||
[
|
||||
services => services
|
||||
.AddSingleton(PineconeTestStore.Instance.Client)
|
||||
.AddPineconeVectorStore(),
|
||||
services => services
|
||||
.AddPineconeVectorStore("ForPineconeLocalTheApiKeysAreIgnored", PineconeTestStore.Instance.ClientOptions)
|
||||
];
|
||||
|
||||
public override Func<IServiceCollection, IServiceCollection>[] DependencyInjectionCollectionRegistrationDelegates =>
|
||||
[
|
||||
services => services
|
||||
.AddSingleton(PineconeTestStore.Instance.Client)
|
||||
.AddPineconeCollection<RecordWithAttributes>(this.CollectionName),
|
||||
services => services
|
||||
.AddPineconeCollection<RecordWithAttributes>(this.CollectionName, "ForPineconeLocalTheApiKeysAreIgnored", PineconeTestStore.Instance.ClientOptions),
|
||||
services => services
|
||||
.AddPineconeCollection<RecordWithAttributes>(this.CollectionName, _ => new PineconeClient("ForPineconeLocalTheApiKeysAreIgnored", PineconeTestStore.Instance.ClientOptions))
|
||||
];
|
||||
}
|
||||
|
||||
public new class RomOfFloatVectorFixture : EmbeddingGenerationTests<string>.RomOfFloatVectorFixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
|
||||
public override VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGenerator)
|
||||
=> PineconeTestStore.Instance.GetVectorStore(new() { EmbeddingGenerator = embeddingGenerator });
|
||||
|
||||
public override Func<IServiceCollection, IServiceCollection>[] DependencyInjectionStoreRegistrationDelegates =>
|
||||
[
|
||||
services => services
|
||||
.AddSingleton(PineconeTestStore.Instance.Client)
|
||||
.AddPineconeVectorStore()
|
||||
];
|
||||
|
||||
public override Func<IServiceCollection, IServiceCollection>[] DependencyInjectionCollectionRegistrationDelegates =>
|
||||
[
|
||||
services => services
|
||||
.AddSingleton(PineconeTestStore.Instance.Client)
|
||||
.AddPineconeCollection<RecordWithAttributes>(this.CollectionName)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests;
|
||||
|
||||
#pragma warning disable CS8605 // Unboxing a possibly null value.
|
||||
|
||||
public class PineconeFilterTests(PineconeFilterTests.Fixture fixture)
|
||||
: FilterTests<string>(fixture), IClassFixture<PineconeFilterTests.Fixture>
|
||||
{
|
||||
// Specialized Pinecone syntax for NOT over Contains ($nin)
|
||||
[ConditionalFact]
|
||||
public virtual Task Not_over_Contains()
|
||||
=> this.TestFilterAsync(
|
||||
r => !new[] { 8, 10 }.Contains(r.Int),
|
||||
r => !new[] { 8, 10 }.Contains((int)r["Int"]));
|
||||
|
||||
#region Null checking
|
||||
|
||||
// Pinecone currently doesn't support null checking ({ "Foo" : null }) in vector search pre-filters
|
||||
public override Task Equal_with_null_reference_type()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Equal_with_null_reference_type);
|
||||
|
||||
public override Task Equal_with_null_captured()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Equal_with_null_captured);
|
||||
|
||||
public override Task NotEqual_with_null_reference_type()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.NotEqual_with_null_reference_type);
|
||||
|
||||
public override Task NotEqual_with_null_captured()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.NotEqual_with_null_captured);
|
||||
|
||||
public override Task Equal_int_property_with_null_nullable_int()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Equal_int_property_with_null_nullable_int);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Not
|
||||
|
||||
// Pinecone currently doesn't support NOT in vector search pre-filters
|
||||
// (https://www.mongodb.com/docs/atlas/atlas-vector-search/vector-search-stage/#atlas-vector-search-pre-filter)
|
||||
public override Task Not_over_And()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Not_over_And);
|
||||
|
||||
public override Task Not_over_Or()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Not_over_Or);
|
||||
|
||||
#endregion
|
||||
|
||||
public override Task Contains_over_field_string_array()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Contains_over_field_string_array);
|
||||
|
||||
public override Task Contains_over_field_string_List()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Contains_over_field_string_List);
|
||||
|
||||
// List fields not (currently) supported on SQLite (see #10343)
|
||||
public override Task Contains_with_Enumerable_Contains()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Contains_with_Enumerable_Contains);
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
// List fields not (currently) supported on SQLite (see #10343)
|
||||
public override Task Contains_with_MemoryExtensions_Contains()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Contains_with_MemoryExtensions_Contains);
|
||||
#endif
|
||||
|
||||
#if NET10_0_OR_GREATER
|
||||
public override Task Contains_with_MemoryExtensions_Contains_with_null_comparer()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Contains_with_MemoryExtensions_Contains_with_null_comparer);
|
||||
#endif
|
||||
|
||||
// Any with Contains over array fields not supported on Pinecone
|
||||
public override Task Any_with_Contains_over_inline_string_array()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Any_with_Contains_over_inline_string_array);
|
||||
|
||||
public override Task Any_with_Contains_over_captured_string_array()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Any_with_Contains_over_captured_string_array);
|
||||
|
||||
public override Task Any_with_Contains_over_captured_string_list()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Any_with_Contains_over_captured_string_list);
|
||||
|
||||
public override Task Any_over_List_with_Contains_over_captured_string_array()
|
||||
=> Assert.ThrowsAsync<NotSupportedException>(base.Any_over_List_with_Contains_over_captured_string_array);
|
||||
|
||||
public new class Fixture : FilterTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests;
|
||||
|
||||
public class PineconeIndexKindTests(PineconeIndexKindTests.Fixture fixture)
|
||||
: IndexKindTests<string>(fixture), IClassFixture<PineconeIndexKindTests.Fixture>
|
||||
{
|
||||
// Pinecone does not support index-less searching
|
||||
public override Task Flat() => Assert.ThrowsAsync<NotSupportedException>(base.Flat);
|
||||
|
||||
[ConditionalFact]
|
||||
public virtual Task PGA()
|
||||
=> this.Test("PGA");
|
||||
|
||||
protected override async Task Test(string indexKind)
|
||||
{
|
||||
await base.Test(indexKind);
|
||||
|
||||
// The Pinecone emulator needs some extra time to spawn a new index service
|
||||
// that uses a different distance function.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
public new class Fixture() : IndexKindTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using VectorData.ConformanceTests;
|
||||
using VectorData.ConformanceTests.ModelTests;
|
||||
|
||||
namespace Pinecone.ConformanceTests;
|
||||
|
||||
public class PineconeTestSuiteImplementationTests : TestSuiteImplementationTests
|
||||
{
|
||||
protected override ICollection<Type> IgnoredTestBases { get; } =
|
||||
[
|
||||
// Pinecone does not support multiple vectors
|
||||
typeof(MultiVectorModelTests<>),
|
||||
|
||||
// Hybrid search not supported
|
||||
typeof(HybridSearchTests<>)
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.VectorData;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests.Support;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
public record PineconeAllTypes()
|
||||
{
|
||||
[VectorStoreKey]
|
||||
public string Id { get; set; }
|
||||
|
||||
[VectorStoreData]
|
||||
public bool BoolProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public bool? NullableBoolProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public string StringProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public string? NullableStringProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public int IntProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public int? NullableIntProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public long LongProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public long? NullableLongProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public float FloatProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public float? NullableFloatProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public double DoubleProperty { get; set; }
|
||||
[VectorStoreData]
|
||||
public double? NullableDoubleProperty { get; set; }
|
||||
|
||||
#pragma warning disable CA1819 // Properties should not return arrays
|
||||
[VectorStoreData]
|
||||
public string[] StringArray { get; set; }
|
||||
[VectorStoreData]
|
||||
public string[]? NullableStringArray { get; set; }
|
||||
#pragma warning restore CA1819 // Properties should not return arrays
|
||||
|
||||
[VectorStoreData]
|
||||
public List<string> StringList { get; set; }
|
||||
[VectorStoreData]
|
||||
public List<string>? NullableStringList { get; set; }
|
||||
|
||||
[VectorStoreVector(Dimensions: 8, DistanceFunction = DistanceFunction.DotProductSimilarity)]
|
||||
public ReadOnlyMemory<float>? Embedding { get; set; }
|
||||
|
||||
internal void AssertEqual(PineconeAllTypes other)
|
||||
{
|
||||
Assert.Equal(this.Id, other.Id);
|
||||
Assert.Equal(this.BoolProperty, other.BoolProperty);
|
||||
Assert.Equal(this.NullableBoolProperty, other.NullableBoolProperty);
|
||||
Assert.Equal(this.StringProperty, other.StringProperty);
|
||||
Assert.Equal(this.NullableStringProperty, other.NullableStringProperty);
|
||||
Assert.Equal(this.IntProperty, other.IntProperty);
|
||||
Assert.Equal(this.NullableIntProperty, other.NullableIntProperty);
|
||||
Assert.Equal(this.LongProperty, other.LongProperty);
|
||||
Assert.Equal(this.NullableLongProperty, other.NullableLongProperty);
|
||||
Assert.Equal(this.FloatProperty, other.FloatProperty);
|
||||
Assert.Equal(this.NullableFloatProperty, other.NullableFloatProperty);
|
||||
Assert.Equal(this.DoubleProperty, other.DoubleProperty);
|
||||
Assert.Equal(this.NullableDoubleProperty, other.NullableDoubleProperty);
|
||||
Assert.Equal(this.StringArray, other.StringArray);
|
||||
Assert.Equal(this.NullableStringArray, other.NullableStringArray);
|
||||
Assert.Equal(this.StringList, other.StringList);
|
||||
Assert.Equal(this.NullableStringList, other.NullableStringList);
|
||||
Assert.Equal(this.Embedding!.Value.ToArray(), other.Embedding!.Value.ToArray());
|
||||
}
|
||||
|
||||
internal static VectorStoreCollectionDefinition GetRecordDefinition()
|
||||
=> new()
|
||||
{
|
||||
Properties =
|
||||
[
|
||||
new VectorStoreKeyProperty(nameof(PineconeAllTypes.Id), typeof(string)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.BoolProperty), typeof(bool)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.NullableBoolProperty), typeof(bool?)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.StringProperty), typeof(string)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.NullableStringProperty), typeof(string)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.IntProperty), typeof(int)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.NullableIntProperty), typeof(int?)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.LongProperty), typeof(long)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.NullableLongProperty), typeof(long?)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.FloatProperty), typeof(float)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.NullableFloatProperty), typeof(float?)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.DoubleProperty), typeof(double)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.NullableDoubleProperty), typeof(double?)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.StringArray), typeof(string[])),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.NullableStringArray), typeof(string[])),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.StringList), typeof(List<string>)),
|
||||
new VectorStoreDataProperty(nameof(PineconeAllTypes.NullableStringList), typeof(List<string?>)),
|
||||
new VectorStoreVectorProperty(nameof(PineconeAllTypes.Embedding), typeof(ReadOnlyMemory<float>?), 8) { DistanceFunction = Microsoft.Extensions.VectorData.DistanceFunction.DotProductSimilarity }
|
||||
]
|
||||
};
|
||||
}
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using VectorData.ConformanceTests.Support;
|
||||
|
||||
namespace Pinecone.ConformanceTests.Support;
|
||||
|
||||
public class PineconeFixture : VectorStoreFixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
#pragma warning disable IDE0005 // Using directive is unnecessary.
|
||||
#pragma warning restore IDE0005 // Using directive is unnecessary.
|
||||
using DotNet.Testcontainers.Builders;
|
||||
using DotNet.Testcontainers.Containers;
|
||||
using Humanizer;
|
||||
using Microsoft.SemanticKernel.Connectors.Pinecone;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
|
||||
namespace Pinecone.ConformanceTests.Support;
|
||||
|
||||
#pragma warning disable CA1001 // Type owns disposable fields but is not disposable
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
|
||||
internal sealed class PineconeTestStore : TestStore
|
||||
{
|
||||
// Values taken from https://docs.pinecone.io/guides/operations/local-development
|
||||
// v0.7.0 works with 2.1 client
|
||||
// v1.0.0 works with 3.0 client
|
||||
// We use hardcoded version to avoid breaking changes.
|
||||
private const string Image = "ghcr.io/pinecone-io/pinecone-local:v1.0.0.rc0";
|
||||
private const ushort FirstPort = 5080;
|
||||
private const int IndexServiceCount = 10;
|
||||
|
||||
public static PineconeTestStore Instance { get; } = new();
|
||||
|
||||
private IContainer? _container;
|
||||
private PineconeClient? _client;
|
||||
private ClientOptions? _clientOptions;
|
||||
|
||||
public PineconeClient Client => this._client ?? throw new InvalidOperationException("Not initialized");
|
||||
|
||||
public ClientOptions ClientOptions => this._clientOptions ?? throw new InvalidOperationException("Not initialized");
|
||||
|
||||
public PineconeVectorStore GetVectorStore(PineconeVectorStoreOptions options)
|
||||
=> new(this.Client, options);
|
||||
|
||||
// Pinecone does not support distance functions other than PGA which is always enabled.
|
||||
public override string DefaultIndexKind => "";
|
||||
|
||||
private PineconeTestStore()
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task StartAsync()
|
||||
{
|
||||
this._container = await this.StartContainerAsync();
|
||||
|
||||
Dictionary<int, int> containerToHostPort = Enumerable.Range(FirstPort, IndexServiceCount + 1)
|
||||
.ToDictionary(port => port, port => (int)this._container.GetMappedPublicPort(port));
|
||||
|
||||
UriBuilder baseAddress = new()
|
||||
{
|
||||
Scheme = "http",
|
||||
Host = this._container.Hostname,
|
||||
Port = this._container.GetMappedPublicPort(FirstPort)
|
||||
};
|
||||
|
||||
this._clientOptions = new()
|
||||
{
|
||||
BaseUrl = baseAddress.Uri.ToString(),
|
||||
MaxRetries = 0,
|
||||
IsTlsEnabled = false,
|
||||
GrpcOptions = new()
|
||||
{
|
||||
HttpClient = new(new RedirectHandler(containerToHostPort), disposeHandler: true)
|
||||
{
|
||||
BaseAddress = baseAddress.Uri
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
this._client = new(
|
||||
apiKey: "ForPineconeLocalTheApiKeysAreIgnored",
|
||||
clientOptions: this._clientOptions);
|
||||
|
||||
this.DefaultVectorStore = new PineconeVectorStore(this._client);
|
||||
}
|
||||
|
||||
protected override async Task StopAsync()
|
||||
{
|
||||
if (this._container is not null)
|
||||
{
|
||||
await this._container.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IContainer> StartContainerAsync()
|
||||
{
|
||||
ContainerBuilder builder = new ContainerBuilder("ghcr.io/pinecone-io/pinecone-local:latest")
|
||||
.WithImage(Image)
|
||||
// Pinecone Local will run on port $FirstPort.
|
||||
.WithPortBinding(FirstPort, assignRandomHostPort: true)
|
||||
// We are currently using the default Pinecone port (5080), but we can change it to a random port.
|
||||
// In such case, we are going to need to set the PORT environment variable to the new port.
|
||||
.WithEnvironment("PORT", FirstPort.ToString());
|
||||
|
||||
for (int indexService = 1; indexService <= IndexServiceCount; indexService++)
|
||||
{
|
||||
// And the index services on the following ports.
|
||||
builder = builder.WithPortBinding(FirstPort + indexService, assignRandomHostPort: true);
|
||||
}
|
||||
|
||||
var container = builder.Build();
|
||||
|
||||
await container.StartAsync();
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
// Collection name must contain only ASCII lowercase letters, digits and dashes.
|
||||
// https://docs.pinecone.io/troubleshooting/restrictions-on-index-names
|
||||
public override string AdjustCollectionName(string baseName)
|
||||
=> baseName.Kebaberize();
|
||||
|
||||
private sealed class RedirectHandler : DelegatingHandler
|
||||
{
|
||||
private readonly Dictionary<int, int> _containerToHostPort;
|
||||
|
||||
public RedirectHandler(Dictionary<int, int> portRedirections)
|
||||
: base(new HttpClientHandler())
|
||||
{
|
||||
this._containerToHostPort = portRedirections;
|
||||
}
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
// When "host" argument is not provided for PineconeClient.Index,
|
||||
// it will try to get the host from the Pinecone service.
|
||||
// In the cloud environment it's fine, but with the local emulator
|
||||
// it reports the address with the container port, not the host port.
|
||||
if (request.RequestUri != null && request.RequestUri.IsAbsoluteUri
|
||||
&& request.RequestUri.Host == "localhost"
|
||||
&& this._containerToHostPort.TryGetValue(request.RequestUri.Port, out int hostPort))
|
||||
{
|
||||
UriBuilder builder = new(request.RequestUri)
|
||||
{
|
||||
Port = hostPort
|
||||
};
|
||||
request.RequestUri = builder.Uri;
|
||||
}
|
||||
|
||||
return base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.TypeTests;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests.TypeTests;
|
||||
|
||||
public class PineconeDataTypeTests(PineconeDataTypeTests.Fixture fixture)
|
||||
: DataTypeTests<string, DataTypeTests<string>.DefaultRecord>(fixture), IClassFixture<PineconeDataTypeTests.Fixture>
|
||||
{
|
||||
public new class Fixture : DataTypeTests<string, DataTypeTests<string>.DefaultRecord>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
|
||||
// Pincone does not support null checks in vector search pre-filters
|
||||
public override bool IsNullFilteringSupported => false;
|
||||
|
||||
public override Type[] UnsupportedDefaultTypes { get; } =
|
||||
[
|
||||
typeof(byte),
|
||||
typeof(short),
|
||||
typeof(decimal),
|
||||
typeof(Guid),
|
||||
typeof(DateTime),
|
||||
typeof(DateTimeOffset),
|
||||
typeof(string[]), // TODO: Error with gRPC status code 3
|
||||
|
||||
#if NET
|
||||
typeof(DateOnly),
|
||||
typeof(TimeOnly)
|
||||
#endif
|
||||
];
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.TypeTests;
|
||||
using Xunit;
|
||||
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
|
||||
namespace Pinecone.ConformanceTests.TypeTests;
|
||||
|
||||
public class PineconeEmbeddingTypeTests(PineconeEmbeddingTypeTests.Fixture fixture)
|
||||
: EmbeddingTypeTests<string>(fixture), IClassFixture<PineconeEmbeddingTypeTests.Fixture>
|
||||
{
|
||||
public new class Fixture : EmbeddingTypeTests<string>.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Pinecone.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.Support;
|
||||
using VectorData.ConformanceTests.TypeTests;
|
||||
using VectorData.ConformanceTests.Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace Pinecone.ConformanceTests.TypeTests;
|
||||
|
||||
public class PineconeKeyTypeTests(PineconeKeyTypeTests.Fixture fixture)
|
||||
: KeyTypeTests(fixture), IClassFixture<PineconeKeyTypeTests.Fixture>
|
||||
{
|
||||
[ConditionalFact]
|
||||
public virtual Task String() => this.Test<string>("foo", "bar");
|
||||
|
||||
public new class Fixture : KeyTypeTests.Fixture
|
||||
{
|
||||
public override TestStore TestStore => PineconeTestStore.Instance;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user