diff --git a/src/main/java/io/lettuce/core/search/arguments/VectorFieldArgs.java b/src/main/java/io/lettuce/core/search/arguments/VectorFieldArgs.java index 5a1c7d551..d525a3d11 100644 --- a/src/main/java/io/lettuce/core/search/arguments/VectorFieldArgs.java +++ b/src/main/java/io/lettuce/core/search/arguments/VectorFieldArgs.java @@ -36,6 +36,7 @@ public class VectorFieldArgs extends FieldArgs { * Vector similarity index algorithms. */ public enum Algorithm { + /** * Brute force algorithm. */ @@ -43,7 +44,43 @@ public enum Algorithm { /** * Hierarchical, navigable, small world algorithm. */ - HNSW + HNSW, + /** + * SVS-VAMANA algorithm provides high-performance approximate vector search optimized for specific use cases with + * advanced compression and optimization features. + * + *

+ * Characteristics: + *

+ * + *

+ * Note: This algorithm may have specific requirements and limitations. Consult the Redis documentation for detailed + * usage guidelines. + * + * @since Redis 8.2 + */ + SVS_VAMANA("SVS-VAMANA"); + + private final String redisName; + + Algorithm() { + this.redisName = name(); + } + + Algorithm(String redisName) { + this.redisName = redisName; + } + + @Override + public String toString() { + return redisName; + } + } /** @@ -169,13 +206,32 @@ public Builder flat() { /** * Use the HNSW (hierarchical, navigable, small world) algorithm. - * + * * @return the instance of the {@link Builder} for the purpose of method chaining */ public Builder hnsw() { return algorithm(Algorithm.HNSW); } + /** + * Use the SVS-VAMANA algorithm for high-performance approximate vector search. + * + *

+ * SVS-VAMANA provides advanced features including: + *

+ * + * @return the instance of the {@link Builder} for the purpose of method chaining + * @since Redis 8.2 + */ + public Builder svsVamana() { + return algorithm(Algorithm.SVS_VAMANA); + } + /** * Set the vector data type. * @@ -211,7 +267,7 @@ public Builder distanceMetric(DistanceMetric metric) { /** * Add a custom attribute. - * + * * @param name the attribute name * @param value the attribute value * @return the instance of the {@link Builder} for the purpose of method chaining diff --git a/src/test/java/io/lettuce/core/search/RediSearchVectorIntegrationTests.java b/src/test/java/io/lettuce/core/search/RediSearchVectorIntegrationTests.java index 93da02e29..c78a7b4b6 100644 --- a/src/test/java/io/lettuce/core/search/RediSearchVectorIntegrationTests.java +++ b/src/test/java/io/lettuce/core/search/RediSearchVectorIntegrationTests.java @@ -14,11 +14,14 @@ import io.lettuce.core.RedisURI; import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.codec.RedisCodec; +import io.lettuce.core.search.arguments.AggregateArgs; import io.lettuce.core.search.arguments.CreateArgs; import io.lettuce.core.search.arguments.FieldArgs; import io.lettuce.core.search.arguments.NumericFieldArgs; import io.lettuce.core.search.arguments.SearchArgs; +import io.lettuce.core.search.arguments.SortByArgs; import io.lettuce.core.search.arguments.TagFieldArgs; +import io.lettuce.core.search.SearchReply.SearchResult; import io.lettuce.core.search.arguments.TextFieldArgs; import io.lettuce.core.search.arguments.VectorFieldArgs; import io.lettuce.core.json.JsonParser; @@ -37,7 +40,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import static io.lettuce.TestTags.INTEGRATION_TEST; import static org.assertj.core.api.Assertions.assertThat; @@ -99,6 +105,283 @@ static void teardown() { } } + /** + * Test SVS-VAMANA vector index creation and basic search functionality. This test verifies that SVS-VAMANA algorithm can be + * used to create vector indexes and perform basic vector similarity searches. + */ + @Test + void testSvsVamanaBasicVectorSearch() { + // VAMANA is available since Redis 8.2 + assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.2")); + + String indexName = "svs-vamana-basic-idx"; + + // Create SVS-VAMANA vector field + FieldArgs vectorField = VectorFieldArgs. builder().name("embedding").svsVamana() + .type(VectorFieldArgs.VectorType.FLOAT32).dimensions(4).distanceMetric(VectorFieldArgs.DistanceMetric.COSINE) + .build(); + + FieldArgs nameField = TextFieldArgs. builder().name("name").build(); + + CreateArgs createArgs = CreateArgs. builder().withPrefix("svs:") + .on(CreateArgs.TargetType.HASH).build(); + + // Create index + String result = redis.ftCreate(indexName, createArgs, Arrays.asList(vectorField, nameField)); + assertThat(result).isEqualTo("OK"); + + // Insert test vectors using binary format + Map doc1 = new HashMap<>(); + doc1.put(ByteBuffer.wrap("name".getBytes()), ByteBuffer.wrap("Document 1".getBytes())); + doc1.put(ByteBuffer.wrap("embedding".getBytes()), floatArrayToByteBuffer(new float[] { 1.0f, 0.0f, 0.0f, 0.0f })); + redisBinary.hmset(ByteBuffer.wrap("svs:doc1".getBytes()), doc1); + + Map doc2 = new HashMap<>(); + doc2.put(ByteBuffer.wrap("name".getBytes()), ByteBuffer.wrap("Document 2".getBytes())); + doc2.put(ByteBuffer.wrap("embedding".getBytes()), floatArrayToByteBuffer(new float[] { 0.0f, 1.0f, 0.0f, 0.0f })); + redisBinary.hmset(ByteBuffer.wrap("svs:doc2".getBytes()), doc2); + + Map doc3 = new HashMap<>(); + doc3.put(ByteBuffer.wrap("name".getBytes()), ByteBuffer.wrap("Document 3".getBytes())); + doc3.put(ByteBuffer.wrap("embedding".getBytes()), floatArrayToByteBuffer(new float[] { 0.7071f, 0.7071f, 0.0f, 0.0f })); + redisBinary.hmset(ByteBuffer.wrap("svs:doc3".getBytes()), doc3); + + // Perform vector search using binary query vector + ByteBuffer queryVector = floatArrayToByteBuffer(new float[] { 1.0f, 0.0f, 0.0f, 0.0f }); + ByteBuffer blobKey = ByteBuffer.wrap("query_vec".getBytes()); + SearchArgs searchArgs = SearchArgs. builder() + .param(blobKey, queryVector).build(); + + ByteBuffer indexKey = ByteBuffer.wrap(indexName.getBytes()); + ByteBuffer queryString = ByteBuffer.wrap("*=>[KNN 2 @embedding $query_vec]".getBytes()); + SearchReply searchResult = redisBinary.ftSearch(indexKey, queryString, searchArgs); + + // Verify results + assertThat(searchResult.getCount()).isEqualTo(2); + assertThat(searchResult.getResults()).hasSize(2); + + // First result should be doc1 (exact match) + ByteBuffer nameKey = ByteBuffer.wrap("name".getBytes()); + String firstName = new String(searchResult.getResults().get(0).getFields().get(nameKey).array()); + assertThat(firstName).isEqualTo("Document 1"); + + // Cleanup + redis.ftDropindex(indexName); + } + + /** + * Test SVS-VAMANA vector index with advanced configuration parameters. This test verifies that SVS-VAMANA can be configured + * with construction and search parameters for performance tuning (without compression which requires Redis 8.2+). + */ + @Test + void testSvsVamanaWithAdvancedParameters() { + + // VAMANA is available since Redis 8.2 + assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.2")); + + String indexName = "svs-vamana-advanced-idx"; + + // Create SVS-VAMANA vector field with advanced parameters (no compression) + FieldArgs vectorField = VectorFieldArgs. builder().name("embedding").svsVamana() + .type(VectorFieldArgs.VectorType.FLOAT32).dimensions(8).distanceMetric(VectorFieldArgs.DistanceMetric.L2) + .attribute("CONSTRUCTION_WINDOW_SIZE", 128).attribute("GRAPH_MAX_DEGREE", 32) + .attribute("SEARCH_WINDOW_SIZE", 64).build(); + + FieldArgs categoryField = TagFieldArgs. builder().name("category").build(); + + CreateArgs createArgs = CreateArgs. builder().withPrefix("advanced:") + .on(CreateArgs.TargetType.HASH).build(); + + // Create index + String result = redis.ftCreate(indexName, createArgs, Arrays.asList(vectorField, categoryField)); + assertThat(result).isEqualTo("OK"); + + // Insert test vectors with higher dimensionality using binary format + Map product1 = new HashMap<>(); + product1.put(ByteBuffer.wrap("category".getBytes()), ByteBuffer.wrap("electronics".getBytes())); + product1.put(ByteBuffer.wrap("embedding".getBytes()), + floatArrayToByteBuffer(new float[] { 1.0f, 0.5f, 0.2f, 0.8f, 0.3f, 0.9f, 0.1f, 0.6f })); + redisBinary.hmset(ByteBuffer.wrap("advanced:product1".getBytes()), product1); + + Map product2 = new HashMap<>(); + product2.put(ByteBuffer.wrap("category".getBytes()), ByteBuffer.wrap("books".getBytes())); + product2.put(ByteBuffer.wrap("embedding".getBytes()), + floatArrayToByteBuffer(new float[] { 0.2f, 0.8f, 0.9f, 0.1f, 0.7f, 0.4f, 0.6f, 0.3f })); + redisBinary.hmset(ByteBuffer.wrap("advanced:product2".getBytes()), product2); + + Map product3 = new HashMap<>(); + product3.put(ByteBuffer.wrap("category".getBytes()), ByteBuffer.wrap("electronics".getBytes())); + product3.put(ByteBuffer.wrap("embedding".getBytes()), + floatArrayToByteBuffer(new float[] { 0.9f, 0.4f, 0.1f, 0.7f, 0.2f, 0.8f, 0.0f, 0.5f })); + redisBinary.hmset(ByteBuffer.wrap("advanced:product3".getBytes()), product3); + + // Perform vector search with category filter + ByteBuffer queryVector = floatArrayToByteBuffer(new float[] { 1.0f, 0.5f, 0.2f, 0.8f, 0.3f, 0.9f, 0.1f, 0.6f }); + ByteBuffer blobKey = ByteBuffer.wrap("query_vec".getBytes()); + SearchArgs searchArgs = SearchArgs. builder() + .param(blobKey, queryVector).build(); + + ByteBuffer indexKey = ByteBuffer.wrap(indexName.getBytes()); + ByteBuffer queryString = ByteBuffer.wrap("(@category:{electronics})=>[KNN 2 @embedding $query_vec]".getBytes()); + SearchReply searchResult = redisBinary.ftSearch(indexKey, queryString, searchArgs); + + // Verify results - should find electronics products only + assertThat(searchResult.getCount()).isEqualTo(2); + assertThat(searchResult.getResults()).hasSize(2); + + // All results should be electronics + ByteBuffer categoryKey = ByteBuffer.wrap("category".getBytes()); + for (SearchReply.SearchResult searchResultItem : searchResult.getResults()) { + String category = new String(searchResultItem.getFields().get(categoryKey).array()); + assertThat(category).isEqualTo("electronics"); + } + + // Cleanup + redis.ftDropindex(indexName); + } + + /** + * Test SVS-VAMANA vector index with aggregation operations. This test verifies that SVS-VAMANA indexes work correctly with + * aggregation queries and can be used for analytical operations on vector data. + */ + @Test + void testSvsVamanaWithAggregation() { + + // VAMANA is available since Redis 8.2 + assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.2")); + + String indexName = "svs-vamana-agg-idx"; + + // Create SVS-VAMANA vector field optimized for aggregation (no compression) + FieldArgs vectorField = VectorFieldArgs. builder().name("embedding").svsVamana() + .type(VectorFieldArgs.VectorType.FLOAT32).dimensions(4).distanceMetric(VectorFieldArgs.DistanceMetric.COSINE) + .attribute("SEARCH_WINDOW_SIZE", 64).build(); + + FieldArgs categoryField = TagFieldArgs. builder().name("category").sortable().build(); + FieldArgs priceField = NumericFieldArgs. builder().name("price").sortable().build(); + + CreateArgs createArgs = CreateArgs. builder().withPrefix("agg:") + .on(CreateArgs.TargetType.HASH).build(); + + // Create index + String result = redis.ftCreate(indexName, createArgs, Arrays.asList(vectorField, categoryField, priceField)); + assertThat(result).isEqualTo("OK"); + + // Insert test data for aggregation + String[] categories = { "electronics", "books", "electronics", "books", "electronics" }; + float[][] vectors = { { 1.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 0.0f }, { 0.7071f, 0.7071f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, { 0.5f, 0.5f, 0.5f, 0.5f } }; + double[] prices = { 99.99, 19.99, 149.99, 29.99, 199.99 }; + + for (int i = 0; i < categories.length; i++) { + Map item = new HashMap<>(); + item.put("category", categories[i]); + item.put("price", String.valueOf(prices[i])); + item.put("embedding", floatArrayToByteBuffer(vectors[i]).array()); + storeHashDocument("agg:item" + (i + 1), item); + } + + // Perform aggregation: group by category and calculate average price + AggregationReply aggregationResult = redis.ftAggregate(indexName, "*", + AggregateArgs. builder() + .groupBy(AggregateArgs.GroupBy. of("category") + .reduce(AggregateArgs.Reducer. count().as("count")) + .reduce(AggregateArgs.Reducer. avg("@price").as("avg_price"))) + .sortBy(AggregateArgs.SortBy.of("avg_price", AggregateArgs.SortDirection.DESC)).build()); + + // Verify aggregation results + // Verify aggregation results + assertThat(aggregationResult.getAggregationGroups()).isEqualTo(1); // 1 aggregation operation + assertThat(aggregationResult.getReplies()).hasSize(1); // One reply containing all groups + + SearchReply reply = aggregationResult.getReplies().get(0); + assertThat(reply.getResults()).hasSize(2); // 2 category groups + + // Verify we have both categories represented + List> aggregationResults = reply.getResults(); + Set foundCategories = new HashSet<>(); + for (SearchResult groupResult : aggregationResults) { + foundCategories.add(groupResult.getFields().get("category")); + } + assertThat(foundCategories).containsExactlyInAnyOrder("electronics", "books"); + + // Cleanup + redis.ftDropindex(indexName); + } + + /** + * Test SVS-VAMANA vector index with different distance metrics. This test verifies that SVS-VAMANA works correctly with all + * supported distance metrics (L2, COSINE, IP) and produces expected similarity rankings. + */ + @Test + void testSvsVamanaWithDifferentDistanceMetrics() { + + // VAMANA is available since Redis 8.2 + assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("8.2")); + + // Test each distance metric + String[] metrics = { "L2", "COSINE", "IP" }; + + for (String metric : metrics) { + String indexName = "svs-vamana-" + metric.toLowerCase() + "-idx"; + + FieldArgs vectorField = VectorFieldArgs. builder().name("embedding").svsVamana() + .type(VectorFieldArgs.VectorType.FLOAT32).dimensions(3) + .distanceMetric(VectorFieldArgs.DistanceMetric.valueOf(metric)).attribute("CONSTRUCTION_WINDOW_SIZE", 64) + .build(); + + FieldArgs nameField = TextFieldArgs. builder().name("name").build(); + + CreateArgs createArgs = CreateArgs. builder().withPrefix(metric.toLowerCase() + ":") + .on(CreateArgs.TargetType.HASH).build(); + + // Create index + String result = redis.ftCreate(indexName, createArgs, Arrays.asList(vectorField, nameField)); + assertThat(result).isEqualTo("OK"); + + // Insert test vectors using binary format + Map vec1 = new HashMap<>(); + vec1.put(ByteBuffer.wrap("name".getBytes()), ByteBuffer.wrap("Vector 1".getBytes())); + vec1.put(ByteBuffer.wrap("embedding".getBytes()), floatArrayToByteBuffer(new float[] { 1.0f, 0.0f, 0.0f })); + redisBinary.hmset(ByteBuffer.wrap((metric.toLowerCase() + ":vec1").getBytes()), vec1); + + Map vec2 = new HashMap<>(); + vec2.put(ByteBuffer.wrap("name".getBytes()), ByteBuffer.wrap("Vector 2".getBytes())); + vec2.put(ByteBuffer.wrap("embedding".getBytes()), floatArrayToByteBuffer(new float[] { 0.0f, 1.0f, 0.0f })); + redisBinary.hmset(ByteBuffer.wrap((metric.toLowerCase() + ":vec2").getBytes()), vec2); + + Map vec3 = new HashMap<>(); + vec3.put(ByteBuffer.wrap("name".getBytes()), ByteBuffer.wrap("Vector 3".getBytes())); + vec3.put(ByteBuffer.wrap("embedding".getBytes()), floatArrayToByteBuffer(new float[] { 0.5f, 0.5f, 0.0f })); + redisBinary.hmset(ByteBuffer.wrap((metric.toLowerCase() + ":vec3").getBytes()), vec3); + + // Query with vector similar to vec1 + ByteBuffer queryVector = floatArrayToByteBuffer(new float[] { 0.9f, 0.1f, 0.0f }); + ByteBuffer blobKey = ByteBuffer.wrap("query_vec".getBytes()); + SearchArgs searchArgs = SearchArgs. builder() + .param(blobKey, queryVector).build(); + + ByteBuffer indexKey = ByteBuffer.wrap(indexName.getBytes()); + ByteBuffer queryString = ByteBuffer.wrap("*=>[KNN 3 @embedding $query_vec]".getBytes()); + SearchReply searchResult = redisBinary.ftSearch(indexKey, queryString, searchArgs); + + // Verify we get results + assertThat(searchResult.getCount()).isEqualTo(3); + assertThat(searchResult.getResults()).hasSize(3); + + // For all metrics, the most similar should be found + // The exact ranking may vary by metric, but we should get valid results + List> results = searchResult.getResults(); + ByteBuffer nameKey = ByteBuffer.wrap("name".getBytes()); + assertThat(results.get(0).getFields().get(nameKey)).isNotNull(); + assertThat(results.get(1).getFields().get(nameKey)).isNotNull(); + assertThat(results.get(2).getFields().get(nameKey)).isNotNull(); + + // Cleanup + redis.ftDropindex(indexName); + } + } + /** * Helper method to convert float array to ByteBuffer for vector storage. Redis expects vectors as binary data when stored * in HASH fields. diff --git a/src/test/java/io/lettuce/core/search/arguments/VectorFieldArgsTest.java b/src/test/java/io/lettuce/core/search/arguments/VectorFieldArgsTest.java index f94487460..4ac693fdf 100644 --- a/src/test/java/io/lettuce/core/search/arguments/VectorFieldArgsTest.java +++ b/src/test/java/io/lettuce/core/search/arguments/VectorFieldArgsTest.java @@ -152,6 +152,7 @@ void testDistanceMetricEnum() { void testAlgorithmEnum() { assertThat(VectorFieldArgs.Algorithm.FLAT.name()).isEqualTo("FLAT"); assertThat(VectorFieldArgs.Algorithm.HNSW.name()).isEqualTo("HNSW"); + assertThat(VectorFieldArgs.Algorithm.SVS_VAMANA.toString()).isEqualTo("SVS-VAMANA"); } @Test @@ -250,4 +251,104 @@ void testBuilderMethodChaining() { assertThat(field.isNoIndex()).isTrue(); } + @Test + void testVectorFieldArgsWithSvsVamana() { + VectorFieldArgs field = VectorFieldArgs. builder().name("vector").svsVamana().build(); + + assertThat(field.getName()).isEqualTo("vector"); + assertThat(field.getAlgorithm()).hasValue(VectorFieldArgs.Algorithm.SVS_VAMANA); + } + + @Test + void testSvsVamanaWithCompression() { + VectorFieldArgs field = VectorFieldArgs. builder().name("compressed_vector").svsVamana() + .attribute("COMPRESSION", "LVQ").build(); + + assertThat(field.getAlgorithm()).hasValue(VectorFieldArgs.Algorithm.SVS_VAMANA); + assertThat(field.getAttributes()).containsEntry("COMPRESSION", "LVQ"); + } + + @Test + void testSvsVamanaWithLeanVecCompression() { + VectorFieldArgs field = VectorFieldArgs. builder().name("leanvec_vector").svsVamana() + .attribute("COMPRESSION", "LEANVEC").build(); + + assertThat(field.getAlgorithm()).hasValue(VectorFieldArgs.Algorithm.SVS_VAMANA); + assertThat(field.getAttributes()).containsEntry("COMPRESSION", "LEANVEC"); + } + + @Test + void testSvsVamanaWithConstructionWindowSize() { + VectorFieldArgs field = VectorFieldArgs. builder().name("vector").svsVamana() + .attribute("CONSTRUCTION_WINDOW_SIZE", 128).build(); + + assertThat(field.getAttributes()).containsEntry("CONSTRUCTION_WINDOW_SIZE", 128); + } + + @Test + void testSvsVamanaWithGraphMaxDegree() { + VectorFieldArgs field = VectorFieldArgs. builder().name("vector").svsVamana() + .attribute("GRAPH_MAX_DEGREE", 64).build(); + + assertThat(field.getAttributes()).containsEntry("GRAPH_MAX_DEGREE", 64); + } + + @Test + void testSvsVamanaWithSearchWindowSize() { + VectorFieldArgs field = VectorFieldArgs. builder().name("vector").svsVamana() + .attribute("SEARCH_WINDOW_SIZE", 100).build(); + + assertThat(field.getAttributes()).containsEntry("SEARCH_WINDOW_SIZE", 100); + } + + @Test + void testSvsVamanaWithAllOptions() { + VectorFieldArgs field = VectorFieldArgs. builder().name("svs_vector").as("vector").svsVamana() + .type(VectorFieldArgs.VectorType.FLOAT32).dimensions(384).distanceMetric(VectorFieldArgs.DistanceMetric.COSINE) + .attribute("COMPRESSION", "LVQ").attribute("CONSTRUCTION_WINDOW_SIZE", 256).attribute("GRAPH_MAX_DEGREE", 64) + .attribute("SEARCH_WINDOW_SIZE", 128).sortable().build(); + + assertThat(field.getName()).isEqualTo("svs_vector"); + assertThat(field.getAs()).hasValue("vector"); + assertThat(field.getAlgorithm()).hasValue(VectorFieldArgs.Algorithm.SVS_VAMANA); + assertThat(field.getAttributes()).containsEntry("TYPE", "FLOAT32"); + assertThat(field.getAttributes()).containsEntry("DIM", 384); + assertThat(field.getAttributes()).containsEntry("DISTANCE_METRIC", "COSINE"); + assertThat(field.getAttributes()).containsEntry("COMPRESSION", "LVQ"); + assertThat(field.getAttributes()).containsEntry("CONSTRUCTION_WINDOW_SIZE", 256); + assertThat(field.getAttributes()).containsEntry("GRAPH_MAX_DEGREE", 64); + assertThat(field.getAttributes()).containsEntry("SEARCH_WINDOW_SIZE", 128); + assertThat(field.isSortable()).isTrue(); + } + + @Test + void testVectorFieldArgsBuildSvsVamana() { + VectorFieldArgs field = VectorFieldArgs. builder().name("svs_test").svsVamana() + .type(VectorFieldArgs.VectorType.FLOAT32).dimensions(128).distanceMetric(VectorFieldArgs.DistanceMetric.L2) + .attribute("COMPRESSION", "LVQ").attribute("CONSTRUCTION_WINDOW_SIZE", 256).attribute("GRAPH_MAX_DEGREE", 64) + .attribute("SEARCH_WINDOW_SIZE", 128).build(); + + CommandArgs commandArgs = new CommandArgs<>(StringCodec.UTF8); + field.build(commandArgs); + + String argsString = commandArgs.toString(); + assertThat(argsString).contains("svs_test"); + assertThat(argsString).contains("VECTOR"); + assertThat(argsString).contains("SVS-VAMANA"); + assertThat(argsString).contains("18"); // 9 attributes * 2 + assertThat(argsString).contains("TYPE"); + assertThat(argsString).contains("FLOAT32"); + assertThat(argsString).contains("DIM"); + assertThat(argsString).contains("128"); + assertThat(argsString).contains("DISTANCE_METRIC"); + assertThat(argsString).contains("L2"); + assertThat(argsString).contains("COMPRESSION"); + assertThat(argsString).contains("LVQ"); + assertThat(argsString).contains("CONSTRUCTION_WINDOW_SIZE"); + assertThat(argsString).contains("256"); + assertThat(argsString).contains("GRAPH_MAX_DEGREE"); + assertThat(argsString).contains("64"); + assertThat(argsString).contains("SEARCH_WINDOW_SIZE"); + } + }