diff --git a/src/Meilisearch/Converters/EmbedderSourceConverter.cs b/src/Meilisearch/Converters/EmbedderSourceConverter.cs new file mode 100644 index 00000000..4d6e004e --- /dev/null +++ b/src/Meilisearch/Converters/EmbedderSourceConverter.cs @@ -0,0 +1,52 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Meilisearch.Converters +{ + internal class EmbedderSourceConverter : JsonConverter + { + public override EmbedderSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var enumValue = reader.GetString(); + if (Enum.TryParse(enumValue, true, out var embedderSource)) + { + return embedderSource; + } + + throw new JsonException($"Invalid EmbedderSource value: '{enumValue}'."); + } + + throw new JsonException($"Expected string for EmbedderSource, but found {reader.TokenType}."); + } + + public override void Write(Utf8JsonWriter writer, EmbedderSource value, JsonSerializerOptions options) + { + string source; + switch (value) + { + case EmbedderSource.OpenAi: + source = "openAi"; + break; + case EmbedderSource.HuggingFace: + source = "huggingFace"; + break; + case EmbedderSource.Ollama: + source = "ollama"; + break; + case EmbedderSource.Rest: + source = "rest"; + break; + case EmbedderSource.UserProvided: + source = "userProvided"; + break; + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + + writer.WriteStringValue(source); + } + } +} diff --git a/src/Meilisearch/Embedder.cs b/src/Meilisearch/Embedder.cs new file mode 100644 index 00000000..d35b34a9 --- /dev/null +++ b/src/Meilisearch/Embedder.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Meilisearch +{ + /// + /// Embedder configuration. + /// + public class Embedder + { + /// + /// Gets or sets the source. + /// + [JsonPropertyName("source")] + public EmbedderSource Source { get; set; } + + /// + /// Gets or sets the URL. + /// + [JsonPropertyName("url")] + public string Url { get; set; } + + /// + /// Gets or sets the API key. + /// + [JsonPropertyName("apiKey")] + public string ApiKey { get; set; } + + /// + /// Gets or sets the model. + /// + [JsonPropertyName("model")] + public string Model { get; set; } + + /// + /// Gets or sets the document template. + /// + [JsonPropertyName("documentTemplate")] + public string DocumentTemplate { get; set; } + + /// + /// Gets or sets the document template max bytes. + /// + [JsonPropertyName("documentTemplateMaxBytes")] + public int? DocumentTemplateMaxBytes { get; set; } + + /// + /// Gets or sets the dimensions. + /// + [JsonPropertyName("dimensions")] + public int? Dimensions { get; set; } + + /// + /// Gets or sets the revision. + /// + [JsonPropertyName("revision")] + public string Revision { get; set; } + + /// + /// Gets or sets the distribution. + /// + [JsonPropertyName("distribution")] + public EmbedderDistribution Distribution { get; set; } + + /// + /// Gets or sets the request. + /// + [JsonPropertyName("request")] + public Dictionary Request { get; set; } + + /// + /// Gets or sets the response. + /// + [JsonPropertyName("response")] + public Dictionary Response { get; set; } + + /// + /// Gets or sets whether the vectors should be compressed. + /// + [JsonPropertyName("binaryQuantized")] + public bool? BinaryQuantized { get; set; } + } +} diff --git a/src/Meilisearch/EmbedderDistribution.cs b/src/Meilisearch/EmbedderDistribution.cs new file mode 100644 index 00000000..b4df8c3e --- /dev/null +++ b/src/Meilisearch/EmbedderDistribution.cs @@ -0,0 +1,67 @@ +using System; +using System.Text.Json.Serialization; + +namespace Meilisearch +{ + /// + /// Embedder distribution. + /// + public class EmbedderDistribution + { + private double _mean; + private double _sigma; + + /// + /// Creates a new instance of . + /// + /// Mean value between 0 and 1. + /// Sigma value between 0 and 1. + public EmbedderDistribution(double mean, double sigma) + { + Mean = mean; + Sigma = sigma; + } + + /// + /// Gets or sets the mean. + /// + [JsonPropertyName("mean")] + public double Mean + { + get => _mean; + set + { + if (value < 0 || value > 1) + { + throw new ArgumentOutOfRangeException(nameof(Mean), "Mean must be between 0 and 1."); + } + + _mean = value; + } + } + + /// + /// Gets or sets the sigma. + /// + [JsonPropertyName("sigma")] + public double Sigma + { + get => _sigma; + set + { + if (value < 0 || value > 1) + { + throw new ArgumentOutOfRangeException(nameof(Sigma), "Sigma must be between 0 and 1."); + } + + _sigma = value; + } + } + + /// + /// Creates a new instance of with a uniform distribution. + /// + /// + public static EmbedderDistribution Uniform() => new EmbedderDistribution(0.5, 0.5); + } +} diff --git a/src/Meilisearch/EmbedderSource.cs b/src/Meilisearch/EmbedderSource.cs new file mode 100644 index 00000000..14b835ca --- /dev/null +++ b/src/Meilisearch/EmbedderSource.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; + +using Meilisearch.Converters; + +namespace Meilisearch +{ + /// + /// Embedder source. + /// + [JsonConverter(typeof(EmbedderSourceConverter))] + public enum EmbedderSource + { + /// + /// OpenAI + /// + OpenAi, + + /// + /// Hugging Face + /// + HuggingFace, + + /// + /// Ollama + /// + Ollama, + + /// + /// REST + /// + Rest, + + /// + /// User-provided + /// + UserProvided, + } +} diff --git a/src/Meilisearch/HybridSearch.cs b/src/Meilisearch/HybridSearch.cs new file mode 100644 index 00000000..fcb7ecfb --- /dev/null +++ b/src/Meilisearch/HybridSearch.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Meilisearch +{ + public class HybridSearch + { + /// + /// Gets or sets the embedder. + /// + [JsonPropertyName("embedder")] + public string Embedder { get; set; } + + /// + /// Gets or sets the semantic ratio. + /// + [JsonPropertyName("semanticRatio")] + public double SemanticRatio { get; set; } + } +} diff --git a/src/Meilisearch/Index.Embedders.cs b/src/Meilisearch/Index.Embedders.cs new file mode 100644 index 00000000..c050eeb6 --- /dev/null +++ b/src/Meilisearch/Index.Embedders.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; + +using Meilisearch.Extensions; + +namespace Meilisearch +{ + public partial class Index + { + /// + /// Gets the embedders setting. + /// + /// The cancellation token for this call. + /// Returns the embedders setting. + public async Task> GetEmbeddersAsync(CancellationToken cancellationToken = default) + { + return await _http + .GetFromJsonAsync>( + $"indexes/{Uid}/settings/embedders", + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Updates the embedders setting. + /// + /// Collection of embedders. + /// The cancellation token for this call. + /// Returns the task info of the asynchronous task. + public async Task UpdateEmbeddersAsync(Dictionary embedders, CancellationToken cancellationToken = default) + { + var responseMessage = + await _http.PatchAsJsonAsync( + $"indexes/{Uid}/settings/embedders", + embedders, + Constants.JsonSerializerOptionsRemoveNulls, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + return await responseMessage.Content + .ReadFromJsonAsync(cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Resets the embedders setting. + /// + /// The cancellation token for this call. + /// Returns the task info of the asynchronous task. + public async Task ResetEmbeddersAsync(CancellationToken cancellationToken = default) + { + var response = await _http + .DeleteAsync($"indexes/{Uid}/settings/embedders", cancellationToken) + .ConfigureAwait(false); + + return await response.Content + .ReadFromJsonAsync(cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + } +} diff --git a/src/Meilisearch/Index.SimilarDocuments.cs b/src/Meilisearch/Index.SimilarDocuments.cs new file mode 100644 index 00000000..7fcbb020 --- /dev/null +++ b/src/Meilisearch/Index.SimilarDocuments.cs @@ -0,0 +1,33 @@ +using System.Net.Http.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Meilisearch +{ + public partial class Index + { + /// + /// Search for similar documents. + /// + /// The query to search for similar documents. + /// The cancellation token for this call. + /// The type of the documents to return. + /// Returns the similar documents. + public async Task> SearchSimilarDocumentsAsync( + SimilarDocumentsQuery query, + CancellationToken cancellationToken = default) + { + var responseMessage = await _http + .PostAsJsonAsync( + $"indexes/{Uid}/similar", + query, + Constants.JsonSerializerOptionsRemoveNulls, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + + return await responseMessage.Content + .ReadFromJsonAsync>(cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + } +} diff --git a/src/Meilisearch/Meilisearch.csproj b/src/Meilisearch/Meilisearch.csproj index 89e5367b..821be2e8 100644 --- a/src/Meilisearch/Meilisearch.csproj +++ b/src/Meilisearch/Meilisearch.csproj @@ -67,5 +67,26 @@ Index.cs + + Index.cs + + + Index.cs + + + Index.cs + + + Index.cs + + + Index.cs + + + Index.cs + + + Index.cs + diff --git a/src/Meilisearch/SearchQuery.cs b/src/Meilisearch/SearchQuery.cs index a4de0e48..99c51265 100644 --- a/src/Meilisearch/SearchQuery.cs +++ b/src/Meilisearch/SearchQuery.cs @@ -21,7 +21,6 @@ public class SearchQuery : SearchQueryBase [JsonPropertyName("limit")] public int? Limit { get; set; } - /// /// Gets or sets hitsPerPage. /// @@ -41,9 +40,27 @@ public class SearchQuery : SearchQueryBase public string Distinct { get; set; } /// - /// Gets or sets rankingScoreThreshold, a number between 0.0 and 1.0. + /// Gets or sets rankingScoreThreshold, a number between 0.0 and 1.0. /// [JsonPropertyName("rankingScoreThreshold")] public decimal? RankingScoreThreshold { get; set; } + + /// + /// Gets or sets the hybrid search settings. + /// + [JsonPropertyName("hybrid")] + public HybridSearch Hybrid { get; set; } + + /// + /// Gets or sets the vector. + /// + [JsonPropertyName("vector")] + public double[] Vector { get; set; } + + /// + /// Gets or sets whether to retrieve vectors. + /// + [JsonPropertyName("retrieveVectors")] + public bool RetrieveVectors { get; set; } } } diff --git a/src/Meilisearch/Settings.cs b/src/Meilisearch/Settings.cs index a926cc88..195af5f8 100644 --- a/src/Meilisearch/Settings.cs +++ b/src/Meilisearch/Settings.cs @@ -103,5 +103,11 @@ public class Settings /// [JsonPropertyName("searchCutoffMs")] public int? SearchCutoffMs { get; set; } + + /// + /// Gets or sets the embeddings attribute. + /// + [JsonPropertyName("embedders")] + public Dictionary Embedders { get; set; } } } diff --git a/src/Meilisearch/SimilarDocumentsQuery.cs b/src/Meilisearch/SimilarDocumentsQuery.cs new file mode 100644 index 00000000..abf78bcc --- /dev/null +++ b/src/Meilisearch/SimilarDocumentsQuery.cs @@ -0,0 +1,85 @@ +using System; +using System.Text.Json.Serialization; + +namespace Meilisearch +{ + /// + /// Search query for similar documents. + /// + public class SimilarDocumentsQuery + { + /// + /// Creates a new instance of the class. + /// + /// + public SimilarDocumentsQuery(string id) + { + if (string.IsNullOrEmpty(id)) + { + throw new ArgumentNullException(nameof(id)); + } + + Id = id; + } + + /// + /// Gets the document id. + /// + [JsonPropertyName("id")] + public string Id { get; } + + /// + /// Gets or sets the embedder. + /// + [JsonPropertyName("embedder")] + public string Embedder { get; set; } + + /// + /// Gets or sets the attributes to retrieve. + /// + [JsonPropertyName("attributesToRetrieve")] + public string[] AttributesToRetrieve { get; set; } = new[] { "*" }; + + /// + /// Gets or sets the offset. + /// + [JsonPropertyName("offset")] + public int Offset { get; set; } = 0; + + /// + /// Gets or sets the limit. + /// + [JsonPropertyName("limit")] + public int Limit { get; set; } = 20; + + /// + /// Gets or sets the filter. + /// + [JsonPropertyName("filter")] + public dynamic Filter { get; set; } + + /// + /// Gets or sets whether to show the ranking score. + /// + [JsonPropertyName("showRankingScore")] + public bool ShowRankingScore { get; set; } + + /// + /// Gets or sets whether to show the ranking score details. + /// + [JsonPropertyName("showRankingScoreDetails")] + public bool ShowRankingScoreDetails { get; set; } + + /// + /// Gets or sets the ranking score threshold. + /// + [JsonPropertyName("rankingScoreThreshold")] + public decimal? RankingScoreThreshold { get; set; } + + /// + /// Gets or sets whether to retrieve the vectors. + /// + [JsonPropertyName("retrieveVectors")] + public bool RetrieveVectors { get; set; } + } +} diff --git a/src/Meilisearch/SimilarDocumentsResult.cs b/src/Meilisearch/SimilarDocumentsResult.cs new file mode 100644 index 00000000..908fc449 --- /dev/null +++ b/src/Meilisearch/SimilarDocumentsResult.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Meilisearch +{ + /// + /// Search result for similar documents. + /// + public class SimilarDocumentsResult + { + /// + /// Creates a new instance of the class. + /// + /// + /// + /// + /// + /// + /// + public SimilarDocumentsResult( + IReadOnlyCollection hits, + string id, + int processingTimeMs, + int offset, + int limit, + int estimatedTotalHits) + { + Hits = hits; + Id = id; + ProcessingTimeMs = processingTimeMs; + Offset = offset; + Limit = limit; + EstimatedTotalHits = estimatedTotalHits; + } + + /// + /// Gets the hits. + /// + [JsonPropertyName("hits")] + public IReadOnlyCollection Hits { get; } + + /// + /// Gets the id. + /// + [JsonPropertyName("id")] + public string Id { get; } + + /// + /// Gets the processing time in milliseconds. + /// + [JsonPropertyName("processingTimeMs")] + public int ProcessingTimeMs { get; } + + /// + /// Gets the offset. + /// + [JsonPropertyName("offset")] + public int Offset { get; } + + /// + /// Gets the limit. + /// + [JsonPropertyName("limit")] + public int Limit { get; } + + /// + /// Gets the estimated total hits. + /// + [JsonPropertyName("estimatedTotalHits")] + public int EstimatedTotalHits { get; } + } +} diff --git a/tests/Meilisearch.Tests/Datasets.cs b/tests/Meilisearch.Tests/Datasets.cs index d4ae2155..e2f57f43 100644 --- a/tests/Meilisearch.Tests/Datasets.cs +++ b/tests/Meilisearch.Tests/Datasets.cs @@ -15,6 +15,7 @@ internal static class Datasets public static readonly string MoviesWithStringIdJsonPath = Path.Combine(BasePath, "movies_with_string_id.json"); public static readonly string MoviesForFacetingJsonPath = Path.Combine(BasePath, "movies_for_faceting.json"); + public static readonly string MoviesForVectorJsonPath = Path.Combine(BasePath, "movies_for_vector.json"); public static readonly string MoviesWithIntIdJsonPath = Path.Combine(BasePath, "movies_with_int_id.json"); public static readonly string MoviesWithInfoJsonPath = Path.Combine(BasePath, "movies_with_info.json"); diff --git a/tests/Meilisearch.Tests/Datasets/movies_for_vector.json b/tests/Meilisearch.Tests/Datasets/movies_for_vector.json new file mode 100644 index 00000000..71087bef --- /dev/null +++ b/tests/Meilisearch.Tests/Datasets/movies_for_vector.json @@ -0,0 +1,32 @@ +[ + { + "title": "Shazam!", + "release_year": 2019, + "id": "287947", + "_vectors": { "manual": [0.8, 0.4, -0.5]} + }, + { + "title": "Captain Marvel", + "release_year": 2019, + "id": "299537", + "_vectors": { "manual": [0.6, 0.8, -0.2] } + }, + { + "title": "Escape Room", + "release_year": 2019, + "id": "522681", + "_vectors": { "manual": [0.1, 0.6, 0.8] } + }, + { + "title": "How to Train Your Dragon: The Hidden World", + "release_year": 2019, + "id": "166428", + "_vectors": { "manual": [0.7, 0.7, -0.4] } + }, + { + "title": "All Quiet on the Western Front", + "release_year": 1930, + "id": "143", + "_vectors": { "manual": [-0.5, 0.3, 0.85] } + } +] diff --git a/tests/Meilisearch.Tests/IndexFixture.cs b/tests/Meilisearch.Tests/IndexFixture.cs index 485a3d79..c31acbb9 100644 --- a/tests/Meilisearch.Tests/IndexFixture.cs +++ b/tests/Meilisearch.Tests/IndexFixture.cs @@ -4,6 +4,8 @@ using System.Text.Json; using System.Threading.Tasks; +using Meilisearch.Tests.Models; + using Xunit; namespace Meilisearch.Tests @@ -109,6 +111,37 @@ public async Task SetUpIndexForFaceting(string indexUid) return index; } + public async Task SetUpIndexForVectorSearch(string indexUid) + { + var index = DefaultClient.Index(indexUid); + + var task = await index.UpdateEmbeddersAsync(new Dictionary + { + { "manual", new Embedder { Source = EmbedderSource.UserProvided, Dimensions = 3 } } + }); + + var finishedTask = await index.WaitForTaskAsync(task.TaskUid); + if (finishedTask.Status != TaskInfoStatus.Succeeded) + { + throw new Exception($"The documents were not added during SetUpIndexForVectorSearch.\n" + + $"Impossible to run the tests.\n" + + $"{JsonSerializer.Serialize(finishedTask.Error)}"); + } + + var products = await JsonFileReader.ReadAsync>(Datasets.MoviesForVectorJsonPath); + task = await index.AddDocumentsAsync(products, primaryKey: "id"); + + finishedTask = await index.WaitForTaskAsync(task.TaskUid); + if (finishedTask.Status != TaskInfoStatus.Succeeded) + { + throw new Exception($"The documents were not added during SetUpIndexForVectorSearch.\n" + + $"Impossible to run the tests.\n" + + $"{JsonSerializer.Serialize(finishedTask.Error)}"); + } + + return index; + } + public async Task SetUpIndexForNestedSearch(string indexUid) { var index = DefaultClient.Index(indexUid); diff --git a/tests/Meilisearch.Tests/Meilisearch.Tests.csproj b/tests/Meilisearch.Tests/Meilisearch.Tests.csproj index d3672a1f..f84b7db6 100644 --- a/tests/Meilisearch.Tests/Meilisearch.Tests.csproj +++ b/tests/Meilisearch.Tests/Meilisearch.Tests.csproj @@ -6,33 +6,35 @@ - 1701;1702;CA1861 + 1701;1702;CA1861 - 1701;1702;CA1861 + 1701;1702;CA1861 - - - - + + + + - + - - - - - - - - + + + + + + + + + + diff --git a/tests/Meilisearch.Tests/Models/VectorMovie.cs b/tests/Meilisearch.Tests/Models/VectorMovie.cs new file mode 100644 index 00000000..7db1d09f --- /dev/null +++ b/tests/Meilisearch.Tests/Models/VectorMovie.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Meilisearch.Tests.Models +{ + public class VectorMovie + { + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } + + [JsonPropertyName("release_year")] + public int ReleaseYear { get; set; } + + [JsonPropertyName("_vectors")] + public Dictionary Vectors { get; set; } + } +} diff --git a/tests/Meilisearch.Tests/SearchTests.cs b/tests/Meilisearch.Tests/SearchTests.cs index 97b89d90..6a861574 100644 --- a/tests/Meilisearch.Tests/SearchTests.cs +++ b/tests/Meilisearch.Tests/SearchTests.cs @@ -3,6 +3,8 @@ using FluentAssertions; +using Meilisearch.Tests.Models; + using Xunit; namespace Meilisearch.Tests @@ -12,6 +14,7 @@ public abstract class SearchTests : IAsyncLifetime where TFixture : In private Index _basicIndex; private Index _nestedIndex; private Index _indexForFaceting; + private Index _indexForVectorSearch; private Index _indexWithIntId; private Index _productIndexForDistinct; private Index _indexForRankingScoreThreshold; @@ -28,6 +31,7 @@ public async Task InitializeAsync() await _fixture.DeleteAllIndexes(); // Test context cleaned for each [Fact] _basicIndex = await _fixture.SetUpBasicIndex("BasicIndex-SearchTests"); _indexForFaceting = await _fixture.SetUpIndexForFaceting("IndexForFaceting-SearchTests"); + _indexForVectorSearch = await _fixture.SetUpIndexForVectorSearch("IndexForVector-SearchTests"); _indexWithIntId = await _fixture.SetUpBasicIndexWithIntId("IndexWithIntId-SearchTests"); _nestedIndex = await _fixture.SetUpIndexForNestedSearch("IndexForNestedDocs-SearchTests"); _productIndexForDistinct = await _fixture.SetUpIndexForDistinctProductsSearch("IndexForDistinctProducts-SearchTests"); @@ -576,5 +580,42 @@ public async Task CustomSearchWithRankingScoreThreshold() movies.Hits.First().Id.Should().Be("13"); movies.Hits.First().Name.Should().Be("Harry Potter"); } + + [Fact] + public async Task CustomSearchWithVector() + { + var searchQuery = new SearchQuery + { + Hybrid = new HybridSearch + { + Embedder = "manual", + SemanticRatio = 1.0f + }, + Vector = new[] { 0.1, 0.6, 0.8 }, + }; + + var movies = await _indexForVectorSearch.SearchAsync(string.Empty, searchQuery); + + Assert.Equal("522681", movies.Hits.First().Id); + Assert.Equal("Escape Room", movies.Hits.First().Title); + } + + [Fact] + public async Task CustomSearchWithSimilarDocuments() + { + var query = new SimilarDocumentsQuery("143") + { + Embedder = "manual" + }; + + var movies = await _indexForVectorSearch.SearchSimilarDocumentsAsync(query); + + Assert.Collection(movies.Hits, + m => Assert.Equal("Escape Room", m.Title), + m => Assert.Equal("Captain Marvel", m.Title), + m => Assert.Equal("How to Train Your Dragon: The Hidden World", m.Title), + m => Assert.Equal("Shazam!", m.Title) + ); + } } } diff --git a/tests/Meilisearch.Tests/SettingsTests.cs b/tests/Meilisearch.Tests/SettingsTests.cs index 395bc54a..87d1b4ca 100644 --- a/tests/Meilisearch.Tests/SettingsTests.cs +++ b/tests/Meilisearch.Tests/SettingsTests.cs @@ -63,7 +63,8 @@ public SettingsTests(TFixture fixture) Pagination = new Pagination { MaxTotalHits = 1000 - } + }, + Embedders = new Dictionary() }; } @@ -663,6 +664,57 @@ public async Task ResetSearchCutoffMsAsync() await AssertGetEquality(_index.GetSearchCutoffMsAsync, _defaultSettings.SearchCutoffMs); } + [Fact] + public async Task GetEmbeddersAsync() + { + await AssertGetEquality(_index.GetEmbeddersAsync, _defaultSettings.Embedders); + } + + [Fact] + public async Task UpdateEmbeddersAsync() + { + var newEmbedders = new Dictionary + { + { + "default", + new Embedder + { + Source = EmbedderSource.HuggingFace, + Model = "BAAI/bge-base-en-v1.5", + DocumentTemplate = "A movie titled '{{doc.name}}' with the following genre {{doc.genre}}", + DocumentTemplateMaxBytes = 400 + } + } + }; + + await AssertUpdateSuccess(_index.UpdateEmbeddersAsync, newEmbedders); + await AssertGetEquality(_index.GetEmbeddersAsync, newEmbedders); + } + + [Fact] + public async Task ResetEmbeddersAsync() + { + var newEmbedders = new Dictionary + { + { + "default", + new Embedder + { + Source = EmbedderSource.HuggingFace, + Model = "BAAI/bge-base-en-v1.5", + DocumentTemplate = "A movie titled '{{doc.name}}' with the following genre {{doc.genre}}", + DocumentTemplateMaxBytes = 400 + } + } + }; + + await AssertUpdateSuccess(_index.UpdateEmbeddersAsync, newEmbedders); + await AssertGetEquality(_index.GetEmbeddersAsync, newEmbedders); + + await AssertResetSuccess(_index.ResetEmbeddersAsync); + await AssertGetEquality(_index.GetEmbeddersAsync, _defaultSettings.Embedders); + } + private static Settings SettingsWithDefaultedNullFields(Settings inputSettings, Settings defaultSettings) { return new Settings @@ -682,7 +734,8 @@ private static Settings SettingsWithDefaultedNullFields(Settings inputSettings, Pagination = inputSettings.Pagination ?? defaultSettings.Pagination, ProximityPrecision = inputSettings.ProximityPrecision ?? defaultSettings.ProximityPrecision, Dictionary = inputSettings.Dictionary ?? defaultSettings.Dictionary, - SearchCutoffMs = inputSettings.SearchCutoffMs ?? defaultSettings.SearchCutoffMs + SearchCutoffMs = inputSettings.SearchCutoffMs ?? defaultSettings.SearchCutoffMs, + Embedders = inputSettings.Embedders ?? defaultSettings.Embedders }; }