diff --git a/src/it/java/io/weaviate/integration/AggregationITest.java b/src/it/java/io/weaviate/integration/AggregationITest.java index 7c7597c0c..e2db7cfc2 100644 --- a/src/it/java/io/weaviate/integration/AggregationITest.java +++ b/src/it/java/io/weaviate/integration/AggregationITest.java @@ -14,6 +14,7 @@ import io.weaviate.ConcurrentTest; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.aggregate.AggregateResponseGroup; import io.weaviate.client6.v1.api.collections.aggregate.AggregateResponseGrouped; @@ -21,8 +22,6 @@ import io.weaviate.client6.v1.api.collections.aggregate.GroupBy; import io.weaviate.client6.v1.api.collections.aggregate.GroupedBy; import io.weaviate.client6.v1.api.collections.aggregate.IntegerAggregation; -import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; -import io.weaviate.client6.v1.api.collections.vectorizers.NoneVectorizer; import io.weaviate.containers.Container; public class AggregationITest extends ConcurrentTest { @@ -36,7 +35,7 @@ public static void beforeAll() throws IOException { .properties( Property.text("category"), Property.integer("price")) - .vector(Hnsw.of(new NoneVectorizer()))); + .vectors(Vectorizer.none())); var things = client.collections.use(COLLECTION); for (var category : List.of("Shoes", "Hat", "Jacket")) { diff --git a/src/it/java/io/weaviate/integration/CollectionsITest.java b/src/it/java/io/weaviate/integration/CollectionsITest.java index 0b5b94fbe..8f358b2fb 100644 --- a/src/it/java/io/weaviate/integration/CollectionsITest.java +++ b/src/it/java/io/weaviate/integration/CollectionsITest.java @@ -10,6 +10,7 @@ import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; import io.weaviate.client6.v1.api.collections.VectorIndex; +import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.api.collections.WeaviateCollection; import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; import io.weaviate.client6.v1.api.collections.vectorizers.NoneVectorizer; @@ -24,7 +25,7 @@ public void testCreateGetDelete() throws IOException { client.collections.create(collectionName, col -> col .properties(Property.text("username"), Property.integer("age")) - .vector(Hnsw.of(new NoneVectorizer()))); + .vectors(Vectorizer.none())); var thingsCollection = client.collections.getConfig(collectionName); diff --git a/src/it/java/io/weaviate/integration/DataITest.java b/src/it/java/io/weaviate/integration/DataITest.java index e7db1664b..eed766951 100644 --- a/src/it/java/io/weaviate/integration/DataITest.java +++ b/src/it/java/io/weaviate/integration/DataITest.java @@ -11,11 +11,10 @@ import io.weaviate.ConcurrentTest; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.WeaviateObject; import io.weaviate.client6.v1.api.collections.query.Metadata; -import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; -import io.weaviate.client6.v1.api.collections.vectorizers.NoneVectorizer; import io.weaviate.containers.Container; public class DataITest extends ConcurrentTest { @@ -99,6 +98,6 @@ private static void createTestCollections() throws IOException { Property.integer("age")) .references( Property.reference("hasAwards", awardsGrammy, awardsOscar)) - .vectors(named -> named.vector(VECTOR_INDEX, Hnsw.of(new NoneVectorizer())))); + .vectors(Vectorizer.none(VECTOR_INDEX))); } } diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index fe6dbc63a..58938d3b0 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -17,6 +17,7 @@ import io.weaviate.ConcurrentTest; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.WeaviateObject; import io.weaviate.client6.v1.api.collections.data.Reference; @@ -25,10 +26,6 @@ import io.weaviate.client6.v1.api.collections.query.QueryMetadata; import io.weaviate.client6.v1.api.collections.query.QueryResponseGroup; import io.weaviate.client6.v1.api.collections.query.Where; -import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; -import io.weaviate.client6.v1.api.collections.vectorizers.Img2VecNeuralVectorizer; -import io.weaviate.client6.v1.api.collections.vectorizers.NoneVectorizer; -import io.weaviate.client6.v1.api.collections.vectorizers.Text2VecContextionaryVectorizer; import io.weaviate.containers.Container; import io.weaviate.containers.Container.ContainerGroup; import io.weaviate.containers.Contextionary; @@ -131,7 +128,7 @@ private static Map populateTest(int n) throws IOException { private static void createTestCollection() throws IOException { client.collections.create(COLLECTION, cfg -> cfg .properties(Property.text("category")) - .vector(VECTOR_INDEX, Hnsw.of(new NoneVectorizer()))); + .vectors(Vectorizer.none(VECTOR_INDEX))); } @Test @@ -140,7 +137,7 @@ public void testNearText() throws IOException { client.collections.create(nsSongs, col -> col .properties(Property.text("title")) - .vector(Hnsw.of(Text2VecContextionaryVectorizer.of()))); + .vectors(Vectorizer.text2VecContextionary())); var songs = client.collections.use(nsSongs); var submarine = songs.data.insert(Map.of("title", "Yellow Submarine")); @@ -162,13 +159,13 @@ public void testNearText() throws IOException { @Test public void testNearText_groupBy() throws IOException { - var vectorIndex = Hnsw.of(Text2VecContextionaryVectorizer.of()); + var vectorIndex = Vectorizer.text2VecContextionary(); var nsArtists = ns("Artists"); client.collections.create(nsArtists, col -> col .properties(Property.text("name")) - .vector(vectorIndex)); + .vectors(vectorIndex)); var artists = client.collections.use(nsArtists); var beatles = artists.data.insert(Map.of("name", "Beatles")); @@ -179,7 +176,7 @@ public void testNearText_groupBy() throws IOException { col -> col .properties(Property.text("title")) .references(Property.reference("performedBy", nsArtists)) - .vector(vectorIndex)); + .vectors(vectorIndex)); var songs = client.collections.use(nsSongs); songs.data.insert(Map.of("title", "Yellow Submarine"), @@ -206,9 +203,8 @@ public void testNearImage() throws IOException { .properties( Property.text("breed"), Property.blob("img")) - .vector(Hnsw.of( - Img2VecNeuralVectorizer.of( - i2v -> i2v.imageFields("img"))))); + .vectors(Vectorizer.img2vecNeural( + i2v -> i2v.imageFields("img")))); var cats = client.collections.use(nsCats); cats.data.insert(Map.of( diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/VectorIndex.java b/src/main/java/io/weaviate/client6/v1/api/collections/VectorIndex.java index 05f535ad9..077d4e10e 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/VectorIndex.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/VectorIndex.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.EnumMap; import java.util.Map; +import java.util.function.Function; import com.google.gson.Gson; import com.google.gson.JsonParser; @@ -18,6 +19,7 @@ import io.weaviate.client6.v1.internal.json.JsonEnum; public interface VectorIndex { + static final Function DEFAULT_VECTOR_INDEX = Hnsw::of; static final String DEFAULT_VECTOR_NAME = "default"; public enum Kind implements JsonEnum { @@ -80,7 +82,8 @@ public TypeAdapter create(Gson gson, TypeToken type) { } final var vectorizerAdapter = gson.getDelegateAdapter(this, TypeToken.get(Vectorizer.class)); - final var writeAdapter = gson.getDelegateAdapter(this, TypeToken.get(rawType)); + // final var writeAdapter = gson.getDelegateAdapter(this, + // TypeToken.get(rawType)); return (TypeAdapter) new TypeAdapter() { @Override @@ -89,10 +92,13 @@ public void write(JsonWriter out, VectorIndex value) throws IOException { out.name("vectorIndexType"); out.value(value._kind().jsonValue()); - var config = writeAdapter.toJsonTree((T) value.config()); - config.getAsJsonObject().remove("vectorizer"); - out.name("vectorIndexConfig"); - Streams.write(config, out); + var adapter = (TypeAdapter) readAdapters.get(value._kind()); + if (adapter != null) { + var config = adapter.toJsonTree((T) value.config()); + config.getAsJsonObject().remove("vectorizer"); + out.name("vectorIndexConfig"); + Streams.write(config, out); + } out.name("vectorizer"); vectorizerAdapter.write(out, value.vectorizer()); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/Vectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/Vectorizer.java index 7752cfb07..523c62ca4 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/Vectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/Vectorizer.java @@ -3,6 +3,8 @@ import java.io.IOException; import java.util.EnumMap; import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; import com.google.gson.Gson; import com.google.gson.TypeAdapter; @@ -12,11 +14,13 @@ import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; +import io.weaviate.client6.v1.api.collections.vectorindex.WrappedVectorIndex; import io.weaviate.client6.v1.api.collections.vectorizers.Img2VecNeuralVectorizer; import io.weaviate.client6.v1.api.collections.vectorizers.Multi2VecClipVectorizer; import io.weaviate.client6.v1.api.collections.vectorizers.NoneVectorizer; import io.weaviate.client6.v1.api.collections.vectorizers.Text2VecContextionaryVectorizer; import io.weaviate.client6.v1.api.collections.vectorizers.Text2VecWeaviateVectorizer; +import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.json.JsonEnum; public interface Vectorizer { @@ -48,6 +52,99 @@ public static Kind valueOfJson(String jsonValue) { Object _self(); + public static Map.Entry none() { + return buildVectorIndex(VectorIndex.DEFAULT_VECTOR_NAME, new NoneVectorizer.Builder(), ObjectBuilder.identity()); + } + + public static Map.Entry none( + Function> fn) { + return buildVectorIndex(VectorIndex.DEFAULT_VECTOR_NAME, new NoneVectorizer.Builder(), ObjectBuilder.identity()); + } + + public static Map.Entry none(String vectorName, + Function> fn) { + return buildVectorIndex(vectorName, new NoneVectorizer.Builder(), ObjectBuilder.identity()); + } + + public static Map.Entry text2vecWeaviate() { + return Map.entry(VectorIndex.DEFAULT_VECTOR_NAME, + VectorIndex.DEFAULT_VECTOR_INDEX.apply(Text2VecWeaviateVectorizer.of())); + } + + public static Map.Entry text2vecWeaviate( + Function> fn) { + return Map.entry(VectorIndex.DEFAULT_VECTOR_NAME, + VectorIndex.DEFAULT_VECTOR_INDEX.apply(Text2VecWeaviateVectorizer.of(fn))); + } + + public static Map.Entry text2vecWeaviate(String vectorName, + Function> fn) { + return Map.entry(vectorName, + VectorIndex.DEFAULT_VECTOR_INDEX.apply(Text2VecWeaviateVectorizer.of(fn))); + } + + public static Map.Entry text2VecContextionary() { + return Map.entry(VectorIndex.DEFAULT_VECTOR_NAME, + VectorIndex.DEFAULT_VECTOR_INDEX.apply(Text2VecContextionaryVectorizer.of())); + } + + public static Map.Entry text2VecContextionary( + Function> fn) { + return Map.entry(VectorIndex.DEFAULT_VECTOR_NAME, + VectorIndex.DEFAULT_VECTOR_INDEX.apply(Text2VecContextionaryVectorizer.of(fn))); + } + + public static Map.Entry text2VecContextionary(String vectorName, + Function> fn) { + return Map.entry(vectorName, + VectorIndex.DEFAULT_VECTOR_INDEX.apply(Text2VecContextionaryVectorizer.of(fn))); + } + + public static Map.Entry text2VecContextionary( + BiFunction>, VectorIndex> ctor, + Function> fnVectorizer, + Function> fnIndex) { + return Map.entry("default", ctor.apply(null, null)); + } + + public static Map.Entry multi2vecClip() { + return buildVectorIndex(VectorIndex.DEFAULT_VECTOR_NAME, new Multi2VecClipVectorizer.Builder(), + ObjectBuilder.identity()); + } + + public static Map.Entry multi2vecClip( + Function> fn) { + return buildVectorIndex(VectorIndex.DEFAULT_VECTOR_NAME, new Multi2VecClipVectorizer.Builder(), fn); + } + + public static Map.Entry multi2vecClip(String vectorName, + Function> fn) { + return buildVectorIndex(vectorName, new Multi2VecClipVectorizer.Builder(), fn); + } + + public static Map.Entry img2vecNeural() { + return buildVectorIndex(VectorIndex.DEFAULT_VECTOR_NAME, new Img2VecNeuralVectorizer.Builder(), + ObjectBuilder.identity()); + } + + public static Map.Entry img2vecNeural( + Function> fn) { + return buildVectorIndex(VectorIndex.DEFAULT_VECTOR_NAME, new Img2VecNeuralVectorizer.Builder(), fn); + } + + public static Map.Entry img2vecNeural(String vectorName, + Function> fn) { + return buildVectorIndex(vectorName, new Img2VecNeuralVectorizer.Builder(), fn); + } + + private static , V extends Vectorizer> Map.Entry buildVectorIndex( + String key, + B builder, Function> fn) { + @SuppressWarnings("unchecked") + B b = (B) fn.apply(builder); + return Map.entry(key, b.buildVectorIndex()); + } + public static enum CustomTypeAdapterFactory implements TypeAdapterFactory { INSTANCE; diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollection.java b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollection.java index 6dc8fa306..a78b4f43b 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollection.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateCollection.java @@ -81,20 +81,29 @@ public Builder references(List references) { return this; } - public Builder vector(VectorIndex vector) { - this.vectors.put(VectorIndex.DEFAULT_VECTOR_NAME, vector); - return this; - } - - public Builder vector(String name, VectorIndex vector) { - this.vectors.put(name, vector); + @SafeVarargs + public final Builder vectors(Map.Entry... vectors) { + for (final var vector : vectors) { + this.vectors.put(vector.getKey(), vector.getValue()); + } return this; } - public Builder vectors(Function>> fn) { - this.vectors = fn.apply(new VectorsBuilder()).build(); - return this; - } + // public Builder vector(VectorIndex vector) { + // this.vectors.put(VectorIndex.DEFAULT_VECTOR_NAME, vector); + // return this; + // } + // + // public Builder vector(String name, VectorIndex vector) { + // this.vectors.put(name, vector); + // return this; + // } + // + // public Builder vectors(Function>> fn) { + // this.vectors = fn.apply(new VectorsBuilder()).build(); + // return this; + // } public static class VectorsBuilder implements ObjectBuilder> { private Map vectors = new HashMap<>(); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorindex/WrappedVectorIndex.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorindex/WrappedVectorIndex.java new file mode 100644 index 000000000..3642269b3 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorindex/WrappedVectorIndex.java @@ -0,0 +1,57 @@ +package io.weaviate.client6.v1.api.collections.vectorindex; + +import java.util.function.Function; + +import io.weaviate.client6.v1.api.collections.VectorIndex; +import io.weaviate.client6.v1.api.collections.Vectorizer; +import io.weaviate.client6.v1.internal.ObjectBuilder; + +public class WrappedVectorIndex extends BaseVectorIndex { + private final VectorIndex vectorIndex; + + public WrappedVectorIndex(Vectorizer vectorizer, VectorIndex vectorIndex) { + super(vectorizer); + this.vectorIndex = vectorIndex != null + ? vectorIndex + : VectorIndex.DEFAULT_VECTOR_INDEX.apply(vectorizer); + } + + public WrappedVectorIndex(Vectorizer vectorizer) { + this(vectorizer, null); + } + + @Override + public Kind _kind() { + return this.vectorIndex._kind(); + } + + @Override + public Object config() { + return this.vectorIndex.config(); + } + + @Override + public Vectorizer vectorizer() { + return super.vectorizer; + } + + public static abstract class Builder, V extends Vectorizer> + implements ObjectBuilder { + private VectorIndex vectorIndex; + + @SuppressWarnings("unchecked") + public SELF vectorIndex(VectorIndex vectorIndex) { + this.vectorIndex = vectorIndex; + return (SELF) this; + } + + public VectorIndex buildVectorIndex() { + var vectorizer = build(); + return new WrappedVectorIndex(vectorizer, vectorIndex); + } + + public static , V extends Vectorizer> Function> identity() { + return builder -> builder; + } + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Img2VecNeuralVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Img2VecNeuralVectorizer.java index 2d9ff6beb..c741a562b 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Img2VecNeuralVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Img2VecNeuralVectorizer.java @@ -8,6 +8,7 @@ import com.google.gson.annotations.SerializedName; import io.weaviate.client6.v1.api.collections.Vectorizer; +import io.weaviate.client6.v1.api.collections.vectorindex.WrappedVectorIndex; import io.weaviate.client6.v1.internal.ObjectBuilder; public record Img2VecNeuralVectorizer( @@ -35,7 +36,7 @@ public Img2VecNeuralVectorizer(Builder builder) { this(builder.imageFields); } - public static class Builder implements ObjectBuilder { + public static class Builder extends WrappedVectorIndex.Builder { private List imageFields = new ArrayList<>(); public Builder imageFields(List fields) { diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Multi2VecClipVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Multi2VecClipVectorizer.java index 945984cc4..4b4a16d59 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Multi2VecClipVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Multi2VecClipVectorizer.java @@ -9,6 +9,7 @@ import com.google.gson.annotations.SerializedName; import io.weaviate.client6.v1.api.collections.Vectorizer; +import io.weaviate.client6.v1.api.collections.vectorindex.WrappedVectorIndex; import io.weaviate.client6.v1.internal.ObjectBuilder; public record Multi2VecClipVectorizer( @@ -52,7 +53,7 @@ public Multi2VecClipVectorizer(Builder builder) { builder.textFields.values().stream().toList())); } - public static class Builder implements ObjectBuilder { + public static class Builder extends WrappedVectorIndex.Builder { private boolean vectorizeCollectionName = false; private String inferenceUrl; private Map imageFields = new HashMap<>(); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/NoneVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/NoneVectorizer.java index 6449ba89b..9fe97fe79 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/NoneVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/NoneVectorizer.java @@ -8,6 +8,7 @@ import com.google.gson.stream.JsonWriter; import io.weaviate.client6.v1.api.collections.Vectorizer; +import io.weaviate.client6.v1.api.collections.vectorindex.WrappedVectorIndex; public record NoneVectorizer() implements Vectorizer { @Override @@ -20,6 +21,14 @@ public Object _self() { return this; } + public static class Builder extends WrappedVectorIndex.Builder { + + @Override + public NoneVectorizer build() { + return new NoneVectorizer(); + } + } + public static final TypeAdapter TYPE_ADAPTER = new TypeAdapter() { @Override diff --git a/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java b/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java index e1ab5a9ee..b39b1f3ce 100644 --- a/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java +++ b/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java @@ -25,6 +25,7 @@ import io.weaviate.client6.v1.api.collections.vectorindex.Distance; import io.weaviate.client6.v1.api.collections.vectorindex.Flat; import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; +import io.weaviate.client6.v1.api.collections.vectorindex.WrappedVectorIndex; import io.weaviate.client6.v1.api.collections.vectorizers.Img2VecNeuralVectorizer; import io.weaviate.client6.v1.api.collections.vectorizers.Multi2VecClipVectorizer; import io.weaviate.client6.v1.api.collections.vectorizers.NoneVectorizer; @@ -192,9 +193,8 @@ public static Object[][] testCases() { Property.integer("size")) .references( Property.reference("owner", "Person", "Company")) - .vectors(named -> named - .vector("v-shape", Hnsw.of(Img2VecNeuralVectorizer.of( - i2v -> i2v.imageFields("img")))))), + .vectors(Vectorizer.img2vecNeural("v-shape", + i2v -> i2v.imageFields("img")))), """ { "class": "Things", @@ -215,6 +215,7 @@ public static Object[][] testCases() { } } """, + (CustomAssert) JSONTest::compareVectorIndex, }, // Reference.TYPE_ADAPTER @@ -229,7 +230,7 @@ public static Object[][] testCases() { "{\"beacon\": \"weaviate://localhost/Doodlebops/id-1\"}", }, - // WeaviateObject.CustomTypeAdapterFactory.INSTANCE + // WeaviateObject.CustomTypeAdapterFactory { new TypeToken, Reference, ObjectMetadata>>() { }, @@ -297,7 +298,8 @@ private static void assertEqualJson(String want, String got) { /** * Custom assert function that uses deep array equality - * to correctly compare Float[] and Float[][] nested in the object. + * to correctly compare {@code Float[]} and {@code Float[][]} + * nested in the object. */ private static void compareVectors(Object got, Object want) { Assertions.assertThat(got) @@ -306,4 +308,28 @@ private static void compareVectors(Object got, Object want) { .withEqualsForType(Arrays::deepEquals, Float[][].class) .isEqualTo(want); } + + /** + * Custom assert function that unwraps {@link WrappedVectorIndex} + * and compares its interface values rather than the object itself.. + * + * Note, that JSONs are correctly deserialized into {@link Hnsw}, {@link Flat}, + * etc., but this test suit expects to find {@link WrappedVectorIndex} + * if the configuration is created with one of the {@link Vectorizer}'s + * static factories. + */ + private static void compareVectorIndex(Object got, Object want) { + Assertions.assertThat(got) + .usingRecursiveComparison() + .withEqualsForType((v1, v2) -> { + if (!(v1 instanceof WrappedVectorIndex) && + !(v2 instanceof WrappedVectorIndex)) { + return v1.equals(v2); + } + return v1._kind().equals(v2._kind()) + && v1.vectorizer().equals(v2.vectorizer()) + && v1.config().equals(v2.config()); + }, VectorIndex.class) + .isEqualTo(want); + } }