chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user