624 lines
25 KiB
C#
624 lines
25 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using Microsoft.Extensions.AI;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.VectorData;
|
|
using VectorData.ConformanceTests.Support;
|
|
using VectorData.ConformanceTests.Xunit;
|
|
using Xunit;
|
|
|
|
namespace VectorData.ConformanceTests;
|
|
|
|
#pragma warning disable CA2000 // Don't actually need to dispose FakeEmbeddingGenerator
|
|
#pragma warning disable CS8605 // Unboxing a possibly null value.
|
|
|
|
public abstract class EmbeddingGenerationTests<TKey>(EmbeddingGenerationTests<TKey>.StringVectorFixture stringVectorFixture, EmbeddingGenerationTests<TKey>.RomOfFloatVectorFixture romOfFloatVectorFixture)
|
|
where TKey : notnull
|
|
{
|
|
#region Search
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_property_generator()
|
|
{
|
|
// Property level: embedding generators are defined at all levels. The property generator should take precedence.
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
var result = await collection.SearchAsync("[1, 1, 0]", top: 1).SingleAsync();
|
|
|
|
Assert.Equal("Property ([1, 1, 3])", result.Record.Text);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_property_generator_dynamic()
|
|
{
|
|
// Property level: embedding generators are defined at all levels. The property generator should take precedence.
|
|
var collection = this.GetDynamicCollection(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
var result = await collection.SearchAsync("[1, 1, 0]", top: 1).SingleAsync();
|
|
|
|
Assert.Equal("Property ([1, 1, 3])", result.Record[nameof(Record.Text)]);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_collection_generator()
|
|
{
|
|
// Collection level: embedding generators are defined at the collection and store level - the collection generator should take precedence.
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: true, propertyGenerator: false);
|
|
|
|
var result = await collection.SearchAsync("[1, 1, 0]", top: 1).SingleAsync();
|
|
|
|
Assert.Equal("Collection ([1, 1, 2])", result.Record.Text);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_store_generator()
|
|
{
|
|
// Store level: an embedding generator is defined at the store level only.
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: false, propertyGenerator: false);
|
|
|
|
var result = await collection.SearchAsync("[1, 1, 0]", top: 1).SingleAsync();
|
|
|
|
Assert.Equal("Store ([1, 1, 1])", result.Record.Text);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_store_dependency_injection()
|
|
{
|
|
foreach (var registrationDelegate in stringVectorFixture.DependencyInjectionStoreRegistrationDelegates)
|
|
{
|
|
IServiceCollection serviceCollection = new ServiceCollection();
|
|
|
|
serviceCollection.AddSingleton<IEmbeddingGenerator>(new FakeEmbeddingGenerator(replaceLast: 1));
|
|
registrationDelegate(serviceCollection);
|
|
|
|
await using var serviceProvider = serviceCollection.BuildServiceProvider();
|
|
|
|
var vectorStore = serviceProvider.GetRequiredService<VectorStore>();
|
|
var collection = vectorStore.GetCollection<TKey, Record>(stringVectorFixture.CollectionName, stringVectorFixture.CreateRecordDefinition());
|
|
|
|
var result = await collection.SearchAsync("[1, 1, 0]", top: 1).SingleAsync();
|
|
|
|
Assert.Equal("Store ([1, 1, 1])", result.Record.Text);
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_collection_dependency_injection()
|
|
{
|
|
foreach (var registrationDelegate in stringVectorFixture.DependencyInjectionCollectionRegistrationDelegates)
|
|
{
|
|
IServiceCollection serviceCollection = new ServiceCollection();
|
|
|
|
serviceCollection.AddSingleton<IEmbeddingGenerator>(new FakeEmbeddingGenerator(replaceLast: 1));
|
|
registrationDelegate(serviceCollection);
|
|
|
|
await using var serviceProvider = serviceCollection.BuildServiceProvider();
|
|
|
|
var collection = serviceProvider.GetRequiredService<VectorStoreCollection<TKey, RecordWithAttributes>>();
|
|
|
|
var result = await collection.SearchAsync("[1, 1, 0]", top: 1).SingleAsync();
|
|
|
|
Assert.Equal("Store ([1, 1, 1])", result.Record.Text);
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_custom_input_type()
|
|
{
|
|
var recordDefinition = new VectorStoreCollectionDefinition()
|
|
{
|
|
Properties = stringVectorFixture.CreateRecordDefinition().Properties
|
|
.Select(p => p is VectorStoreVectorProperty vectorProperty
|
|
? new VectorStoreVectorProperty<Customer>(nameof(Record.Embedding), dimensions: 3)
|
|
{
|
|
DistanceFunction = stringVectorFixture.DefaultDistanceFunction,
|
|
IndexKind = stringVectorFixture.DefaultIndexKind
|
|
}
|
|
: p)
|
|
.ToList()
|
|
};
|
|
|
|
var collection = stringVectorFixture.GetCollection<RecordWithCustomerVectorProperty>(
|
|
stringVectorFixture.CreateVectorStore(new FakeCustomerEmbeddingGenerator([1, 1, 1])),
|
|
stringVectorFixture.CollectionName,
|
|
recordDefinition);
|
|
|
|
var result = await collection.SearchAsync(new Customer(), top: 1).SingleAsync();
|
|
|
|
Assert.Equal("Store ([1, 1, 1])", result.Record.Text);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_search_only_embedding_generation()
|
|
{
|
|
var collection = romOfFloatVectorFixture.GetCollection<RecordWithRomOfFloatVectorProperty>(
|
|
romOfFloatVectorFixture.CreateVectorStore(new FakeEmbeddingGenerator()),
|
|
romOfFloatVectorFixture.CollectionName,
|
|
romOfFloatVectorFixture.CreateRecordDefinition());
|
|
|
|
var result = await collection.SearchAsync("[1, 1, 1]", top: 1).SingleAsync();
|
|
|
|
Assert.Equal("Store ([1, 1, 1])", result.Record.Text);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual 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 for string.
|
|
var collection = stringVectorFixture.GetCollection<RawRecord>(stringVectorFixture.TestStore.DefaultVectorStore, stringVectorFixture.CollectionName + "withoutgenerator");
|
|
|
|
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 class RawRecord
|
|
{
|
|
[VectorStoreKey]
|
|
public TKey Key { get; set; } = default!;
|
|
[VectorStoreVector(Dimensions: 3)]
|
|
public ReadOnlyMemory<float> Embedding { get; set; }
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_incompatible_generator_throws()
|
|
{
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
// We have a generator configured for string, not int.
|
|
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => collection.SearchAsync(8, top: 1).ToListAsync().AsTask());
|
|
|
|
Assert.Equal($"An input of type 'int' was provided, but an incompatible embedding generator of type '{nameof(FakeEmbeddingGenerator)}' was configured.", exception.Message);
|
|
}
|
|
|
|
#endregion Search
|
|
|
|
#region Upsert
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task UpsertAsync()
|
|
{
|
|
var counter = stringVectorFixture.GenerateNextCounter();
|
|
|
|
var record = new Record
|
|
{
|
|
Key = stringVectorFixture.GenerateNextKey<TKey>(),
|
|
Embedding = "[100, 1, 0]",
|
|
Counter = counter,
|
|
Text = nameof(UpsertAsync)
|
|
};
|
|
|
|
// Property level: embedding generators are defined at all levels. The property generator should take precedence.
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
await collection.UpsertAsync(record).ConfigureAwait(false);
|
|
|
|
await stringVectorFixture.TestStore.WaitForDataAsync(collection, 1, filter: r => r.Counter == counter);
|
|
|
|
var result = await collection.SearchAsync(new ReadOnlyMemory<float>([100, 1, 3]), top: 1).SingleAsync();
|
|
Assert.Equal(counter, result.Record.Counter);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task UpsertAsync_dynamic()
|
|
{
|
|
var counter = stringVectorFixture.GenerateNextCounter();
|
|
|
|
var record = new Dictionary<string, object?>
|
|
{
|
|
[nameof(Record.Key)] = stringVectorFixture.GenerateNextKey<TKey>(),
|
|
[nameof(Record.Embedding)] = "[200, 1, 0]",
|
|
[nameof(Record.Counter)] = counter,
|
|
[nameof(Record.Text)] = nameof(UpsertAsync_dynamic)
|
|
};
|
|
|
|
// Property level: embedding generators are defined at all levels. The property generator should take precedence.
|
|
var collection = this.GetDynamicCollection(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
await collection.UpsertAsync(record).ConfigureAwait(false);
|
|
|
|
await stringVectorFixture.TestStore.WaitForDataAsync(collection, 1, filter: r => (int)r[nameof(Record.Counter)] == counter);
|
|
|
|
var result = await collection.SearchAsync(new ReadOnlyMemory<float>([200, 1, 3]), top: 1).SingleAsync();
|
|
Assert.Equal(counter, result.Record[nameof(Record.Counter)]);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task UpsertAsync_batch()
|
|
{
|
|
var (counter1, counter2) = (stringVectorFixture.GenerateNextCounter(), stringVectorFixture.GenerateNextCounter());
|
|
|
|
Record[] records =
|
|
[
|
|
new()
|
|
{
|
|
Key = stringVectorFixture.GenerateNextKey<TKey>(),
|
|
Embedding = "[300, 1, 0]",
|
|
Counter = counter1,
|
|
Text = nameof(UpsertAsync_batch) + "1"
|
|
},
|
|
new()
|
|
{
|
|
Key = stringVectorFixture.GenerateNextKey<TKey>(),
|
|
Embedding = "[400, 1, 0]",
|
|
Counter = counter2,
|
|
Text = nameof(UpsertAsync_batch) + "2"
|
|
}
|
|
];
|
|
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
await collection.UpsertAsync(records).ConfigureAwait(false);
|
|
|
|
await stringVectorFixture.TestStore.WaitForDataAsync(collection, 2, filter: r => (int)r.Counter == counter1 || (int)r.Counter == counter2);
|
|
|
|
var result = await collection.SearchAsync(new ReadOnlyMemory<float>([300, 1, 3]), top: 1).SingleAsync();
|
|
Assert.Equal(counter1, result.Record.Counter);
|
|
|
|
result = await collection.SearchAsync(new ReadOnlyMemory<float>([400, 1, 3]), top: 1).SingleAsync();
|
|
Assert.Equal(counter2, result.Record.Counter);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task UpsertAsync_batch_dynamic()
|
|
{
|
|
var (counter1, counter2) = (stringVectorFixture.GenerateNextCounter(), stringVectorFixture.GenerateNextCounter());
|
|
|
|
Dictionary<string, object?>[] records =
|
|
[
|
|
new()
|
|
{
|
|
[nameof(Record.Key)] = stringVectorFixture.GenerateNextKey<TKey>(),
|
|
[nameof(Record.Embedding)] = "[500, 1, 0]",
|
|
[nameof(Record.Counter)] = counter1,
|
|
[nameof(Record.Text)] = nameof(UpsertAsync_batch_dynamic) + "1"
|
|
},
|
|
new()
|
|
{
|
|
[nameof(Record.Key)] = stringVectorFixture.GenerateNextKey<TKey>(),
|
|
[nameof(Record.Embedding)] = "[600, 1, 0]",
|
|
[nameof(Record.Counter)] = counter2,
|
|
[nameof(Record.Text)] = nameof(UpsertAsync_batch_dynamic) + "2"
|
|
}
|
|
];
|
|
|
|
var collection = this.GetDynamicCollection(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
await collection.UpsertAsync(records).ConfigureAwait(false);
|
|
|
|
await stringVectorFixture.TestStore.WaitForDataAsync(collection, 2, filter: r => (int)r[nameof(Record.Counter)] == counter1 || (int)r[nameof(Record.Counter)] == counter2);
|
|
|
|
var result = await collection.SearchAsync(new ReadOnlyMemory<float>([500, 1, 3]), top: 1).SingleAsync();
|
|
Assert.Equal(counter1, result.Record[nameof(Record.Counter)]);
|
|
|
|
result = await collection.SearchAsync(new ReadOnlyMemory<float>([600, 1, 3]), top: 1).SingleAsync();
|
|
Assert.Equal(counter2, result.Record[nameof(Record.Counter)]);
|
|
}
|
|
|
|
#endregion Upsert
|
|
|
|
#region IncludeVectors
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task SearchAsync_with_IncludeVectors_throws()
|
|
{
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
var exception = await Assert.ThrowsAsync<NotSupportedException>(() => collection.SearchAsync("[1, 0, 0]", top: 1, new() { IncludeVectors = true }).ToListAsync().AsTask());
|
|
|
|
Assert.Equal("When an embedding generator is configured, `Include Vectors` cannot be enabled.", exception.Message);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task GetAsync_with_IncludeVectors_throws()
|
|
{
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
var exception = await Assert.ThrowsAsync<NotSupportedException>(() => collection.GetAsync(stringVectorFixture.TestData[0].Key, new() { IncludeVectors = true }));
|
|
|
|
Assert.Equal("When an embedding generator is configured, `Include Vectors` cannot be enabled.", exception.Message);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public virtual async Task GetAsync_enumerable_with_IncludeVectors_throws()
|
|
{
|
|
var collection = this.GetCollection<Record>(storeGenerator: true, collectionGenerator: true, propertyGenerator: true);
|
|
|
|
var exception = await Assert.ThrowsAsync<NotSupportedException>(() =>
|
|
collection.GetAsync(
|
|
[stringVectorFixture.TestData[0].Key, stringVectorFixture.TestData[1].Key],
|
|
new() { IncludeVectors = true })
|
|
.ToListAsync().AsTask());
|
|
|
|
Assert.Equal("When an embedding generator is configured, `Include Vectors` cannot be enabled.", exception.Message);
|
|
}
|
|
|
|
#endregion IncludeVectors
|
|
|
|
#region Support
|
|
|
|
public class Record : TestRecord<TKey>
|
|
{
|
|
public string? Embedding { get; set; }
|
|
|
|
public int Counter { get; set; }
|
|
public string? Text { get; set; }
|
|
}
|
|
|
|
public class RecordWithAttributes
|
|
{
|
|
[VectorStoreKey]
|
|
public TKey Key { get; set; } = default!;
|
|
|
|
[VectorStoreVector(Dimensions: 3)]
|
|
public string? Embedding { get; set; }
|
|
|
|
[VectorStoreData(IsIndexed = true)]
|
|
public int Counter { get; set; }
|
|
|
|
[VectorStoreData]
|
|
public string? Text { get; set; }
|
|
}
|
|
|
|
public class RecordWithCustomerVectorProperty
|
|
{
|
|
public TKey Key { get; set; } = default!;
|
|
public Customer? Embedding { get; set; }
|
|
|
|
public int Counter { get; set; }
|
|
public string? Text { get; set; }
|
|
}
|
|
|
|
public class RecordWithRomOfFloatVectorProperty : TestRecord<TKey>
|
|
{
|
|
public ReadOnlyMemory<float> Embedding { get; set; }
|
|
|
|
public int Counter { get; set; }
|
|
public string? Text { get; set; }
|
|
}
|
|
|
|
public class Customer
|
|
{
|
|
public string? FirstName { get; set; }
|
|
public string? LastName { get; set; }
|
|
}
|
|
|
|
private VectorStoreCollection<TKey, TRecord> GetCollection<TRecord>(
|
|
bool storeGenerator = false,
|
|
bool collectionGenerator = false,
|
|
bool propertyGenerator = false)
|
|
where TRecord : class
|
|
{
|
|
var (vectorStore, recordDefinition) = this.GetStoreAndRecordDefinition(storeGenerator, collectionGenerator, propertyGenerator);
|
|
|
|
return stringVectorFixture.GetCollection<TRecord>(vectorStore, stringVectorFixture.CollectionName, recordDefinition);
|
|
}
|
|
|
|
private VectorStoreCollection<object, Dictionary<string, object?>> GetDynamicCollection(
|
|
bool storeGenerator = false,
|
|
bool collectionGenerator = false,
|
|
bool propertyGenerator = false)
|
|
{
|
|
var (vectorStore, recordDefinition) = this.GetStoreAndRecordDefinition(storeGenerator, collectionGenerator, propertyGenerator);
|
|
|
|
return stringVectorFixture.GetDynamicCollection(vectorStore, stringVectorFixture.CollectionName, recordDefinition);
|
|
}
|
|
|
|
private (VectorStore, VectorStoreCollectionDefinition) GetStoreAndRecordDefinition(
|
|
bool storeGenerator = false,
|
|
bool collectionGenerator = false,
|
|
bool propertyGenerator = false)
|
|
{
|
|
var recordDefinition = stringVectorFixture.CreateRecordDefinition();
|
|
|
|
if (propertyGenerator)
|
|
{
|
|
foreach (var vectorProperty in recordDefinition.Properties.OfType<VectorStoreVectorProperty>())
|
|
{
|
|
vectorProperty.EmbeddingGenerator = new FakeEmbeddingGenerator(replaceLast: 3);
|
|
}
|
|
}
|
|
|
|
if (collectionGenerator)
|
|
{
|
|
recordDefinition.EmbeddingGenerator = new FakeEmbeddingGenerator(replaceLast: 2);
|
|
}
|
|
|
|
var vectorStore = stringVectorFixture.CreateVectorStore(storeGenerator ? new FakeEmbeddingGenerator(replaceLast: 1) : null);
|
|
|
|
return (vectorStore, recordDefinition);
|
|
}
|
|
|
|
public abstract class StringVectorFixture : VectorStoreCollectionFixture<TKey, Record>
|
|
{
|
|
private int _counter;
|
|
|
|
protected override string CollectionNameBase => nameof(EmbeddingGenerationTests<int>);
|
|
|
|
public override VectorStoreCollectionDefinition CreateRecordDefinition()
|
|
=> new()
|
|
{
|
|
Properties =
|
|
[
|
|
new VectorStoreKeyProperty(nameof(Record.Key), typeof(TKey)),
|
|
new VectorStoreVectorProperty(nameof(Record.Embedding), typeof(string), dimensions: 3)
|
|
{
|
|
DistanceFunction = this.DefaultDistanceFunction,
|
|
IndexKind = this.DefaultIndexKind
|
|
},
|
|
|
|
new VectorStoreDataProperty(nameof(Record.Counter), typeof(int)) { IsIndexed = true },
|
|
new VectorStoreDataProperty(nameof(Record.Text), typeof(string))
|
|
],
|
|
EmbeddingGenerator = new FakeEmbeddingGenerator()
|
|
};
|
|
|
|
protected override List<Record> BuildTestData() =>
|
|
[
|
|
new()
|
|
{
|
|
Key = this.GenerateNextKey<TKey>(),
|
|
Embedding = "[1, 1, 1]",
|
|
Counter = this.GenerateNextCounter(),
|
|
Text = "Store ([1, 1, 1])"
|
|
},
|
|
new()
|
|
{
|
|
Key = this.GenerateNextKey<TKey>(),
|
|
Embedding = "[1, 1, 2]",
|
|
Counter = this.GenerateNextCounter(),
|
|
Text = "Collection ([1, 1, 2])"
|
|
},
|
|
new()
|
|
{
|
|
Key = this.GenerateNextKey<TKey>(),
|
|
Embedding = "[1, 1, 3]",
|
|
Counter = this.GenerateNextCounter(),
|
|
Text = "Property ([1, 1, 3])"
|
|
}
|
|
];
|
|
|
|
public virtual VectorStoreCollection<TKey, TRecord> GetCollection<TRecord>(
|
|
VectorStore vectorStore,
|
|
string collectionName,
|
|
VectorStoreCollectionDefinition? recordDefinition = null)
|
|
where TRecord : class
|
|
=> vectorStore.GetCollection<TKey, TRecord>(collectionName, recordDefinition);
|
|
|
|
public virtual VectorStoreCollection<object, Dictionary<string, object?>> GetDynamicCollection(
|
|
VectorStore vectorStore,
|
|
string collectionName,
|
|
VectorStoreCollectionDefinition recordDefinition)
|
|
=> vectorStore.GetDynamicCollection(collectionName, recordDefinition);
|
|
|
|
public abstract VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGenerator = null);
|
|
|
|
public abstract Func<IServiceCollection, IServiceCollection>[] DependencyInjectionStoreRegistrationDelegates { get; }
|
|
public abstract Func<IServiceCollection, IServiceCollection>[] DependencyInjectionCollectionRegistrationDelegates { get; }
|
|
|
|
public virtual int GenerateNextCounter()
|
|
=> Interlocked.Increment(ref this._counter);
|
|
}
|
|
|
|
public abstract class RomOfFloatVectorFixture : VectorStoreCollectionFixture<TKey, RecordWithRomOfFloatVectorProperty>
|
|
{
|
|
private int _counter;
|
|
|
|
protected override string CollectionNameBase => "SearchOnlyEmbeddingGenerationTests";
|
|
|
|
public override VectorStoreCollectionDefinition CreateRecordDefinition()
|
|
=> new()
|
|
{
|
|
Properties =
|
|
[
|
|
new VectorStoreKeyProperty(nameof(RecordWithRomOfFloatVectorProperty.Key), typeof(TKey)),
|
|
new VectorStoreVectorProperty(nameof(RecordWithRomOfFloatVectorProperty.Embedding), typeof(ReadOnlyMemory<float>), dimensions: 3)
|
|
{
|
|
DistanceFunction = this.DefaultDistanceFunction,
|
|
IndexKind = this.DefaultIndexKind
|
|
},
|
|
|
|
new VectorStoreDataProperty(nameof(RecordWithRomOfFloatVectorProperty.Counter), typeof(int)) { IsIndexed = true },
|
|
new VectorStoreDataProperty(nameof(RecordWithRomOfFloatVectorProperty.Text), typeof(string))
|
|
],
|
|
EmbeddingGenerator = new FakeEmbeddingGenerator()
|
|
};
|
|
|
|
protected override List<RecordWithRomOfFloatVectorProperty> BuildTestData() =>
|
|
[
|
|
new()
|
|
{
|
|
Key = this.GenerateNextKey<TKey>(),
|
|
Embedding = new ReadOnlyMemory<float>([1, 1, 1]),
|
|
Counter = this.GenerateNextCounter(),
|
|
Text = "Store ([1, 1, 1])",
|
|
},
|
|
new()
|
|
{
|
|
Key = this.GenerateNextKey<TKey>(),
|
|
Embedding = new ReadOnlyMemory<float>([1, 1, 2]),
|
|
Counter = this.GenerateNextCounter(),
|
|
Text = "Collection ([1, 1, 2])"
|
|
},
|
|
new()
|
|
{
|
|
Key = this.GenerateNextKey<TKey>(),
|
|
Embedding = new ReadOnlyMemory<float>([1, 1, 3]),
|
|
Counter = this.GenerateNextCounter(),
|
|
Text = "Property ([1, 1, 3])"
|
|
}
|
|
];
|
|
|
|
public virtual VectorStoreCollection<TKey, TRecord> GetCollection<TRecord>(
|
|
VectorStore vectorStore,
|
|
string collectionName,
|
|
VectorStoreCollectionDefinition? recordDefinition = null)
|
|
where TRecord : class
|
|
=> vectorStore.GetCollection<TKey, TRecord>(collectionName, recordDefinition);
|
|
|
|
public virtual VectorStoreCollection<object, Dictionary<string, object?>> GetDynamicCollection(
|
|
VectorStore vectorStore,
|
|
string collectionName,
|
|
VectorStoreCollectionDefinition recordDefinition)
|
|
=> vectorStore.GetDynamicCollection(collectionName, recordDefinition);
|
|
|
|
public abstract VectorStore CreateVectorStore(IEmbeddingGenerator? embeddingGenerator = null);
|
|
|
|
public abstract Func<IServiceCollection, IServiceCollection>[] DependencyInjectionStoreRegistrationDelegates { get; }
|
|
public abstract Func<IServiceCollection, IServiceCollection>[] DependencyInjectionCollectionRegistrationDelegates { get; }
|
|
|
|
public virtual int GenerateNextCounter()
|
|
=> Interlocked.Increment(ref this._counter);
|
|
}
|
|
|
|
protected sealed class FakeEmbeddingGenerator(int? replaceLast = null) : 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)
|
|
{
|
|
var vector = value.TrimStart('[').TrimEnd(']').Split(',').Select(s => float.Parse(s.Trim())).ToArray();
|
|
|
|
if (replaceLast is not null)
|
|
{
|
|
vector[vector.Length - 1] = replaceLast.Value;
|
|
}
|
|
|
|
results.Add(new Embedding<float>(vector));
|
|
}
|
|
|
|
return Task.FromResult(results);
|
|
}
|
|
|
|
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
=> null;
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
|
|
private sealed class FakeCustomerEmbeddingGenerator(float[] embedding) : IEmbeddingGenerator<Customer, Embedding<float>>
|
|
{
|
|
public Task<GeneratedEmbeddings<Embedding<float>>> GenerateAsync(IEnumerable<Customer> values, EmbeddingGenerationOptions? options = null, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(new GeneratedEmbeddings<Embedding<float>> { new(embedding) });
|
|
|
|
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
=> null;
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
|
|
#endregion Support
|
|
}
|