From 2e83483c6547def9d13580e4f5c5600affea6010 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 9 Sep 2025 19:22:41 +0200 Subject: [PATCH 01/16] spike: overload vectorIndex method to partially override the default vector config --- .../client6/v1/api/collections/VectorIndex.java | 7 +++++++ .../vectorizers/Img2VecNeuralVectorizer.java | 12 ++++++++++++ 2 files changed, 19 insertions(+) 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 4da02656c..40034d04c 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; @@ -15,11 +16,17 @@ import io.weaviate.client6.v1.api.collections.vectorindex.Flat; import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; +import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.json.JsonEnum; public interface VectorIndex { static final String DEFAULT_VECTOR_NAME = "default"; static final VectorIndex DEFAULT_VECTOR_INDEX = Hnsw.of(); + static final Function>, VectorIndex> DEFAULT_VECTOR_INDEX_FUNC = Hnsw::of; + + static VectorIndex createDefault(Function> fn) { + return DEFAULT_VECTOR_INDEX_FUNC.apply(fn); + } public enum Kind implements JsonEnum { HNSW("hnsw"), 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 79cf57867..922304bc5 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 @@ -9,6 +9,7 @@ import io.weaviate.client6.v1.api.collections.VectorIndex; import io.weaviate.client6.v1.api.collections.Vectorizer; +import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; import io.weaviate.client6.v1.internal.ObjectBuilder; public record Img2VecNeuralVectorizer( @@ -54,6 +55,17 @@ public Builder imageFields(String... fields) { return imageFields(Arrays.asList(fields)); } + /** + * Override default vector index configuration. + * + * HNSW + * is the default vector index. + */ + public Builder vectorIndex(Function> fn) { + return vectorIndex(VectorIndex.createDefault(fn)); + } + /** * Override default vector index configuration. * From 0741622dd45348412fb1d72191a1fe0a2922b36e Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 15 Sep 2025 11:31:35 +0200 Subject: [PATCH 02/16] Revert "spike: overload vectorIndex method to partially override the default vector config" This reverts commit b721a52ca21a44b1a98a9256b7cf9a3cc0bcb88c. --- .../client6/v1/api/collections/VectorIndex.java | 7 ------- .../vectorizers/Img2VecNeuralVectorizer.java | 12 ------------ 2 files changed, 19 deletions(-) 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 40034d04c..4da02656c 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,7 +3,6 @@ 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; @@ -16,17 +15,11 @@ import io.weaviate.client6.v1.api.collections.vectorindex.Flat; import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; -import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.json.JsonEnum; public interface VectorIndex { static final String DEFAULT_VECTOR_NAME = "default"; static final VectorIndex DEFAULT_VECTOR_INDEX = Hnsw.of(); - static final Function>, VectorIndex> DEFAULT_VECTOR_INDEX_FUNC = Hnsw::of; - - static VectorIndex createDefault(Function> fn) { - return DEFAULT_VECTOR_INDEX_FUNC.apply(fn); - } public enum Kind implements JsonEnum { HNSW("hnsw"), 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 922304bc5..79cf57867 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 @@ -9,7 +9,6 @@ import io.weaviate.client6.v1.api.collections.VectorIndex; import io.weaviate.client6.v1.api.collections.Vectorizer; -import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; import io.weaviate.client6.v1.internal.ObjectBuilder; public record Img2VecNeuralVectorizer( @@ -55,17 +54,6 @@ public Builder imageFields(String... fields) { return imageFields(Arrays.asList(fields)); } - /** - * Override default vector index configuration. - * - * HNSW - * is the default vector index. - */ - public Builder vectorIndex(Function> fn) { - return vectorIndex(VectorIndex.createDefault(fn)); - } - /** * Override default vector index configuration. * From 755d7e88c01be613874d0f539e5ab71115386c5c Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 15 Sep 2025 11:43:18 +0200 Subject: [PATCH 03/16] chore: rename Property.reference -> ReferenceProperty.to --- .../integration/CollectionsITest.java | 3 +- .../io/weaviate/integration/DataITest.java | 9 ++--- .../weaviate/integration/ReferencesITest.java | 6 ++-- .../io/weaviate/integration/SearchITest.java | 3 +- .../client6/v1/api/collections/Property.java | 9 ----- .../v1/api/collections/ReferenceProperty.java | 36 +++++++++++++++++++ .../config/WeaviateConfigClient.java | 3 +- .../config/WeaviateConfigClientAsync.java | 3 +- .../client6/v1/internal/json/JSONTest.java | 3 +- 9 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/it/java/io/weaviate/integration/CollectionsITest.java b/src/it/java/io/weaviate/integration/CollectionsITest.java index 732f63c95..23ffdffb4 100644 --- a/src/it/java/io/weaviate/integration/CollectionsITest.java +++ b/src/it/java/io/weaviate/integration/CollectionsITest.java @@ -12,6 +12,7 @@ import io.weaviate.client6.v1.api.collections.CollectionConfig; import io.weaviate.client6.v1.api.collections.InvertedIndex; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.Replication; import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.api.collections.Vectorizers; @@ -59,7 +60,7 @@ public void testCrossReferences() throws IOException { // Act: Create Things collection with owner -> owners var nsThings = ns("Things"); client.collections.create(nsThings, - col -> col.references(Property.reference("ownedBy", nsOwners))); + col -> col.references(ReferenceProperty.to("ownedBy", nsOwners))); var things = client.collections.use(nsThings); // Assert: Things --ownedBy-> Owners diff --git a/src/it/java/io/weaviate/integration/DataITest.java b/src/it/java/io/weaviate/integration/DataITest.java index 7db8e6b40..4fe6bc887 100644 --- a/src/it/java/io/weaviate/integration/DataITest.java +++ b/src/it/java/io/weaviate/integration/DataITest.java @@ -15,6 +15,7 @@ import io.weaviate.client6.v1.api.WeaviateApiException; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.Vectorizers; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.WeaviateObject; @@ -116,7 +117,7 @@ private static void createTestCollections() throws IOException { Property.text("name"), Property.integer("age")) .references( - Property.reference("hasAwards", awardsGrammy, awardsOscar)) + ReferenceProperty.to("hasAwards", awardsGrammy, awardsOscar)) .vectors(Vectorizers.selfProvided(VECTOR_INDEX))); } @@ -128,7 +129,7 @@ public void testReferences_AddReplaceDelete() throws IOException { client.collections.create(nsPersons, collection -> collection .properties(Property.text("name")) - .references(Property.reference("hasFriend", nsPersons))); + .references(ReferenceProperty.to("hasFriend", nsPersons))); var persons = client.collections.use(nsPersons); var john = persons.data.insert(Map.of("name", "john")); @@ -232,7 +233,7 @@ public void testUpdate() throws IOException { client.collections.create(nsBooks, collection -> collection .properties(Property.text("title"), Property.integer("year")) - .references(Property.reference("writtenBy", nsAuthors)) + .references(ReferenceProperty.to("writtenBy", nsAuthors)) .vectors(Vectorizers.selfProvided())); var authors = client.collections.use(nsAuthors); @@ -364,7 +365,7 @@ public void testReferenceAddMany() throws IOException { client.collections.create(nsAirports); client.collections.create(nsCities, c -> c - .references(Property.reference("hasAirports", nsAirports))); + .references(ReferenceProperty.to("hasAirports", nsAirports))); var airports = client.collections.use(nsAirports); var cities = client.collections.use(nsCities); diff --git a/src/it/java/io/weaviate/integration/ReferencesITest.java b/src/it/java/io/weaviate/integration/ReferencesITest.java index 404a6adfe..53a36a2c1 100644 --- a/src/it/java/io/weaviate/integration/ReferencesITest.java +++ b/src/it/java/io/weaviate/integration/ReferencesITest.java @@ -48,7 +48,7 @@ public void testReferences() throws IOException { Property.text("name"), Property.integer("age")) .references( - Property.reference("hasAwards", nsGrammy, nsOscar))); + ReferenceProperty.to("hasAwards", nsGrammy, nsOscar))); var artists = client.collections.use(nsArtists); var grammies = client.collections.use(nsGrammy); @@ -129,7 +129,7 @@ public void testNestedReferences() throws IOException { // Act: create Artists collection with hasAwards reference client.collections.create(nsGrammy, col -> col - .references(Property.reference("presentedBy", nsAcademy))); + .references(ReferenceProperty.to("presentedBy", nsAcademy))); client.collections.create(nsArtists, col -> col @@ -137,7 +137,7 @@ public void testNestedReferences() throws IOException { Property.text("name"), Property.integer("age")) .references( - Property.reference("hasAwards", nsGrammy))); + ReferenceProperty.to("hasAwards", nsGrammy))); var artists = client.collections.use(nsArtists); var grammies = client.collections.use(nsGrammy); diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index f14f98582..09714a176 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -20,6 +20,7 @@ import io.weaviate.client6.v1.api.WeaviateApiException; import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.Vectorizers; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.WeaviateMetadata; @@ -180,7 +181,7 @@ public void testNearText_groupBy() throws IOException { client.collections.create(nsSongs, col -> col .properties(Property.text("title")) - .references(Property.reference("performedBy", nsArtists)) + .references(ReferenceProperty.to("performedBy", nsArtists)) .vectors(vectorizer)); var songs = client.collections.use(nsSongs); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/Property.java b/src/main/java/io/weaviate/client6/v1/api/collections/Property.java index fb5976500..66121675c 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/Property.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/Property.java @@ -1,6 +1,5 @@ package io.weaviate.client6.v1.api.collections; -import java.util.Arrays; import java.util.List; import java.util.function.Function; @@ -272,14 +271,6 @@ private static Property newProperty(String name, String dataType, Function collections) { - return new ReferenceProperty(name, collections); - } - /** * Create a new "edit" builder from the property configuration. Consult the dataTypes) { + /** + * Create a cross-reference to another collection. + * + *
{@code
+   * // Single-target reference
+   * ReferenceProperty.to("livesIn", "Cities");
+   *
+   * // Multi-reference
+   * ReferenceProperty.to("hasSeen", "Movies", "Plays", "SoapOperas");
+   * }
+ * + * @param name Name of the property. + * @param collections One or more collections which can be referenced. + * @return ReferenceProperty + */ + public static ReferenceProperty to(String name, String... collections) { + return new ReferenceProperty(name, Arrays.asList(collections)); + } + + /** + * Create a multi-target reference property. + * + *
{@code
+   * List thingsToSee = List.of("Movies", "Plays", "SoapOperas");
+   * ReferenceProperty.to("hasSeen", thingsToSee);
+   * }
+ * + * @param name Name of the property. + * @param collections One or more collections which can be referenced. + * @return ReferenceProperty + */ + public static ReferenceProperty to(String name, List collections) { + return new ReferenceProperty(name, collections); + } + public Property toProperty() { return new Property.Builder(propertyName, dataTypes).build(); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClient.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClient.java index fdc957f01..9fbddea00 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClient.java @@ -9,6 +9,7 @@ import io.weaviate.client6.v1.api.collections.CollectionConfig; import io.weaviate.client6.v1.api.collections.CollectionHandleDefaults; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.WeaviateCollectionsClient; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.grpc.GrpcTransport; @@ -52,7 +53,7 @@ public void addProperty(Property property) throws IOException { } public void addReference(String propertyName, String... dataTypes) throws IOException { - this.addProperty(Property.reference(propertyName, dataTypes).toProperty()); + this.addProperty(ReferenceProperty.to(propertyName, dataTypes).toProperty()); } public void update(String collectionName, diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClientAsync.java index 34d6a66a6..3ace9ee3b 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/WeaviateConfigClientAsync.java @@ -10,6 +10,7 @@ import io.weaviate.client6.v1.api.collections.CollectionConfig; import io.weaviate.client6.v1.api.collections.CollectionHandleDefaults; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.WeaviateCollectionsClientAsync; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.grpc.GrpcTransport; @@ -53,7 +54,7 @@ public CompletableFuture addProperty(Property property) throws IOException } public CompletableFuture addReference(String name, String... dataTypes) throws IOException { - return this.addProperty(Property.reference(name, dataTypes).toProperty()); + return this.addProperty(ReferenceProperty.to(name, dataTypes).toProperty()); } public CompletableFuture update(String collectionName, 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 e045ad162..18c7632fb 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 @@ -18,6 +18,7 @@ import io.weaviate.client6.v1.api.collections.Generative; import io.weaviate.client6.v1.api.collections.ObjectMetadata; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.Reranker; import io.weaviate.client6.v1.api.collections.Tokenization; import io.weaviate.client6.v1.api.collections.Vectorizer; @@ -228,7 +229,7 @@ public static Object[][] testCases() { Property.text("custom_id", p -> p.tokenization(Tokenization.WORD)), Property.integer("size")) .references( - Property.reference("owner", "Person", "Company")) + ReferenceProperty.to("owner", "Person", "Company")) .vectors( Vectorizers.img2vecNeural("v-shape", i2v -> i2v.imageFields("img")))), From 8533ec070ad161edfa81cdb84e64fa61e20fad57 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 15 Sep 2025 12:04:21 +0200 Subject: [PATCH 04/16] chore: rename local.httpPort -> loca.port config option --- src/main/java/io/weaviate/client6/v1/api/Config.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index a14f01290..3edc1ffe5 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -185,7 +185,7 @@ public static class Local extends Builder { public Local() { scheme("http"); host("localhost"); - httpPort(8080); + port(8080); grpcPort(50051); } @@ -200,7 +200,7 @@ public Local host(String host) { } /** Override default HTTP port. */ - public Local httpPort(int port) { + public Local port(int port) { this.httpPort = port; return this; } From a498c2357a0a2dc1c9d7d0efc17f9059a8c7b94f Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 15 Sep 2025 12:11:36 +0200 Subject: [PATCH 05/16] chore: rename CollectionConfig::vectors -> vectorConfig --- .../weaviate/integration/AggregationITest.java | 2 +- .../weaviate/integration/CollectionsITest.java | 2 +- .../io/weaviate/integration/DataITest.java | 4 ++-- .../io/weaviate/integration/SearchITest.java | 18 +++++++++--------- .../v1/api/collections/CollectionConfig.java | 6 +++--- .../config/UpdateCollectionRequest.java | 4 ++-- .../client6/v1/internal/json/JSONTest.java | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/it/java/io/weaviate/integration/AggregationITest.java b/src/it/java/io/weaviate/integration/AggregationITest.java index 19d8f3460..a6b60f060 100644 --- a/src/it/java/io/weaviate/integration/AggregationITest.java +++ b/src/it/java/io/weaviate/integration/AggregationITest.java @@ -35,7 +35,7 @@ public static void beforeAll() throws IOException { .properties( Property.text("category"), Property.integer("price")) - .vectors(Vectorizers.selfProvided())); + .vectorConfig(Vectorizers.selfProvided())); 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 23ffdffb4..80feb1330 100644 --- a/src/it/java/io/weaviate/integration/CollectionsITest.java +++ b/src/it/java/io/weaviate/integration/CollectionsITest.java @@ -31,7 +31,7 @@ public void testCreateGetDelete() throws IOException { client.collections.create(collectionName, col -> col .properties(Property.text("username"), Property.integer("age")) - .vectors(Vectorizers.selfProvided())); + .vectorConfig(Vectorizers.selfProvided())); 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 4fe6bc887..86fe145f1 100644 --- a/src/it/java/io/weaviate/integration/DataITest.java +++ b/src/it/java/io/weaviate/integration/DataITest.java @@ -118,7 +118,7 @@ private static void createTestCollections() throws IOException { Property.integer("age")) .references( ReferenceProperty.to("hasAwards", awardsGrammy, awardsOscar)) - .vectors(Vectorizers.selfProvided(VECTOR_INDEX))); + .vectorConfig(Vectorizers.selfProvided(VECTOR_INDEX))); } @Test @@ -234,7 +234,7 @@ public void testUpdate() throws IOException { collection -> collection .properties(Property.text("title"), Property.integer("year")) .references(ReferenceProperty.to("writtenBy", nsAuthors)) - .vectors(Vectorizers.selfProvided())); + .vectorConfig(Vectorizers.selfProvided())); var authors = client.collections.use(nsAuthors); var walter = authors.data.insert(Map.of("name", "walter scott")); diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index 09714a176..5c3aac22d 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -134,7 +134,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")) - .vectors(Vectorizers.selfProvided(VECTOR_INDEX))); + .vectorConfig(Vectorizers.selfProvided(VECTOR_INDEX))); } @Test @@ -143,7 +143,7 @@ public void testNearText() throws IOException { client.collections.create(nsSongs, col -> col .properties(Property.text("title")) - .vectors(Vectorizers.text2vecContextionary())); + .vectorConfig(Vectorizers.text2vecContextionary())); var songs = client.collections.use(nsSongs); var submarine = songs.data.insert(Map.of("title", "Yellow Submarine")); @@ -171,7 +171,7 @@ public void testNearText_groupBy() throws IOException { client.collections.create(nsArtists, col -> col .properties(Property.text("name")) - .vectors(vectorizer)); + .vectorConfig(vectorizer)); var artists = client.collections.use(nsArtists); var beatles = artists.data.insert(Map.of("name", "Beatles")); @@ -182,7 +182,7 @@ public void testNearText_groupBy() throws IOException { col -> col .properties(Property.text("title")) .references(ReferenceProperty.to("performedBy", nsArtists)) - .vectors(vectorizer)); + .vectorConfig(vectorizer)); var songs = client.collections.use(nsSongs); songs.data.insert(Map.of("title", "Yellow Submarine"), @@ -209,7 +209,7 @@ public void testNearImage() throws IOException { .properties( Property.text("breed"), Property.blob("img")) - .vectors(Vectorizers.img2vecNeural( + .vectorConfig(Vectorizers.img2vecNeural( i2v -> i2v.imageFields("img")))); var cats = client.collections.use(nsCats); @@ -391,7 +391,7 @@ public void testNearObject() throws IOException { client.collections.create(nsAnimals, collection -> collection .properties(Property.text("kind")) - .vectors(Vectorizers.text2vecContextionary())); + .vectorConfig(Vectorizers.text2vecContextionary())); var animals = client.collections.use(nsAnimals); @@ -420,7 +420,7 @@ public void testHybrid() throws IOException { client.collections.create(nsHobbies, collection -> collection .properties(Property.text("name"), Property.text("description")) - .vectors(Vectorizers.text2vecContextionary())); + .vectorConfig(Vectorizers.text2vecContextionary())); var hobbies = client.collections.use(nsHobbies); @@ -453,7 +453,7 @@ public void testBadRequest() throws IOException { client.collections.create(nsThings, collection -> collection .properties(Property.text("name")) - .vectors(Vectorizers.text2vecContextionary())); + .vectorConfig(Vectorizers.text2vecContextionary())); var things = client.collections.use(nsThings); var balloon = things.data.insert(Map.of("name", "balloon")); @@ -470,7 +470,7 @@ public void testBadRequest_async() throws Throwable { async.collections.create(nsThings, collection -> collection .properties(Property.text("name")) - .vectors(Vectorizers.text2vecContextionary())) + .vectorConfig(Vectorizers.text2vecContextionary())) .join(); var things = async.collections.use(nsThings); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/CollectionConfig.java b/src/main/java/io/weaviate/client6/v1/api/collections/CollectionConfig.java index 70448ce6a..167f1d35e 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/CollectionConfig.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/CollectionConfig.java @@ -52,7 +52,7 @@ public Builder edit() { .description(description) .properties(properties) .references(references) - .vectors(vectors) + .vectorConfig(vectors) .multiTenancy(multiTenancy) .sharding(sharding) .replication(replication) @@ -131,13 +131,13 @@ private List referenceList() { return this.references.values().stream().toList(); } - public final Builder vectors(Map vectors) { + public final Builder vectorConfig(Map vectors) { this.vectors.putAll(vectors); return this; } @SafeVarargs - public final Builder vectors(Map.Entry... vectors) { + public final Builder vectorConfig(Map.Entry... vectors) { this.vectors.putAll(Map.ofEntries(vectors)); return this; } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java index e3962e739..0103c2daf 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java @@ -96,13 +96,13 @@ public Builder generativeModule(Generative generativeModule) { } public final Builder vectors(Map vectors) { - this.newCollection.vectors(vectors); + this.newCollection.vectorConfig(vectors); return this; } @SafeVarargs public final Builder vectors(Map.Entry... vectors) { - this.newCollection.vectors(vectors); + this.newCollection.vectorConfig(vectors); return this; } 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 18c7632fb..ba22a9db8 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 @@ -230,7 +230,7 @@ public static Object[][] testCases() { Property.integer("size")) .references( ReferenceProperty.to("owner", "Person", "Company")) - .vectors( + .vectorConfig( Vectorizers.img2vecNeural("v-shape", i2v -> i2v.imageFields("img")))), """ From 3abd01856cdaee9338e461431a02470105c3d9c1 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 15 Sep 2025 13:06:53 +0200 Subject: [PATCH 06/16] chore: rename Vectorizer -> VectorConfig and move static factories under the new namespace --- .../integration/AggregationITest.java | 4 +- .../integration/CollectionsITest.java | 9 +- .../io/weaviate/integration/DataITest.java | 6 +- .../io/weaviate/integration/SearchITest.java | 18 +- .../v1/api/collections/CollectionConfig.java | 22 +- .../v1/api/collections/VectorConfig.java | 317 ++++++++++++++++++ .../v1/api/collections/Vectorizer.java | 137 -------- .../v1/api/collections/Vectorizers.java | 195 ----------- .../config/UpdateCollectionRequest.java | 6 +- .../vectorizers/Img2VecNeuralVectorizer.java | 8 +- .../vectorizers/Multi2VecClipVectorizer.java | 8 +- .../vectorizers/SelfProvidedVectorizer.java | 6 +- .../Text2VecContextionaryVectorizer.java | 8 +- .../Text2VecWeaviateVectorizer.java | 8 +- .../client6/v1/internal/json/JSON.java | 2 +- .../client6/v1/internal/json/JSONTest.java | 19 +- 16 files changed, 371 insertions(+), 402 deletions(-) create mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/VectorConfig.java delete mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/Vectorizer.java delete mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/Vectorizers.java diff --git a/src/it/java/io/weaviate/integration/AggregationITest.java b/src/it/java/io/weaviate/integration/AggregationITest.java index a6b60f060..30df82fb5 100644 --- a/src/it/java/io/weaviate/integration/AggregationITest.java +++ b/src/it/java/io/weaviate/integration/AggregationITest.java @@ -14,7 +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.Vectorizers; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.aggregate.Aggregate; import io.weaviate.client6.v1.api.collections.aggregate.AggregateResponseGroup; @@ -35,7 +35,7 @@ public static void beforeAll() throws IOException { .properties( Property.text("category"), Property.integer("price")) - .vectorConfig(Vectorizers.selfProvided())); + .vectorConfig(VectorConfig.selfProvided())); 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 80feb1330..7e21784e6 100644 --- a/src/it/java/io/weaviate/integration/CollectionsITest.java +++ b/src/it/java/io/weaviate/integration/CollectionsITest.java @@ -14,8 +14,7 @@ import io.weaviate.client6.v1.api.collections.Property; import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.Replication; -import io.weaviate.client6.v1.api.collections.Vectorizer; -import io.weaviate.client6.v1.api.collections.Vectorizers; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.config.Shard; import io.weaviate.client6.v1.api.collections.config.ShardStatus; import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; @@ -31,18 +30,18 @@ public void testCreateGetDelete() throws IOException { client.collections.create(collectionName, col -> col .properties(Property.text("username"), Property.integer("age")) - .vectorConfig(Vectorizers.selfProvided())); + .vectorConfig(VectorConfig.selfProvided())); var thingsCollection = client.collections.getConfig(collectionName); Assertions.assertThat(thingsCollection).get() .hasFieldOrPropertyWithValue("collectionName", collectionName) - .extracting(CollectionConfig::vectors, InstanceOfAssertFactories.map(String.class, Vectorizer.class)) + .extracting(CollectionConfig::vectors, InstanceOfAssertFactories.map(String.class, VectorConfig.class)) .as("default vector").extractingByKey("default") .satisfies(defaultVector -> { Assertions.assertThat(defaultVector) .as("has none vectorizer").isInstanceOf(SelfProvidedVectorizer.class); - Assertions.assertThat(defaultVector).extracting(Vectorizer::vectorIndex) + Assertions.assertThat(defaultVector).extracting(VectorConfig::vectorIndex) .isInstanceOf(Hnsw.class); }); diff --git a/src/it/java/io/weaviate/integration/DataITest.java b/src/it/java/io/weaviate/integration/DataITest.java index 86fe145f1..c679f7a7a 100644 --- a/src/it/java/io/weaviate/integration/DataITest.java +++ b/src/it/java/io/weaviate/integration/DataITest.java @@ -16,7 +16,7 @@ import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; import io.weaviate.client6.v1.api.collections.ReferenceProperty; -import io.weaviate.client6.v1.api.collections.Vectorizers; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.WeaviateObject; import io.weaviate.client6.v1.api.collections.data.BatchReference; @@ -118,7 +118,7 @@ private static void createTestCollections() throws IOException { Property.integer("age")) .references( ReferenceProperty.to("hasAwards", awardsGrammy, awardsOscar)) - .vectorConfig(Vectorizers.selfProvided(VECTOR_INDEX))); + .vectorConfig(VectorConfig.selfProvided(VECTOR_INDEX))); } @Test @@ -234,7 +234,7 @@ public void testUpdate() throws IOException { collection -> collection .properties(Property.text("title"), Property.integer("year")) .references(ReferenceProperty.to("writtenBy", nsAuthors)) - .vectorConfig(Vectorizers.selfProvided())); + .vectorConfig(VectorConfig.selfProvided())); var authors = client.collections.use(nsAuthors); var walter = authors.data.insert(Map.of("name", "walter scott")); diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index 5c3aac22d..f42290c74 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -21,7 +21,7 @@ import io.weaviate.client6.v1.api.WeaviateClient; import io.weaviate.client6.v1.api.collections.Property; import io.weaviate.client6.v1.api.collections.ReferenceProperty; -import io.weaviate.client6.v1.api.collections.Vectorizers; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.WeaviateMetadata; import io.weaviate.client6.v1.api.collections.WeaviateObject; @@ -134,7 +134,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")) - .vectorConfig(Vectorizers.selfProvided(VECTOR_INDEX))); + .vectorConfig(VectorConfig.selfProvided(VECTOR_INDEX))); } @Test @@ -143,7 +143,7 @@ public void testNearText() throws IOException { client.collections.create(nsSongs, col -> col .properties(Property.text("title")) - .vectorConfig(Vectorizers.text2vecContextionary())); + .vectorConfig(VectorConfig.text2vecContextionary())); var songs = client.collections.use(nsSongs); var submarine = songs.data.insert(Map.of("title", "Yellow Submarine")); @@ -165,7 +165,7 @@ public void testNearText() throws IOException { @Test public void testNearText_groupBy() throws IOException { - var vectorizer = Vectorizers.text2vecContextionary(); + var vectorizer = VectorConfig.text2vecContextionary(); var nsArtists = ns("Artists"); client.collections.create(nsArtists, @@ -209,7 +209,7 @@ public void testNearImage() throws IOException { .properties( Property.text("breed"), Property.blob("img")) - .vectorConfig(Vectorizers.img2vecNeural( + .vectorConfig(VectorConfig.img2vecNeural( i2v -> i2v.imageFields("img")))); var cats = client.collections.use(nsCats); @@ -391,7 +391,7 @@ public void testNearObject() throws IOException { client.collections.create(nsAnimals, collection -> collection .properties(Property.text("kind")) - .vectorConfig(Vectorizers.text2vecContextionary())); + .vectorConfig(VectorConfig.text2vecContextionary())); var animals = client.collections.use(nsAnimals); @@ -420,7 +420,7 @@ public void testHybrid() throws IOException { client.collections.create(nsHobbies, collection -> collection .properties(Property.text("name"), Property.text("description")) - .vectorConfig(Vectorizers.text2vecContextionary())); + .vectorConfig(VectorConfig.text2vecContextionary())); var hobbies = client.collections.use(nsHobbies); @@ -453,7 +453,7 @@ public void testBadRequest() throws IOException { client.collections.create(nsThings, collection -> collection .properties(Property.text("name")) - .vectorConfig(Vectorizers.text2vecContextionary())); + .vectorConfig(VectorConfig.text2vecContextionary())); var things = client.collections.use(nsThings); var balloon = things.data.insert(Map.of("name", "balloon")); @@ -470,7 +470,7 @@ public void testBadRequest_async() throws Throwable { async.collections.create(nsThings, collection -> collection .properties(Property.text("name")) - .vectorConfig(Vectorizers.text2vecContextionary())) + .vectorConfig(VectorConfig.text2vecContextionary())) .join(); var things = async.collections.use(nsThings); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/CollectionConfig.java b/src/main/java/io/weaviate/client6/v1/api/collections/CollectionConfig.java index 167f1d35e..581c357ee 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/CollectionConfig.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/CollectionConfig.java @@ -27,7 +27,7 @@ public record CollectionConfig( @SerializedName("description") String description, @SerializedName("properties") List properties, List references, - @SerializedName("vectorConfig") Map vectors, + @SerializedName("vectorConfig") Map vectors, @SerializedName("multiTenancyConfig") MultiTenancy multiTenancy, @SerializedName("shardingConfig") Sharding sharding, @SerializedName("replicationConfig") Replication replication, @@ -88,7 +88,7 @@ public static class Builder implements ObjectBuilder { private String description; private Map properties = new HashMap<>(); private Map references = new HashMap<>(); - private Map vectors = new HashMap<>(); + private Map vectors = new HashMap<>(); private MultiTenancy multiTenancy; private Sharding sharding; private Replication replication; @@ -131,31 +131,17 @@ private List referenceList() { return this.references.values().stream().toList(); } - public final Builder vectorConfig(Map vectors) { + public final Builder vectorConfig(Map vectors) { this.vectors.putAll(vectors); return this; } @SafeVarargs - public final Builder vectorConfig(Map.Entry... vectors) { + public final Builder vectorConfig(Map.Entry... vectors) { this.vectors.putAll(Map.ofEntries(vectors)); return this; } - public static class VectorsBuilder implements ObjectBuilder> { - private Map vectors = new HashMap<>(); - - public VectorsBuilder vector(String name, VectorIndex vector) { - vectors.put(name, vector); - return this; - } - - @Override - public Map build() { - return this.vectors; - } - } - public Builder sharding(Sharding sharding) { this.sharding = sharding; return this; diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/VectorConfig.java b/src/main/java/io/weaviate/client6/v1/api/collections/VectorConfig.java new file mode 100644 index 000000000..db893d9c3 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/VectorConfig.java @@ -0,0 +1,317 @@ +package io.weaviate.client6.v1.api.collections; + +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.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +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.SelfProvidedVectorizer; +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 VectorConfig { + public enum Kind implements JsonEnum { + NONE("none"), + IMG2VEC_NEURAL("img2vec-neural"), + TEXT2VEC_CONTEXTIONARY("text2vec-contextionary"), + TEXT2VEC_WEAVIATE("text2vec-weaviate"), + MULTI2VEC_CLIP("multi2vec-clip"); + + private static final Map jsonValueMap = JsonEnum.collectNames(Kind.values()); + private final String jsonValue; + + private Kind(String jsonValue) { + this.jsonValue = jsonValue; + } + + @Override + public String jsonValue() { + return this.jsonValue; + } + + public static Kind valueOfJson(String jsonValue) { + return JsonEnum.valueOfJson(jsonValue, jsonValueMap, Kind.class); + } + } + + Kind _kind(); + + Object _self(); + + VectorIndex vectorIndex(); + + /** Create a bring-your-own-vector vector index. */ + public static Map.Entry selfProvided() { + return selfProvided(VectorIndex.DEFAULT_VECTOR_NAME); + } + + /** + * Create a bring-your-own-vector vector index. + * + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry selfProvided( + Function> fn) { + return selfProvided(VectorIndex.DEFAULT_VECTOR_NAME, fn); + } + + /** + * Create a named bring-your-own-vector vector index. + * + * @param vectorName Vector name. + */ + public static Map.Entry selfProvided(String vectorName) { + return Map.entry(vectorName, SelfProvidedVectorizer.of()); + } + + /** + * Create a named bring-your-own-vector vector index. + * + * @param vectorName Vector name. + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry selfProvided(String vectorName, + Function> fn) { + return Map.entry(vectorName, SelfProvidedVectorizer.of(fn)); + } + + /** Create a vector index with an {@code img2vec-neural} vectorizer. */ + public static Map.Entry img2vecNeural() { + return img2vecNeural(VectorIndex.DEFAULT_VECTOR_NAME); + } + + /** + * Create a vector index with an {@code img2vec-neural} vectorizer. + * + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry img2vecNeural( + Function> fn) { + return img2vecNeural(VectorIndex.DEFAULT_VECTOR_NAME, fn); + } + + /** + * Create a named vector index with an {@code img2vec-neural} vectorizer. + * + * @param vectorName Vector name. + */ + public static Map.Entry img2vecNeural(String vectorName) { + return Map.entry(vectorName, Img2VecNeuralVectorizer.of()); + } + + /** + * Create a vector index with an {@code img2vec-neural} vectorizer. + * + * @param vectorName Vector name. + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry img2vecNeural(String vectorName, + Function> fn) { + return Map.entry(vectorName, Img2VecNeuralVectorizer.of(fn)); + } + + /** Create a vector index with an {@code multi2vec-clip} vectorizer. */ + public static Map.Entry multi2vecClip() { + return multi2vecClip(VectorIndex.DEFAULT_VECTOR_NAME); + } + + /** + * Create a vector index with an {@code multi2vec-clip} vectorizer. + * + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry multi2vecClip( + Function> fn) { + return multi2vecClip(VectorIndex.DEFAULT_VECTOR_NAME, fn); + } + + /** + * Create a named vector index with an {@code multi2vec-clip} vectorizer. + * + * @param vectorName Vector name. + */ + public static Map.Entry multi2vecClip(String vectorName) { + return Map.entry(vectorName, Multi2VecClipVectorizer.of()); + } + + /** + * Create a named vector index with an {@code multi2vec-clip} vectorizer. + * + * @param vectorName Vector name. + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry multi2vecClip(String vectorName, + Function> fn) { + return Map.entry(vectorName, Multi2VecClipVectorizer.of(fn)); + } + + /** Create a vector index with an {@code text2vec-contextionary} vectorizer. */ + public static Map.Entry text2vecContextionary() { + return text2vecContextionary(VectorIndex.DEFAULT_VECTOR_NAME); + } + + /** + * Create a vector index with an {@code text2vec-contextionary} vectorizer. + * + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry text2vecContextionary( + Function> fn) { + return text2vecContextionary(VectorIndex.DEFAULT_VECTOR_NAME, fn); + } + + /** + * Create a named vector index with an {@code text2vec-contextionary} + * vectorizer. + * + * @param vectorName Vector name. + */ + public static Map.Entry text2vecContextionary(String vectorName) { + return Map.entry(vectorName, Text2VecContextionaryVectorizer.of()); + } + + /** + * Create a named vector index with an {@code text2vec-contextionary} + * vectorizer. + * + * @param vectorName Vector name. + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry text2vecContextionary(String vectorName, + Function> fn) { + return Map.entry(vectorName, Text2VecContextionaryVectorizer.of(fn)); + } + + /** Create a vector index with an {@code text2vec-weaviate} vectorizer. */ + public static Map.Entry text2VecWeaviate() { + return text2VecWeaviate(VectorIndex.DEFAULT_VECTOR_NAME); + } + + /** + * Create a vector index with an {@code text2vec-weaviate} vectorizer. + * + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry text2VecWeaviate( + Function> fn) { + return text2VecWeaviate(VectorIndex.DEFAULT_VECTOR_NAME, fn); + } + + /** + * Create a named vector index with an {@code text2vec-weaviate} vectorizer. + * + * @param vectorName Vector name. + */ + public static Map.Entry text2VecWeaviate(String vectorName) { + return Map.entry(vectorName, Text2VecWeaviateVectorizer.of()); + } + + /** + * Create a named vector index with an {@code text2vec-weaviate} vectorizer. + * + * @param vectorName Vector name. + * @param fn Lambda expression for optional parameters. + */ + public static Map.Entry text2VecWeaviate(String vectorName, + Function> fn) { + return Map.entry(vectorName, Text2VecWeaviateVectorizer.of(fn)); + } + + public static enum CustomTypeAdapterFactory implements TypeAdapterFactory { + INSTANCE; + + private static final EnumMap> delegateAdapters = new EnumMap<>( + VectorConfig.Kind.class); + + private final void addAdapter(Gson gson, VectorConfig.Kind kind, Class cls) { + delegateAdapters.put(kind, + (TypeAdapter) gson.getDelegateAdapter(this, TypeToken.get(cls))); + } + + private final void init(Gson gson) { + addAdapter(gson, VectorConfig.Kind.NONE, SelfProvidedVectorizer.class); + addAdapter(gson, VectorConfig.Kind.IMG2VEC_NEURAL, Img2VecNeuralVectorizer.class); + addAdapter(gson, VectorConfig.Kind.MULTI2VEC_CLIP, Multi2VecClipVectorizer.class); + addAdapter(gson, VectorConfig.Kind.TEXT2VEC_WEAVIATE, Text2VecWeaviateVectorizer.class); + addAdapter(gson, VectorConfig.Kind.TEXT2VEC_CONTEXTIONARY, Text2VecContextionaryVectorizer.class); + } + + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + final var rawType = type.getRawType(); + if (!VectorConfig.class.isAssignableFrom(rawType)) { + return null; + } + + if (delegateAdapters.isEmpty()) { + init(gson); + } + + return (TypeAdapter) new TypeAdapter() { + + @Override + public void write(JsonWriter out, VectorConfig value) throws IOException { + TypeAdapter adapter = (TypeAdapter) delegateAdapters.get(value._kind()); + + // Serialize vectorizer config as { "vectorizer-kind": { ... } } + // and remove "vectorIndex" object which every vectorizer has. + var vectorizer = new JsonObject(); + var config = adapter.toJsonTree((T) value._self()); + + // This will create { "vectorIndexType": "", "vectorIndexConfig": { ... } } + // to which we just need to add "vectorizer": { ... } key. + var vectorIndex = config.getAsJsonObject().remove("vectorIndex"); + + vectorizer.add(value._kind().jsonValue(), config); + vectorIndex.getAsJsonObject().add("vectorizer", vectorizer); + + Streams.write(vectorIndex, out); + } + + @Override + public VectorConfig read(JsonReader in) throws IOException { + var jsonObject = JsonParser.parseReader(in).getAsJsonObject(); + + // VectorIndex.CustomTypeAdapterFactory expects keys + // ["vectorIndexType", "vectorIndexConfig"]. + var vectorIndex = new JsonObject(); + vectorIndex.add("vectorIndexType", jsonObject.get("vectorIndexType")); + vectorIndex.add("vectorIndexConfig", jsonObject.get("vectorIndexConfig")); + + var vectorizerObject = jsonObject.get("vectorizer").getAsJsonObject(); + var vectorizerName = vectorizerObject.keySet().iterator().next(); + + VectorConfig.Kind kind; + try { + kind = VectorConfig.Kind.valueOfJson(vectorizerName); + } catch (IllegalArgumentException e) { + return null; + } + + var adapter = delegateAdapters.get(kind); + var concreteVectorizer = vectorizerObject.get(vectorizerName).getAsJsonObject(); + + // Each individual vectorizer has a `VectorIndex vectorIndex` field. + concreteVectorizer.add("vectorIndex", vectorIndex); + + return adapter.fromJsonTree(concreteVectorizer); + } + }.nullSafe(); + } + } +} 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 deleted file mode 100644 index fed6d21d3..000000000 --- a/src/main/java/io/weaviate/client6/v1/api/collections/Vectorizer.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.weaviate.client6.v1.api.collections; - -import java.io.IOException; -import java.util.EnumMap; -import java.util.Map; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.TypeAdapter; -import com.google.gson.TypeAdapterFactory; -import com.google.gson.internal.Streams; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; - -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.SelfProvidedVectorizer; -import io.weaviate.client6.v1.api.collections.vectorizers.Text2VecContextionaryVectorizer; -import io.weaviate.client6.v1.api.collections.vectorizers.Text2VecWeaviateVectorizer; -import io.weaviate.client6.v1.internal.json.JsonEnum; - -public interface Vectorizer { - public enum Kind implements JsonEnum { - NONE("none"), - IMG2VEC_NEURAL("img2vec-neural"), - TEXT2VEC_CONTEXTIONARY("text2vec-contextionary"), - TEXT2VEC_WEAVIATE("text2vec-weaviate"), - MULTI2VEC_CLIP("multi2vec-clip"); - - private static final Map jsonValueMap = JsonEnum.collectNames(Kind.values()); - private final String jsonValue; - - private Kind(String jsonValue) { - this.jsonValue = jsonValue; - } - - @Override - public String jsonValue() { - return this.jsonValue; - } - - public static Kind valueOfJson(String jsonValue) { - return JsonEnum.valueOfJson(jsonValue, jsonValueMap, Kind.class); - } - } - - Kind _kind(); - - Object _self(); - - VectorIndex vectorIndex(); - - public static enum CustomTypeAdapterFactory implements TypeAdapterFactory { - INSTANCE; - - private static final EnumMap> delegateAdapters = new EnumMap<>( - Vectorizer.Kind.class); - - private final void addAdapter(Gson gson, Vectorizer.Kind kind, Class cls) { - delegateAdapters.put(kind, (TypeAdapter) gson.getDelegateAdapter(this, TypeToken.get(cls))); - } - - private final void init(Gson gson) { - addAdapter(gson, Vectorizer.Kind.NONE, SelfProvidedVectorizer.class); - addAdapter(gson, Vectorizer.Kind.IMG2VEC_NEURAL, Img2VecNeuralVectorizer.class); - addAdapter(gson, Vectorizer.Kind.MULTI2VEC_CLIP, Multi2VecClipVectorizer.class); - addAdapter(gson, Vectorizer.Kind.TEXT2VEC_WEAVIATE, Text2VecWeaviateVectorizer.class); - addAdapter(gson, Vectorizer.Kind.TEXT2VEC_CONTEXTIONARY, Text2VecContextionaryVectorizer.class); - } - - @SuppressWarnings("unchecked") - @Override - public TypeAdapter create(Gson gson, TypeToken type) { - final var rawType = type.getRawType(); - if (!Vectorizer.class.isAssignableFrom(rawType)) { - return null; - } - - if (delegateAdapters.isEmpty()) { - init(gson); - } - - return (TypeAdapter) new TypeAdapter() { - - @Override - public void write(JsonWriter out, Vectorizer value) throws IOException { - TypeAdapter adapter = (TypeAdapter) delegateAdapters.get(value._kind()); - - // Serialize vectorizer config as { "vectorizer-kind": { ... } } - // and remove "vectorIndex" object which every vectorizer has. - var vectorizer = new JsonObject(); - var config = adapter.toJsonTree((T) value._self()); - - // This will create { "vectorIndexType": "", "vectorIndexConfig": { ... } } - // to which we just need to add "vectorizer": { ... } key. - var vectorIndex = config.getAsJsonObject().remove("vectorIndex"); - - vectorizer.add(value._kind().jsonValue(), config); - vectorIndex.getAsJsonObject().add("vectorizer", vectorizer); - - Streams.write(vectorIndex, out); - } - - @Override - public Vectorizer read(JsonReader in) throws IOException { - var jsonObject = JsonParser.parseReader(in).getAsJsonObject(); - - // VectorIndex.CustomTypeAdapterFactory expects keys - // ["vectorIndexType", "vectorIndexConfig"]. - var vectorIndex = new JsonObject(); - vectorIndex.add("vectorIndexType", jsonObject.get("vectorIndexType")); - vectorIndex.add("vectorIndexConfig", jsonObject.get("vectorIndexConfig")); - - var vectorizerObject = jsonObject.get("vectorizer").getAsJsonObject(); - var vectorizerName = vectorizerObject.keySet().iterator().next(); - - Vectorizer.Kind kind; - try { - kind = Vectorizer.Kind.valueOfJson(vectorizerName); - } catch (IllegalArgumentException e) { - return null; - } - - var adapter = delegateAdapters.get(kind); - var concreteVectorizer = vectorizerObject.get(vectorizerName).getAsJsonObject(); - - // Each individual vectorizer has a `VectorIndex vectorIndex` field. - concreteVectorizer.add("vectorIndex", vectorIndex); - - return adapter.fromJsonTree(concreteVectorizer); - } - }.nullSafe(); - } - } -} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/Vectorizers.java b/src/main/java/io/weaviate/client6/v1/api/collections/Vectorizers.java deleted file mode 100644 index 38c9618c6..000000000 --- a/src/main/java/io/weaviate/client6/v1/api/collections/Vectorizers.java +++ /dev/null @@ -1,195 +0,0 @@ -package io.weaviate.client6.v1.api.collections; - -import java.util.Map; -import java.util.function.Function; - -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.SelfProvidedVectorizer; -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; - -/** Static factories for creating instances of {@link Vectorizer}. */ -public final class Vectorizers { - /** Prevent public initialization. */ - private Vectorizers() { - } - - /** Create a bring-your-own-vector vector index. */ - public static Map.Entry selfProvided() { - return selfProvided(VectorIndex.DEFAULT_VECTOR_NAME); - } - - /** - * Create a bring-your-own-vector vector index. - * - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry selfProvided( - Function> fn) { - return selfProvided(VectorIndex.DEFAULT_VECTOR_NAME, fn); - } - - /** - * Create a named bring-your-own-vector vector index. - * - * @param vectorName Vector name. - */ - public static Map.Entry selfProvided(String vectorName) { - return Map.entry(vectorName, SelfProvidedVectorizer.of()); - } - - /** - * Create a named bring-your-own-vector vector index. - * - * @param vectorName Vector name. - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry selfProvided(String vectorName, - Function> fn) { - return Map.entry(vectorName, SelfProvidedVectorizer.of(fn)); - } - - /** Create a vector index with an {@code img2vec-neural} vectorizer. */ - public static Map.Entry img2vecNeural() { - return img2vecNeural(VectorIndex.DEFAULT_VECTOR_NAME); - } - - /** - * Create a vector index with an {@code img2vec-neural} vectorizer. - * - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry img2vecNeural( - Function> fn) { - return img2vecNeural(VectorIndex.DEFAULT_VECTOR_NAME, fn); - } - - /** - * Create a named vector index with an {@code img2vec-neural} vectorizer. - * - * @param vectorName Vector name. - */ - public static Map.Entry img2vecNeural(String vectorName) { - return Map.entry(vectorName, Img2VecNeuralVectorizer.of()); - } - - /** - * Create a vector index with an {@code img2vec-neural} vectorizer. - * - * @param vectorName Vector name. - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry img2vecNeural(String vectorName, - Function> fn) { - return Map.entry(vectorName, Img2VecNeuralVectorizer.of(fn)); - } - - /** Create a vector index with an {@code multi2vec-clip} vectorizer. */ - public static Map.Entry multi2vecClip() { - return multi2vecClip(VectorIndex.DEFAULT_VECTOR_NAME); - } - - /** - * Create a vector index with an {@code multi2vec-clip} vectorizer. - * - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry multi2vecClip( - Function> fn) { - return multi2vecClip(VectorIndex.DEFAULT_VECTOR_NAME, fn); - } - - /** - * Create a named vector index with an {@code multi2vec-clip} vectorizer. - * - * @param vectorName Vector name. - */ - public static Map.Entry multi2vecClip(String vectorName) { - return Map.entry(vectorName, Multi2VecClipVectorizer.of()); - } - - /** - * Create a named vector index with an {@code multi2vec-clip} vectorizer. - * - * @param vectorName Vector name. - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry multi2vecClip(String vectorName, - Function> fn) { - return Map.entry(vectorName, Multi2VecClipVectorizer.of(fn)); - } - - /** Create a vector index with an {@code text2vec-contextionary} vectorizer. */ - public static Map.Entry text2vecContextionary() { - return text2vecContextionary(VectorIndex.DEFAULT_VECTOR_NAME); - } - - /** - * Create a vector index with an {@code text2vec-contextionary} vectorizer. - * - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry text2vecContextionary( - Function> fn) { - return text2vecContextionary(VectorIndex.DEFAULT_VECTOR_NAME, fn); - } - - /** - * Create a named vector index with an {@code text2vec-contextionary} - * vectorizer. - * - * @param vectorName Vector name. - */ - public static Map.Entry text2vecContextionary(String vectorName) { - return Map.entry(vectorName, Text2VecContextionaryVectorizer.of()); - } - - /** - * Create a named vector index with an {@code text2vec-contextionary} - * vectorizer. - * - * @param vectorName Vector name. - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry text2vecContextionary(String vectorName, - Function> fn) { - return Map.entry(vectorName, Text2VecContextionaryVectorizer.of(fn)); - } - - /** Create a vector index with an {@code text2vec-weaviate} vectorizer. */ - public static Map.Entry text2VecWeaviate() { - return text2VecWeaviate(VectorIndex.DEFAULT_VECTOR_NAME); - } - - /** - * Create a vector index with an {@code text2vec-weaviate} vectorizer. - * - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry text2VecWeaviate( - Function> fn) { - return text2VecWeaviate(VectorIndex.DEFAULT_VECTOR_NAME, fn); - } - - /** - * Create a named vector index with an {@code text2vec-weaviate} vectorizer. - * - * @param vectorName Vector name. - */ - public static Map.Entry text2VecWeaviate(String vectorName) { - return Map.entry(vectorName, Text2VecWeaviateVectorizer.of()); - } - - /** - * Create a named vector index with an {@code text2vec-weaviate} vectorizer. - * - * @param vectorName Vector name. - * @param fn Lambda expression for optional parameters. - */ - public static Map.Entry text2VecWeaviate(String vectorName, - Function> fn) { - return Map.entry(vectorName, Text2VecWeaviateVectorizer.of(fn)); - } -} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java index 0103c2daf..f2372b23c 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java @@ -10,7 +10,7 @@ import io.weaviate.client6.v1.api.collections.InvertedIndex; import io.weaviate.client6.v1.api.collections.Replication; import io.weaviate.client6.v1.api.collections.Reranker; -import io.weaviate.client6.v1.api.collections.Vectorizer; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.json.JSON; import io.weaviate.client6.v1.internal.rest.Endpoint; @@ -95,13 +95,13 @@ public Builder generativeModule(Generative generativeModule) { return this; } - public final Builder vectors(Map vectors) { + public final Builder vectors(Map vectors) { this.newCollection.vectorConfig(vectors); return this; } @SafeVarargs - public final Builder vectors(Map.Entry... vectors) { + public final Builder vectors(Map.Entry... vectors) { this.newCollection.vectorConfig(vectors); return this; } 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 79cf57867..ea55850f8 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 @@ -7,19 +7,19 @@ import com.google.gson.annotations.SerializedName; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; -import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.internal.ObjectBuilder; public record Img2VecNeuralVectorizer( /** BLOB properties included in the embedding. */ @SerializedName("imageFields") List imageFields, /** Vector index configuration. */ - VectorIndex vectorIndex) implements Vectorizer { + VectorIndex vectorIndex) implements VectorConfig { @Override - public Vectorizer.Kind _kind() { - return Vectorizer.Kind.IMG2VEC_NEURAL; + public VectorConfig.Kind _kind() { + return VectorConfig.Kind.IMG2VEC_NEURAL; } @Override 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 6b7fb57a3..4676ac805 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 @@ -8,8 +8,8 @@ import com.google.gson.annotations.SerializedName; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; -import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.internal.ObjectBuilder; public record Multi2VecClipVectorizer( @@ -22,7 +22,7 @@ public record Multi2VecClipVectorizer( /** Weights of the included properties. */ @SerializedName("weights") Weights weights, /** Vector index configuration. */ - VectorIndex vectorIndex) implements Vectorizer { + VectorIndex vectorIndex) implements VectorConfig { private static record Weights( /** @@ -38,8 +38,8 @@ private static record Weights( } @Override - public Vectorizer.Kind _kind() { - return Vectorizer.Kind.MULTI2VEC_CLIP; + public VectorConfig.Kind _kind() { + return VectorConfig.Kind.MULTI2VEC_CLIP; } @Override diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/SelfProvidedVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/SelfProvidedVectorizer.java index 146557227..e8aefdf03 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/SelfProvidedVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/SelfProvidedVectorizer.java @@ -2,15 +2,15 @@ import java.util.function.Function; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; -import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; import io.weaviate.client6.v1.internal.ObjectBuilder; -public record SelfProvidedVectorizer(VectorIndex vectorIndex) implements Vectorizer { +public record SelfProvidedVectorizer(VectorIndex vectorIndex) implements VectorConfig { @Override public Kind _kind() { - return Vectorizer.Kind.NONE; + return VectorConfig.Kind.NONE; } @Override diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java index ad7360ea9..b55b78be4 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java @@ -8,8 +8,8 @@ import com.google.gson.annotations.SerializedName; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; -import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.internal.ObjectBuilder; public record Text2VecContextionaryVectorizer( @@ -25,11 +25,11 @@ public record Text2VecContextionaryVectorizer( /** Properties included in the embedding. */ @SerializedName("sourceProperties") List sourceProperties, /** Vector index configuration. */ - VectorIndex vectorIndex) implements Vectorizer { + VectorIndex vectorIndex) implements VectorConfig { @Override - public Vectorizer.Kind _kind() { - return Vectorizer.Kind.TEXT2VEC_CONTEXTIONARY; + public VectorConfig.Kind _kind() { + return VectorConfig.Kind.TEXT2VEC_CONTEXTIONARY; } @Override diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java index 00ff9db7d..b62e93623 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java @@ -7,8 +7,8 @@ import com.google.gson.annotations.SerializedName; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; -import io.weaviate.client6.v1.api.collections.Vectorizer; import io.weaviate.client6.v1.internal.ObjectBuilder; public record Text2VecWeaviateVectorizer( @@ -21,11 +21,11 @@ public record Text2VecWeaviateVectorizer( /** Properties included in the embedding. */ @SerializedName("sourceProperties") List sourceProperties, /** Vector index configuration. */ - VectorIndex vectorIndex) implements Vectorizer { + VectorIndex vectorIndex) implements VectorConfig { @Override - public Vectorizer.Kind _kind() { - return Vectorizer.Kind.TEXT2VEC_WEAVIATE; + public VectorConfig.Kind _kind() { + return VectorConfig.Kind.TEXT2VEC_WEAVIATE; } @Override diff --git a/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java b/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java index a56fce609..9f195539e 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java +++ b/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java @@ -20,7 +20,7 @@ public final class JSON { gsonBuilder.registerTypeAdapterFactory( io.weaviate.client6.v1.api.collections.Vectors.CustomTypeAdapterFactory.INSTANCE); gsonBuilder.registerTypeAdapterFactory( - io.weaviate.client6.v1.api.collections.Vectorizer.CustomTypeAdapterFactory.INSTANCE); + io.weaviate.client6.v1.api.collections.VectorConfig.CustomTypeAdapterFactory.INSTANCE); gsonBuilder.registerTypeAdapterFactory( io.weaviate.client6.v1.api.collections.VectorIndex.CustomTypeAdapterFactory.INSTANCE); gsonBuilder.registerTypeAdapterFactory( 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 ba22a9db8..559754367 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 @@ -21,8 +21,7 @@ import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.Reranker; import io.weaviate.client6.v1.api.collections.Tokenization; -import io.weaviate.client6.v1.api.collections.Vectorizer; -import io.weaviate.client6.v1.api.collections.Vectorizers; +import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.Vectors; import io.weaviate.client6.v1.api.collections.WeaviateObject; import io.weaviate.client6.v1.api.collections.data.BatchReference; @@ -45,7 +44,7 @@ public static Object[][] testCases() { return new Object[][] { // Vectorizer.CustomTypeAdapterFactory { - Vectorizer.class, + VectorConfig.class, SelfProvidedVectorizer.of(), """ { @@ -56,7 +55,7 @@ public static Object[][] testCases() { """, }, { - Vectorizer.class, + VectorConfig.class, Img2VecNeuralVectorizer.of(i2v -> i2v.imageFields("jpeg", "png")), """ { @@ -71,7 +70,7 @@ public static Object[][] testCases() { """, }, { - Vectorizer.class, + VectorConfig.class, Multi2VecClipVectorizer.of(m2v -> m2v .inferenceUrl("http://example.com") .imageField("img", 1f) @@ -95,7 +94,7 @@ public static Object[][] testCases() { """, }, { - Vectorizer.class, + VectorConfig.class, Text2VecContextionaryVectorizer.of(), """ { @@ -111,7 +110,7 @@ public static Object[][] testCases() { """, }, { - Vectorizer.class, + VectorConfig.class, Text2VecWeaviateVectorizer.of(t2v -> t2v .inferenceUrl("http://example.com") .dimensions(4) @@ -135,7 +134,7 @@ public static Object[][] testCases() { // VectorIndex.CustomTypeAdapterFactory { - Vectorizer.class, + VectorConfig.class, SelfProvidedVectorizer.of(none -> none .vectorIndex(Flat.of(flat -> flat .vectorCacheMaxObjects(100)))), @@ -148,7 +147,7 @@ public static Object[][] testCases() { """, }, { - Vectorizer.class, + VectorConfig.class, SelfProvidedVectorizer.of(none -> none .vectorIndex(Hnsw.of(hnsw -> hnsw .distance(Distance.DOT) @@ -231,7 +230,7 @@ public static Object[][] testCases() { .references( ReferenceProperty.to("owner", "Person", "Company")) .vectorConfig( - Vectorizers.img2vecNeural("v-shape", + VectorConfig.img2vecNeural("v-shape", i2v -> i2v.imageFields("img")))), """ { From 91144a94a3656aa53527e714cb9630a4b6ab6d01 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 15 Sep 2025 13:23:10 +0200 Subject: [PATCH 07/16] chore: improve Vectors::toString representation --- .../client6/v1/api/collections/Vectors.java | 36 ++++++++++++------- .../v1/api/collections/VectorsTest.java | 20 +++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 src/test/java/io/weaviate/client6/v1/api/collections/VectorsTest.java diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/Vectors.java b/src/main/java/io/weaviate/client6/v1/api/collections/Vectors.java index 97551e6e4..2c5dacf4b 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/Vectors.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/Vectors.java @@ -1,6 +1,7 @@ package io.weaviate.client6.v1.api.collections; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -15,17 +16,13 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -import io.weaviate.client6.v1.internal.ObjectBuilder; -import lombok.ToString; - /** * Vectors is an abstraction over named vectors, which can store * both 1-dimensional and 2-dimensional vectors. */ -@ToString public class Vectors { /** Elements of this map must only be {@code float[]} or {@code float[][]}. */ - private final Map namedVectors; + private final Map vectorsMap; /** Create a 1-dimensional vector. */ public static Vectors of(float[] vector) { @@ -58,7 +55,7 @@ public static Vectors of(String name, float[][] vector) { * @param vector {@code float[]} or {@code float[][]} vector. */ private Vectors(String name, Object vector) { - this.namedVectors = Collections.singletonMap(name, vector); + this.vectorsMap = Collections.singletonMap(name, vector); } /** @@ -72,7 +69,7 @@ private Vectors(String name, Object vector) { * @param vector Map of named vectors. */ private Vectors(Map namedVectors) { - this.namedVectors = namedVectors; + this.vectorsMap = namedVectors; } /** Merge all vectors in a single vector map. */ @@ -81,7 +78,7 @@ public Vectors(Vectors... vectors) { for (var vec : vectors) { namedVectors.putAll(vec.asMap()); } - this.namedVectors = namedVectors; + this.vectorsMap = namedVectors; } /** @@ -91,7 +88,7 @@ public Vectors(Vectors... vectors) { * @throws ClassCastException The underlying vector is not a {@code float[]}. */ public float[] getSingle(String name) { - return (float[]) namedVectors.get(name); + return (float[]) vectorsMap.get(name); } /** @@ -112,7 +109,7 @@ public float[] getDefaultSingle() { * {@code float[][]}. */ public float[][] getMulti(String name) { - return (float[][]) namedVectors.get(name); + return (float[][]) vectorsMap.get(name); } /** @@ -134,7 +131,22 @@ public float[][] getDefaultMulti() { * @return Map of name-vector pairs. The returned map is immutable. */ public Map asMap() { - return Map.copyOf(namedVectors); + return Map.copyOf(vectorsMap); + } + + @Override + public String toString() { + var vectorStrings = vectorsMap.entrySet().stream() + .map(v -> { + var name = v.getKey(); + var value = v.getValue(); + var array = (value instanceof float[] f) + ? Arrays.toString((float[]) value) + : Arrays.deepToString((float[][]) value); + return "%s=%s".formatted(name, array); + }) + .toList(); + return "Vectors(%s)".formatted(String.join(", ", vectorStrings)); } public static enum CustomTypeAdapterFactory implements TypeAdapterFactory { @@ -154,7 +166,7 @@ public TypeAdapter create(Gson gson, TypeToken type) { @Override public void write(JsonWriter out, Vectors value) throws IOException { - mapAdapter.write(out, value.namedVectors); + mapAdapter.write(out, value.vectorsMap); } @Override diff --git a/src/test/java/io/weaviate/client6/v1/api/collections/VectorsTest.java b/src/test/java/io/weaviate/client6/v1/api/collections/VectorsTest.java new file mode 100644 index 000000000..b28b5d36d --- /dev/null +++ b/src/test/java/io/weaviate/client6/v1/api/collections/VectorsTest.java @@ -0,0 +1,20 @@ +package io.weaviate.client6.v1.api.collections; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +public class VectorsTest { + @Test + public void testToString_1d() { + var vector = Vectors.of(new float[] { 1, 2, 3 }); + var got = vector.toString(); + Assertions.assertThat(got).isEqualTo("Vectors(default=[1.0, 2.0, 3.0])"); + } + + @Test + public void testToString_2d() { + var vector = Vectors.of(new float[][] { { 1, 2, 3 }, { 1, 2, 3 } }); + var got = vector.toString(); + Assertions.assertThat(got).isEqualTo("Vectors(default=[[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]])"); + } +} From e0bf5b9fe21bcc13f03ac3aff729ea04717d3573 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 15 Sep 2025 17:24:17 +0200 Subject: [PATCH 08/16] feat: add quantization --- .../v1/api/collections/MultiTenancy.java | 4 +- .../v1/api/collections/Quantization.java | 156 ++++++++++++++++++ .../v1/api/collections/VectorConfig.java | 39 ++++- .../v1/api/collections/quantizers/BQ.java | 62 +++++++ .../v1/api/collections/quantizers/PQ.java | 111 +++++++++++++ .../v1/api/collections/quantizers/RQ.java | 62 +++++++ .../v1/api/collections/quantizers/SQ.java | 69 ++++++++ .../collections/quantizers/Uncompressed.java | 20 +++ .../vectorizers/Img2VecNeuralVectorizer.java | 13 +- .../vectorizers/Multi2VecClipVectorizer.java | 14 +- .../vectorizers/SelfProvidedVectorizer.java | 18 +- .../Text2VecContextionaryVectorizer.java | 18 +- .../Text2VecWeaviateVectorizer.java | 14 +- .../client6/v1/internal/json/JSON.java | 2 + .../client6/v1/internal/json/JSONTest.java | 35 ++++ 15 files changed, 618 insertions(+), 19 deletions(-) create mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/Quantization.java create mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/quantizers/BQ.java create mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/quantizers/PQ.java create mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/quantizers/RQ.java create mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/quantizers/SQ.java create mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/quantizers/Uncompressed.java diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/MultiTenancy.java b/src/main/java/io/weaviate/client6/v1/api/collections/MultiTenancy.java index c02d42539..9524a1f05 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/MultiTenancy.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/MultiTenancy.java @@ -7,7 +7,7 @@ import io.weaviate.client6.v1.internal.ObjectBuilder; public record MultiTenancy( - @SerializedName("enabled") Boolean enabled, + @SerializedName("enabled") boolean enabled, @SerializedName("autoTenantCreation") Boolean createAutomatically, @SerializedName("autoTenantActivation") Boolean activateAutomatically) { @@ -23,7 +23,7 @@ public MultiTenancy(Builder builder) { } public static class Builder implements ObjectBuilder { - private Boolean enabled = true; + private boolean enabled = true; private Boolean createAutomatically; private Boolean activateAutomatically; diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/Quantization.java b/src/main/java/io/weaviate/client6/v1/api/collections/Quantization.java new file mode 100644 index 000000000..64c4ec022 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/Quantization.java @@ -0,0 +1,156 @@ +package io.weaviate.client6.v1.api.collections; + +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; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import io.weaviate.client6.v1.api.collections.quantizers.BQ; +import io.weaviate.client6.v1.api.collections.quantizers.PQ; +import io.weaviate.client6.v1.api.collections.quantizers.RQ; +import io.weaviate.client6.v1.api.collections.quantizers.SQ; +import io.weaviate.client6.v1.api.collections.quantizers.Uncompressed; +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.json.JsonEnum; + +public interface Quantization { + + public enum Kind implements JsonEnum { + UNCOMPRESSED("skipDefaultQuantization"), + RQ("rq"), + BQ("bq"), + PQ("pq"), + SQ("sq"); + + private static final Map jsonValueMap = JsonEnum.collectNames(Kind.values()); + private final String jsonValue; + + private Kind(String jsonValue) { + this.jsonValue = jsonValue; + } + + @Override + public String jsonValue() { + return this.jsonValue; + } + + public static Kind valueOfJson(String jsonValue) { + return JsonEnum.valueOfJson(jsonValue, jsonValueMap, Kind.class); + } + } + + Kind _kind(); + + Object _self(); + + public static Quantization uncompressed() { + return Uncompressed.of(); + } + + public static Quantization bq() { + return BQ.of(); + } + + public static Quantization bq(Function> fn) { + return BQ.of(fn); + } + + public static Quantization pq() { + return PQ.of(); + } + + public static Quantization pq(Function> fn) { + return PQ.of(fn); + } + + public static Quantization sq() { + return SQ.of(); + } + + public static Quantization sq(Function> fn) { + return SQ.of(fn); + } + + public static Quantization rq() { + return RQ.of(); + } + + public static Quantization rq(Function> fn) { + return RQ.of(fn); + } + + public static enum CustomTypeAdapterFactory implements TypeAdapterFactory { + INSTANCE; + + private static final EnumMap> delegateAdapters = new EnumMap<>( + Quantization.Kind.class); + + private final void addAdapter(Gson gson, Quantization.Kind kind, Class cls) { + delegateAdapters.put(kind, + (TypeAdapter) gson.getDelegateAdapter(this, TypeToken.get(cls))); + } + + private final void init(Gson gson) { + addAdapter(gson, Quantization.Kind.UNCOMPRESSED, Uncompressed.class); + addAdapter(gson, Quantization.Kind.BQ, BQ.class); + addAdapter(gson, Quantization.Kind.RQ, RQ.class); + addAdapter(gson, Quantization.Kind.SQ, SQ.class); + addAdapter(gson, Quantization.Kind.PQ, PQ.class); + } + + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + final var rawType = type.getRawType(); + if (!Quantization.class.isAssignableFrom(rawType)) { + return null; + } + + if (delegateAdapters.isEmpty()) { + init(gson); + } + + return (TypeAdapter) new TypeAdapter() { + + @Override + public void write(JsonWriter out, Quantization value) throws IOException { + if (value._kind() == Quantization.Kind.UNCOMPRESSED) { + // out.name(value._kind().jsonValue()); + out.value(true); + return; + } + TypeAdapter adapter = (TypeAdapter) delegateAdapters.get(value._kind()); + adapter.write(out, (T) value._self()); + } + + @Override + public Quantization read(JsonReader in) throws IOException { + var quantizerObject = JsonParser.parseReader(in).getAsJsonObject(); + var quantizationName = quantizerObject.keySet().iterator().next(); + Quantization.Kind kind; + try { + kind = Quantization.Kind.valueOfJson(quantizationName); + } catch (IllegalArgumentException e) { + return null; + } + + if (kind == Quantization.Kind.UNCOMPRESSED) { + return new Uncompressed(); + } + + var adapter = delegateAdapters.get(kind); + var concreteQuantizer = quantizerObject.get(quantizationName).getAsJsonObject(); + return adapter.fromJsonTree(concreteQuantizer); + } + }.nullSafe(); + } + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/VectorConfig.java b/src/main/java/io/weaviate/client6/v1/api/collections/VectorConfig.java index db893d9c3..1e8cd96d5 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/VectorConfig.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/VectorConfig.java @@ -54,6 +54,8 @@ public static Kind valueOfJson(String jsonValue) { VectorIndex vectorIndex(); + Quantization quantization(); + /** Create a bring-your-own-vector vector index. */ public static Map.Entry selfProvided() { return selfProvided(VectorIndex.DEFAULT_VECTOR_NAME); @@ -269,29 +271,50 @@ public void write(JsonWriter out, VectorConfig value) throws IOException { TypeAdapter adapter = (TypeAdapter) delegateAdapters.get(value._kind()); // Serialize vectorizer config as { "vectorizer-kind": { ... } } - // and remove "vectorIndex" object which every vectorizer has. + // and remove "vectorIndex" and quantization objects which every vectorizer has. var vectorizer = new JsonObject(); var config = adapter.toJsonTree((T) value._self()); // This will create { "vectorIndexType": "", "vectorIndexConfig": { ... } } - // to which we just need to add "vectorizer": { ... } key. + // to which we just need to add "vectorizer": { ... } key + // and "bq"/"pg"/"sq"/"rq": { ... } (quantizer) key. var vectorIndex = config.getAsJsonObject().remove("vectorIndex"); vectorizer.add(value._kind().jsonValue(), config); vectorIndex.getAsJsonObject().add("vectorizer", vectorizer); + if (value.quantization() != null) { + vectorIndex.getAsJsonObject() + .get("vectorIndexConfig").getAsJsonObject() + .add(value.quantization()._kind().jsonValue(), config.getAsJsonObject().remove("quantization")); + } + Streams.write(vectorIndex, out); } @Override public VectorConfig read(JsonReader in) throws IOException { var jsonObject = JsonParser.parseReader(in).getAsJsonObject(); + var vectorIndexConfig = jsonObject.get("vectorIndexConfig").getAsJsonObject(); + + String quantizationKind = null; + if (vectorIndexConfig.has(Quantization.Kind.BQ.jsonValue())) { + quantizationKind = Quantization.Kind.BQ.jsonValue(); + } else if (vectorIndexConfig.has(Quantization.Kind.PQ.jsonValue())) { + quantizationKind = Quantization.Kind.PQ.jsonValue(); + } else if (vectorIndexConfig.has(Quantization.Kind.SQ.jsonValue())) { + quantizationKind = Quantization.Kind.SQ.jsonValue(); + } else if (vectorIndexConfig.has(Quantization.Kind.RQ.jsonValue())) { + quantizationKind = Quantization.Kind.RQ.jsonValue(); + } else { + quantizationKind = Quantization.Kind.UNCOMPRESSED.jsonValue(); + } // VectorIndex.CustomTypeAdapterFactory expects keys // ["vectorIndexType", "vectorIndexConfig"]. var vectorIndex = new JsonObject(); vectorIndex.add("vectorIndexType", jsonObject.get("vectorIndexType")); - vectorIndex.add("vectorIndexConfig", jsonObject.get("vectorIndexConfig")); + vectorIndex.add("vectorIndexConfig", vectorIndexConfig); var vectorizerObject = jsonObject.get("vectorizer").getAsJsonObject(); var vectorizerName = vectorizerObject.keySet().iterator().next(); @@ -309,6 +332,16 @@ public VectorConfig read(JsonReader in) throws IOException { // Each individual vectorizer has a `VectorIndex vectorIndex` field. concreteVectorizer.add("vectorIndex", vectorIndex); + // Each individual vectorizer has a `Quantization quantization` field. + // We need to specify the kind in order for + // Quantization.CustomTypeAdapterFactory to be able to find the right adapter. + if (vectorIndexConfig.has(quantizationKind)) { + JsonObject quantization = new JsonObject(); + quantization.add(quantizationKind, vectorIndexConfig.get(quantizationKind)); + concreteVectorizer.add("quantization", quantization); + } else { + concreteVectorizer.add("quantization", null); + } return adapter.fromJsonTree(concreteVectorizer); } }.nullSafe(); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/BQ.java b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/BQ.java new file mode 100644 index 000000000..9d4cdb691 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/BQ.java @@ -0,0 +1,62 @@ +package io.weaviate.client6.v1.api.collections.quantizers; + +import java.util.function.Function; + +import com.google.gson.annotations.SerializedName; + +import io.weaviate.client6.v1.api.collections.Quantization; +import io.weaviate.client6.v1.internal.ObjectBuilder; + +public record BQ( + @SerializedName("enabled") boolean enabled, + @SerializedName("rescore_limit") Integer rescoreLimit, + @SerializedName("cache") Boolean cache) implements Quantization { + + @Override + public Quantization.Kind _kind() { + return Quantization.Kind.BQ; + } + + @Override + public Object _self() { + return this; + } + + public static BQ of() { + return of(ObjectBuilder.identity()); + } + + public static BQ of(Function> fn) { + return fn.apply(new Builder()).build(); + } + + public BQ(Builder builder) { + this(builder.enabled, builder.rescoreLimit, builder.cache); + } + + public static class Builder implements ObjectBuilder { + private boolean enabled = true; + private Integer rescoreLimit; + private Boolean cache; + + public Builder enabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public Builder rescoreLimit(int rescoreLimit) { + this.rescoreLimit = rescoreLimit; + return this; + } + + public Builder cache(boolean enabled) { + this.cache = enabled; + return this; + } + + @Override + public BQ build() { + return new BQ(this); + } + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/PQ.java b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/PQ.java new file mode 100644 index 000000000..e8caefb43 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/PQ.java @@ -0,0 +1,111 @@ +package io.weaviate.client6.v1.api.collections.quantizers; + +import java.util.function.Function; + +import com.google.gson.annotations.SerializedName; + +import io.weaviate.client6.v1.api.collections.Quantization; +import io.weaviate.client6.v1.internal.ObjectBuilder; + +public record PQ( + @SerializedName("enabled") boolean enabled, + @SerializedName("centroids") Integer centroids, + @SerializedName("segments") Integer segments, + @SerializedName("encoder_type") EncoderType encoderType, + @SerializedName("encoder_distribusion") EncoderDistribution encoderDistribution, + @SerializedName("training_limit") Integer trainingLimit, + @SerializedName("bit_compression") Boolean bitCompression) implements Quantization { + + public enum EncoderType { + @SerializedName("kmeans") + KMEANS, + @SerializedName("tile") + TILE; + } + + public enum EncoderDistribution { + @SerializedName("log-normal") + NORMAL, + @SerializedName("normal") + LOG_NORMAL; + } + + @Override + public Quantization.Kind _kind() { + return Quantization.Kind.RQ; + } + + @Override + public Object _self() { + return this; + } + + public static PQ of() { + return of(ObjectBuilder.identity()); + } + + public static PQ of(Function> fn) { + return fn.apply(new Builder()).build(); + } + + public PQ(Builder builder) { + this( + builder.enabled, + builder.centroids, + builder.segments, + builder.encoderType, + builder.encoderDistribution, + builder.trainingLimit, + builder.bitCompression); + } + + public static class Builder implements ObjectBuilder { + private boolean enabled = true; + private Integer centroids; + private Integer segments; + private EncoderType encoderType; + private EncoderDistribution encoderDistribution; + private Integer trainingLimit; + private Boolean bitCompression; + + public Builder enabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public Builder centroids(int centroids) { + this.centroids = centroids; + return this; + } + + public Builder segments(int segments) { + this.segments = segments; + return this; + } + + public Builder encoderType(EncoderType encoderType) { + this.encoderType = encoderType; + return this; + } + + public Builder encoderDistribution(EncoderDistribution encoderDistribution) { + this.encoderDistribution = encoderDistribution; + return this; + } + + public Builder trainingLimit(int trainingLimit) { + this.trainingLimit = trainingLimit; + return this; + } + + public Builder bitCompression(boolean enabled) { + this.bitCompression = enabled; + return this; + } + + @Override + public PQ build() { + return new PQ(this); + } + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/RQ.java b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/RQ.java new file mode 100644 index 000000000..0b4151e08 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/RQ.java @@ -0,0 +1,62 @@ +package io.weaviate.client6.v1.api.collections.quantizers; + +import java.util.function.Function; + +import com.google.gson.annotations.SerializedName; + +import io.weaviate.client6.v1.api.collections.Quantization; +import io.weaviate.client6.v1.internal.ObjectBuilder; + +public record RQ( + @SerializedName("enabled") boolean enabled, + @SerializedName("rescore_limit") Integer rescoreLimit, + @SerializedName("bits") Integer bits) implements Quantization { + + @Override + public Quantization.Kind _kind() { + return Quantization.Kind.RQ; + } + + @Override + public Object _self() { + return this; + } + + public static RQ of() { + return of(ObjectBuilder.identity()); + } + + public static RQ of(Function> fn) { + return fn.apply(new Builder()).build(); + } + + public RQ(Builder builder) { + this(builder.enabled, builder.rescoreLimit, builder.bits); + } + + public static class Builder implements ObjectBuilder { + private boolean enabled = true; + private Integer rescoreLimit; + private Integer bits; + + public Builder enabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public Builder rescoreLimit(int rescoreLimit) { + this.rescoreLimit = rescoreLimit; + return this; + } + + public Builder bits(int bits) { + this.bits = bits; + return this; + } + + @Override + public RQ build() { + return new RQ(this); + } + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/SQ.java b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/SQ.java new file mode 100644 index 000000000..ccd9f7070 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/SQ.java @@ -0,0 +1,69 @@ +package io.weaviate.client6.v1.api.collections.quantizers; + +import java.util.function.Function; + +import com.google.gson.annotations.SerializedName; + +import io.weaviate.client6.v1.api.collections.Quantization; +import io.weaviate.client6.v1.internal.ObjectBuilder; + +public record SQ( + @SerializedName("enabled") boolean enabled, + @SerializedName("rescore_limit") Integer rescoreLimit, + @SerializedName("training_limit") Integer trainingLimit, + @SerializedName("cache") Boolean cache) implements Quantization { + + @Override + public Quantization.Kind _kind() { + return Quantization.Kind.SQ; + } + + @Override + public Object _self() { + return this; + } + + public static SQ of() { + return of(ObjectBuilder.identity()); + } + + public static SQ of(Function> fn) { + return fn.apply(new Builder()).build(); + } + + public SQ(Builder builder) { + this(builder.enabled, builder.rescoreLimit, builder.trainingLimit, builder.cache); + } + + public static class Builder implements ObjectBuilder { + private boolean enabled = true; + private Integer rescoreLimit; + private Integer trainingLimit; + private Boolean cache; + + public Builder enabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public Builder rescoreLimit(int rescoreLimit) { + this.rescoreLimit = rescoreLimit; + return this; + } + + public Builder trainingLimit(int trainingLimit) { + this.trainingLimit = trainingLimit; + return this; + } + + public Builder cache(boolean enabled) { + this.cache = enabled; + return this; + } + + @Override + public SQ build() { + return new SQ(this); + } + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/Uncompressed.java b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/Uncompressed.java new file mode 100644 index 000000000..ad84ed7c9 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/collections/quantizers/Uncompressed.java @@ -0,0 +1,20 @@ +package io.weaviate.client6.v1.api.collections.quantizers; + +import io.weaviate.client6.v1.api.collections.Quantization; + +public record Uncompressed() implements Quantization { + + @Override + public Quantization.Kind _kind() { + return Quantization.Kind.UNCOMPRESSED; + } + + @Override + public Object _self() { + return true; + } + + public static Uncompressed of() { + return new Uncompressed(); + } +} 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 ea55850f8..c5611ef19 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 @@ -7,6 +7,7 @@ import com.google.gson.annotations.SerializedName; +import io.weaviate.client6.v1.api.collections.Quantization; import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; import io.weaviate.client6.v1.internal.ObjectBuilder; @@ -15,7 +16,9 @@ public record Img2VecNeuralVectorizer( /** BLOB properties included in the embedding. */ @SerializedName("imageFields") List imageFields, /** Vector index configuration. */ - VectorIndex vectorIndex) implements VectorConfig { + VectorIndex vectorIndex, + /** Vector quantization method. */ + Quantization quantization) implements VectorConfig { @Override public VectorConfig.Kind _kind() { @@ -36,12 +39,13 @@ public static Img2VecNeuralVectorizer of(Function { private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; private List imageFields = new ArrayList<>(); + private Quantization quantization; /** Add BLOB properties to include in the embedding. */ public Builder imageFields(List fields) { @@ -66,6 +70,11 @@ public Builder vectorIndex(VectorIndex vectorIndex) { return this; } + public Builder quantization(Quantization quantization) { + this.quantization = quantization; + return this; + } + @Override public Img2VecNeuralVectorizer build() { return new Img2VecNeuralVectorizer(this); 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 4676ac805..07ccfb0ef 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 @@ -8,6 +8,7 @@ import com.google.gson.annotations.SerializedName; +import io.weaviate.client6.v1.api.collections.Quantization; import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; import io.weaviate.client6.v1.internal.ObjectBuilder; @@ -22,7 +23,9 @@ public record Multi2VecClipVectorizer( /** Weights of the included properties. */ @SerializedName("weights") Weights weights, /** Vector index configuration. */ - VectorIndex vectorIndex) implements VectorConfig { + VectorIndex vectorIndex, + /** Vector quantization method. */ + Quantization quantization) implements VectorConfig { private static record Weights( /** @@ -63,11 +66,13 @@ public Multi2VecClipVectorizer(Builder builder) { new Weights( builder.imageFields.values().stream().toList(), builder.textFields.values().stream().toList()), - builder.vectorIndex); + builder.vectorIndex, + builder.quantization); } public static class Builder implements ObjectBuilder { private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; + private Quantization quantization; private String inferenceUrl; private Map imageFields = new HashMap<>(); private Map textFields = new HashMap<>(); @@ -134,6 +139,11 @@ public Builder vectorIndex(VectorIndex vectorIndex) { return this; } + public Builder quantization(Quantization quantization) { + this.quantization = quantization; + return this; + } + @Override public Multi2VecClipVectorizer build() { return new Multi2VecClipVectorizer(this); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/SelfProvidedVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/SelfProvidedVectorizer.java index e8aefdf03..269053fb0 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/SelfProvidedVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/SelfProvidedVectorizer.java @@ -2,12 +2,16 @@ import java.util.function.Function; +import io.weaviate.client6.v1.api.collections.Quantization; import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; -import io.weaviate.client6.v1.api.collections.vectorindex.Hnsw; import io.weaviate.client6.v1.internal.ObjectBuilder; -public record SelfProvidedVectorizer(VectorIndex vectorIndex) implements VectorConfig { +public record SelfProvidedVectorizer( + /** Vector index configuration. */ + VectorIndex vectorIndex, + /** Vector quantization method. */ + Quantization quantization) implements VectorConfig { @Override public Kind _kind() { return VectorConfig.Kind.NONE; @@ -27,17 +31,23 @@ public static SelfProvidedVectorizer of(Function { - private VectorIndex vectorIndex = Hnsw.of(); + private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; + private Quantization quantization; public Builder vectorIndex(VectorIndex vectorIndex) { this.vectorIndex = vectorIndex; return this; } + public Builder quantization(Quantization quantization) { + this.quantization = quantization; + return this; + } + @Override public SelfProvidedVectorizer build() { return new SelfProvidedVectorizer(this); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java index b55b78be4..9f7a4808a 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecContextionaryVectorizer.java @@ -8,6 +8,7 @@ import com.google.gson.annotations.SerializedName; +import io.weaviate.client6.v1.api.collections.Quantization; import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; import io.weaviate.client6.v1.internal.ObjectBuilder; @@ -25,7 +26,9 @@ public record Text2VecContextionaryVectorizer( /** Properties included in the embedding. */ @SerializedName("sourceProperties") List sourceProperties, /** Vector index configuration. */ - VectorIndex vectorIndex) implements VectorConfig { + VectorIndex vectorIndex, + /** Vector quantization method. */ + Quantization quantization) implements VectorConfig { @Override public VectorConfig.Kind _kind() { @@ -50,18 +53,20 @@ public static Text2VecContextionaryVectorizer of( * Canonical constructor always sets {@link #vectorizeCollectionName} to false. */ public Text2VecContextionaryVectorizer(boolean vectorizeCollectionName, List sourceProperties, - VectorIndex vectorIndex) { + VectorIndex vectorIndex, Quantization quantization) { this.vectorizeCollectionName = false; - this.vectorIndex = vectorIndex; this.sourceProperties = Collections.emptyList(); + this.vectorIndex = vectorIndex; + this.quantization = quantization; } public Text2VecContextionaryVectorizer(Builder builder) { - this(builder.vectorizeCollectionName, builder.sourceProperties, builder.vectorIndex); + this(builder.vectorizeCollectionName, builder.sourceProperties, builder.vectorIndex, builder.quantization); } public static class Builder implements ObjectBuilder { private final boolean vectorizeCollectionName = false; + private Quantization quantization; private List sourceProperties = new ArrayList<>(); private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; @@ -89,6 +94,11 @@ public Builder vectorIndex(VectorIndex vectorIndex) { return this; } + public Builder quantization(Quantization quantization) { + this.quantization = quantization; + return this; + } + public Text2VecContextionaryVectorizer build() { return new Text2VecContextionaryVectorizer(this); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java index b62e93623..629befb73 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorizers/Text2VecWeaviateVectorizer.java @@ -7,6 +7,7 @@ import com.google.gson.annotations.SerializedName; +import io.weaviate.client6.v1.api.collections.Quantization; import io.weaviate.client6.v1.api.collections.VectorConfig; import io.weaviate.client6.v1.api.collections.VectorIndex; import io.weaviate.client6.v1.internal.ObjectBuilder; @@ -21,7 +22,9 @@ public record Text2VecWeaviateVectorizer( /** Properties included in the embedding. */ @SerializedName("sourceProperties") List sourceProperties, /** Vector index configuration. */ - VectorIndex vectorIndex) implements VectorConfig { + VectorIndex vectorIndex, + /** Vector quantization method. */ + Quantization quantization) implements VectorConfig { @Override public VectorConfig.Kind _kind() { @@ -47,7 +50,8 @@ public Text2VecWeaviateVectorizer(Builder builder) { builder.dimensions, builder.model, builder.sourceProperties, - builder.vectorIndex); + builder.vectorIndex, + builder.quantization); } public static final String SNOWFLAKE_ARCTIC_EMBED_M_15 = "Snowflake/snowflake-arctic-embed-m-v1.5"; @@ -55,6 +59,7 @@ public Text2VecWeaviateVectorizer(Builder builder) { public static class Builder implements ObjectBuilder { private VectorIndex vectorIndex = VectorIndex.DEFAULT_VECTOR_INDEX; + private Quantization quantization; private String inferenceUrl; private Integer dimensions; private String model; @@ -110,6 +115,11 @@ public Builder vectorIndex(VectorIndex vectorIndex) { return this; } + public Builder quantization(Quantization quantization) { + this.quantization = quantization; + return this; + } + public Text2VecWeaviateVectorizer build() { return new Text2VecWeaviateVectorizer(this); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java b/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java index 9f195539e..d800b2f48 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java +++ b/src/main/java/io/weaviate/client6/v1/internal/json/JSON.java @@ -27,6 +27,8 @@ public final class JSON { io.weaviate.client6.v1.api.collections.Reranker.CustomTypeAdapterFactory.INSTANCE); gsonBuilder.registerTypeAdapterFactory( io.weaviate.client6.v1.api.collections.Generative.CustomTypeAdapterFactory.INSTANCE); + gsonBuilder.registerTypeAdapterFactory( + io.weaviate.client6.v1.api.collections.Quantization.CustomTypeAdapterFactory.INSTANCE); gsonBuilder.registerTypeAdapterFactory( io.weaviate.client6.v1.internal.DateUtil.CustomTypeAdapterFactory.INSTANCE); 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 559754367..9a6825945 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 @@ -18,6 +18,7 @@ import io.weaviate.client6.v1.api.collections.Generative; import io.weaviate.client6.v1.api.collections.ObjectMetadata; import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.Quantization; import io.weaviate.client6.v1.api.collections.ReferenceProperty; import io.weaviate.client6.v1.api.collections.Reranker; import io.weaviate.client6.v1.api.collections.Tokenization; @@ -146,6 +147,40 @@ public static Object[][] testCases() { } """, }, + { + VectorConfig.class, + SelfProvidedVectorizer.of(none -> none + .quantization(Quantization.bq(bq -> bq + .rescoreLimit(10) + .cache(true)))), + """ + { + "vectorIndexType": "hnsw", + "vectorizer": {"none": {}}, + "vectorIndexConfig": { + "bq": { + "enabled": true, + "rescore_limit": 10, + "cache": true + } + } + } + """, + }, + { + VectorConfig.class, + SelfProvidedVectorizer.of(none -> none + .quantization(Quantization.uncompressed())), + """ + { + "vectorIndexType": "hnsw", + "vectorizer": {"none": {}}, + "vectorIndexConfig": { + "skipDefaultQuantization": true + } + } + """, + }, { VectorConfig.class, SelfProvidedVectorizer.of(none -> none From bdc7a16fe106a7fff3c92737208bdeeba4447436 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 16 Sep 2025 11:04:19 +0200 Subject: [PATCH 09/16] feat: add multiTenancy setter for update collection request Rename vectors -> vectorConfig --- .../config/UpdateCollectionRequest.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java index f2372b23c..f0093492b 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/config/UpdateCollectionRequest.java @@ -8,6 +8,7 @@ import io.weaviate.client6.v1.api.collections.CollectionConfig; import io.weaviate.client6.v1.api.collections.Generative; import io.weaviate.client6.v1.api.collections.InvertedIndex; +import io.weaviate.client6.v1.api.collections.MultiTenancy; import io.weaviate.client6.v1.api.collections.Replication; import io.weaviate.client6.v1.api.collections.Reranker; import io.weaviate.client6.v1.api.collections.VectorConfig; @@ -95,17 +96,27 @@ public Builder generativeModule(Generative generativeModule) { return this; } - public final Builder vectors(Map vectors) { + public final Builder vectorConfig(Map vectors) { this.newCollection.vectorConfig(vectors); return this; } @SafeVarargs - public final Builder vectors(Map.Entry... vectors) { + public final Builder vectorConfig(Map.Entry... vectors) { this.newCollection.vectorConfig(vectors); return this; } + public Builder multiTenancy(MultiTenancy multiTenancy) { + this.newCollection.multiTenancy(multiTenancy); + return this; + } + + public Builder multiTenancy(Function> fn) { + this.newCollection.multiTenancy(fn); + return this; + } + @Override public UpdateCollectionRequest build() { return new UpdateCollectionRequest(this); From ae534ae172e5ced0e2096176f7cb6fc10ca8baf0 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 16 Sep 2025 12:02:03 +0200 Subject: [PATCH 10/16] feat: check connection to cluster when creating a new client 1) Ping /live endpoint once we have the RestTransport but before the client is fully initialized. If the instance is not reachable, close the existing resouces and throw a WeaviateConnectException. The user can catch that earlier in their application, e.g. at startup, not when they send the first request. 2) Renamed connection helper methods: - local -> connectToLocal - wcd -> connectToWeaviateCloud - custom -> connectToCustom --- .../java/io/weaviate/containers/Weaviate.java | 2 +- .../client6/v1/api/WeaviateApiException.java | 6 +-- .../client6/v1/api/WeaviateClient.java | 50 +++++++++++++------ .../client6/v1/api/WeaviateClientAsync.java | 41 +++++++++++---- .../v1/api/WeaviateConnectException.java | 16 ++++++ .../v1/api/WeaviateOAuthException.java | 5 +- .../v1/api/WeaviateTransportException.java | 16 ++++++ .../internal/rest/DefaultRestTransport.java | 8 ++- .../v1/api/WeaviateClientAsyncTest.java | 29 +++++++++++ .../client6/v1/api/WeaviateClientTest.java | 29 +++++++++++ 10 files changed, 165 insertions(+), 37 deletions(-) create mode 100644 src/main/java/io/weaviate/client6/v1/api/WeaviateConnectException.java create mode 100644 src/main/java/io/weaviate/client6/v1/api/WeaviateTransportException.java create mode 100644 src/test/java/io/weaviate/client6/v1/api/WeaviateClientAsyncTest.java create mode 100644 src/test/java/io/weaviate/client6/v1/api/WeaviateClientTest.java diff --git a/src/it/java/io/weaviate/containers/Weaviate.java b/src/it/java/io/weaviate/containers/Weaviate.java index e1bb7157f..aaeeb30c6 100644 --- a/src/it/java/io/weaviate/containers/Weaviate.java +++ b/src/it/java/io/weaviate/containers/Weaviate.java @@ -78,7 +78,7 @@ public WeaviateClient getNewClient(Function .grpcHost(host) .httpPort(getMappedPort(8080)) .grpcPort(getMappedPort(50051))); - return WeaviateClient.custom(customFn); + return WeaviateClient.connectToCustom(customFn); } public static Weaviate createDefault() { diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java index 84fffeaee..e91bc1265 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java @@ -1,9 +1,9 @@ package io.weaviate.client6.v1.api; /** - * Exception class thrown by client API message when the request's reached the - * server, but the operation did not complete successfully either either due to - * a bad request or a server error. + * Exception class thrown by client when the request had reached the + * server, but the operation did not complete successfully either + * due to a bad request or a server error. */ public class WeaviateApiException extends WeaviateException { private final String errorMessage; diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java index 970bbcfd2..f4d76ea51 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java @@ -32,8 +32,6 @@ public class WeaviateClient implements AutoCloseable { public final WeaviateAliasClient alias; public WeaviateClient(Config config) { - this.config = config; - RestTransportOptions restOpt; GrpcChannelOptions grpcOpt; if (config.authentication() == null) { @@ -52,11 +50,33 @@ public WeaviateClient(Config config) { grpcOpt = config.grpcTransportOptions(tokenProvider); } - this.restTransport = new DefaultRestTransport(restOpt); - this.grpcTransport = new DefaultGrpcTransport(grpcOpt); + // Initialize REST transport to a temporary variable to dispose of + // the associated resources in case we have to throw an exception. + // Assign to this.restTransport only once we're in the clear to + // avoid publishing the object before it's fully initialized. + var _restTransport = new DefaultRestTransport(restOpt); + boolean isLive = false; + try { + isLive = _restTransport.performRequest(null, IsLiveRequest._ENDPOINT); + } catch (IOException e) { + throw new WeaviateConnectException(e); + } + + if (!isLive) { + var ex = new WeaviateConnectException("Weaviate not available at " + restOpt.baseUrl()); + try { + _restTransport.close(); + } catch (Exception e) { + ex.addSuppressed(e); + } + throw ex; + } + this.restTransport = _restTransport; + this.grpcTransport = new DefaultGrpcTransport(grpcOpt); this.alias = new WeaviateAliasClient(restTransport); this.collections = new WeaviateCollectionsClient(restTransport, grpcTransport); + this.config = config; } /** @@ -77,7 +97,7 @@ public WeaviateClient(Config config) { * Example: * *
{@code
-   * var client = WeaviateClient.local();
+   * var client = WeaviateClient.connectToLocal();
    *
    * // Need to make the next request non-blocking
    * try (final var async = client.async()) {
@@ -92,9 +112,9 @@ public WeaviateClient(Config config) {
    * If you only intend to use {@link WeaviateClientAsync}, prefer creating it
    * directly via one of its static factories:
    * 
    - *
  • {@link WeaviateClientAsync#local} - *
  • {@link WeaviateClientAsync#wcd} - *
  • {@link WeaviateClientAsync#custom} + *
  • {@link WeaviateClientAsync#connectToLocal} + *
  • {@link WeaviateClientAsync#connectToWeaviateCloud} + *
  • {@link WeaviateClientAsync#connectToCustom} *
* * Otherwise the client wastes time initializing resources it will never use. @@ -104,29 +124,29 @@ public WeaviateClientAsync async() { } /** Connect to a local Weaviate instance. */ - public static WeaviateClient local() { - return local(ObjectBuilder.identity()); + public static WeaviateClient connectToLocal() { + return connectToLocal(ObjectBuilder.identity()); } /** Connect to a local Weaviate instance. */ - public static WeaviateClient local(Function> fn) { + public static WeaviateClient connectToLocal(Function> fn) { return new WeaviateClient(fn.apply(new Config.Local()).build()); } /** Connect to a Weaviate Cloud instance. */ - public static WeaviateClient wcd(String httpHost, String apiKey) { - return wcd(httpHost, apiKey, ObjectBuilder.identity()); + public static WeaviateClient connectToWeaviateCloud(String httpHost, String apiKey) { + return connectToWeaviateCloud(httpHost, apiKey, ObjectBuilder.identity()); } /** Connect to a Weaviate Cloud instance. */ - public static WeaviateClient wcd(String httpHost, String apiKey, + public static WeaviateClient connectToWeaviateCloud(String httpHost, String apiKey, Function> fn) { var config = new Config.WeaviateCloud(httpHost, Authentication.apiKey(apiKey)); return new WeaviateClient(fn.apply(config).build()); } /** Connect to a Weaviate instance with custom configuration. */ - public static WeaviateClient custom(Function> fn) { + public static WeaviateClient connectToCustom(Function> fn) { return new WeaviateClient(fn.apply(new Config.Custom()).build()); } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java index 2af8870e9..0d73628d4 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java @@ -45,7 +45,7 @@ public WeaviateClientAsync(Config config) { try (final var noAuthRest = new DefaultRestTransport(config.restTransportOptions())) { tokenProvider = config.authentication().getTokenProvider(noAuthRest); } catch (Exception e) { - // Generally IOExceptions are caught in TokenProvider internals. + // Generally exceptions are caught in TokenProvider internals. // This one may be thrown when noAuthRest transport is auto-closed. throw new WeaviateOAuthException(e); } @@ -53,9 +53,30 @@ public WeaviateClientAsync(Config config) { grpcOpt = config.grpcTransportOptions(tokenProvider); } - this.restTransport = new DefaultRestTransport(restOpt); - this.grpcTransport = new DefaultGrpcTransport(grpcOpt); + // Initialize REST transport to a temporary variable to dispose of + // the associated resources in case we have to throw an exception. + // Assign to this.restTransport only once we're in the clear to + // avoid publishing the object before it's fully initialized. + var _restTransport = new DefaultRestTransport(restOpt); + boolean isLive = false; + try { + isLive = _restTransport.performRequest(null, IsLiveRequest._ENDPOINT); + } catch (IOException e) { + throw new WeaviateConnectException(e); + } + if (!isLive) { + var ex = new WeaviateConnectException("Weaviate not available at " + restOpt.baseUrl()); + try { + _restTransport.close(); + } catch (Exception e) { + ex.addSuppressed(e); + } + throw ex; + } + + this.restTransport = _restTransport; + this.grpcTransport = new DefaultGrpcTransport(grpcOpt); this.alias = new WeaviateAliasClientAsync(restTransport); this.collections = new WeaviateCollectionsClientAsync(restTransport, grpcTransport); } @@ -67,8 +88,8 @@ public WeaviateClientAsync(Config config) { * This call is blocking if {@link Authentication} configured, * as the client will need to do the initial token exchange. */ - public static WeaviateClientAsync local() { - return local(ObjectBuilder.identity()); + public static WeaviateClientAsync connectToLocal() { + return connectToLocal(ObjectBuilder.identity()); } /** @@ -78,7 +99,7 @@ public static WeaviateClientAsync local() { * This call is blocking if {@link Authentication} configured, * as the client will need to do the initial token exchange. */ - public static WeaviateClientAsync local(Function> fn) { + public static WeaviateClientAsync connectToLocal(Function> fn) { return new WeaviateClientAsync(fn.apply(new Config.Local()).build()); } @@ -89,8 +110,8 @@ public static WeaviateClientAsync local(Function> fn) { var config = new Config.WeaviateCloud(httpHost, Authentication.apiKey(apiKey)); return new WeaviateClientAsync(fn.apply(config).build()); @@ -113,7 +134,7 @@ public static WeaviateClientAsync wcd(String httpHost, String apiKey, * This call is blocking if {@link Authentication} configured, * as the client will need to do the initial token exchange. */ - public static WeaviateClientAsync custom(Function> fn) { + public static WeaviateClientAsync connectToCustom(Function> fn) { return new WeaviateClientAsync(Config.of(fn)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateConnectException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateConnectException.java new file mode 100644 index 000000000..feda9c507 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateConnectException.java @@ -0,0 +1,16 @@ +package io.weaviate.client6.v1.api; + +/** Exception thrown if the Weaviate instance appears to be offline. */ +public class WeaviateConnectException extends WeaviateException { + public WeaviateConnectException(String message) { + super(message); + } + + public WeaviateConnectException(String message, Throwable cause) { + super(message, cause); + } + + public WeaviateConnectException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateOAuthException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateOAuthException.java index 4c2f7931d..61d03d01d 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateOAuthException.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateOAuthException.java @@ -1,9 +1,8 @@ package io.weaviate.client6.v1.api; /** - * Exception class thrown by client API message when the request's reached the - * server, but the operation did not complete successfully either either due to - * a bad request or a server error. + * Exception throws by the authentication layer if it encountered another + * exception at any point of obtaining the new token or rotating one. */ public class WeaviateOAuthException extends WeaviateException { public WeaviateOAuthException(String message) { diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateTransportException.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateTransportException.java new file mode 100644 index 000000000..11ec7ece9 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateTransportException.java @@ -0,0 +1,16 @@ +package io.weaviate.client6.v1.api; + +/** Exception thrown by the internal transport layer. Usually not retryable. */ +public class WeaviateTransportException extends WeaviateException { + public WeaviateTransportException(String message) { + super(message); + } + + public WeaviateTransportException(String message, Throwable cause) { + super(message, cause); + } + + public WeaviateTransportException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java index 66e1f7862..2e6323be9 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java @@ -28,6 +28,7 @@ import org.apache.hc.core5.io.CloseMode; import io.weaviate.client6.v1.api.WeaviateApiException; +import io.weaviate.client6.v1.api.WeaviateTransportException; public class DefaultRestTransport implements RestTransport { private final CloseableHttpClient httpClient; @@ -53,8 +54,7 @@ public DefaultRestTransport(RestTransportOptions transportOptions) { sslCtx.init(null, transportOptions.trustManagerFactory().getTrustManagers(), null); tlsStrategy = new DefaultClientTlsStrategy(sslCtx); } catch (NoSuchAlgorithmException | KeyManagementException e) { - // todo: throw WeaviateConnectionException - throw new RuntimeException("connect to Weaviate", e); + throw new WeaviateTransportException("init custom SSL context", e); } PoolingHttpClientConnectionManager syncManager = PoolingHttpClientConnectionManagerBuilder.create() @@ -96,7 +96,6 @@ private ClassicHttpRequest prepareClassicRequest(RequestT var method = endpoint.method(request); var uri = uri(endpoint, request); - // TODO: apply options; var req = ClassicRequestBuilder.create(method).setUri(uri); var body = endpoint.body(request); if (body != null) { @@ -180,8 +179,7 @@ private ResponseT _handleResponse(Endpoint endpoint, S return (ResponseT) ((Boolean) bool.getResult(statusCode)); } - // TODO: make it a WeaviateTransportException - throw new RuntimeException("Unhandled endpoint type " + endpoint.getClass().getSimpleName()); + throw new WeaviateTransportException("Unhandled endpoint type " + endpoint.getClass().getSimpleName()); } @Override diff --git a/src/test/java/io/weaviate/client6/v1/api/WeaviateClientAsyncTest.java b/src/test/java/io/weaviate/client6/v1/api/WeaviateClientAsyncTest.java new file mode 100644 index 000000000..e538de7a0 --- /dev/null +++ b/src/test/java/io/weaviate/client6/v1/api/WeaviateClientAsyncTest.java @@ -0,0 +1,29 @@ +package io.weaviate.client6.v1.api; + +import org.junit.Test; + +public class WeaviateClientAsyncTest { + + @SuppressWarnings("resource") + @Test(expected = WeaviateConnectException.class) + public void testFailedConnection() { + var config = new Config.Local(); + config.host("localhost").port(1234); + new WeaviateClientAsync(config.build()); + } + + @Test(expected = WeaviateConnectException.class) + public void testFailedConnection_Local() { + WeaviateClientAsync.connectToLocal(); + } + + @Test(expected = WeaviateConnectException.class) + public void testFailedConnection_WeaviateCloud() { + WeaviateClientAsync.connectToWeaviateCloud("no-cluster.io", "no-key"); + } + + @Test(expected = WeaviateConnectException.class) + public void testFailedConnection_Custom() { + WeaviateClient.connectToCustom(conn -> conn.httpHost("localhost").httpPort(1234)); + } +} diff --git a/src/test/java/io/weaviate/client6/v1/api/WeaviateClientTest.java b/src/test/java/io/weaviate/client6/v1/api/WeaviateClientTest.java new file mode 100644 index 000000000..e51bd66c3 --- /dev/null +++ b/src/test/java/io/weaviate/client6/v1/api/WeaviateClientTest.java @@ -0,0 +1,29 @@ +package io.weaviate.client6.v1.api; + +import org.junit.Test; + +public class WeaviateClientTest { + + @SuppressWarnings("resource") + @Test(expected = WeaviateConnectException.class) + public void testFailedConnection() { + var config = new Config.Local(); + config.host("localhost").port(1234); + new WeaviateClient(config.build()); + } + + @Test(expected = WeaviateConnectException.class) + public void testFailedConnection_Local() { + WeaviateClient.connectToLocal(); + } + + @Test(expected = WeaviateConnectException.class) + public void testFailedConnection_WeaviateCloud() { + WeaviateClient.connectToWeaviateCloud("no-cluster.io", "no-key"); + } + + @Test(expected = WeaviateConnectException.class) + public void testFailedConnection_Custom() { + WeaviateClient.connectToCustom(conn -> conn.httpHost("localhost").httpPort(1234)); + } +} From cab1067493f3c7012b622dd3e837843237f8684c Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 16 Sep 2025 12:30:05 +0200 Subject: [PATCH 11/16] ci: force GH to pull the latest branch state --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index a0acd1121..303bba5d8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,6 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} - name: Login to Docker Hub if: ${{ !github.event.pull_request.head.repo.fork }} uses: docker/login-action@v2 From 5e8dc8f84b621a2eb8598999f80d40fb45014002 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 16 Sep 2025 12:36:33 +0200 Subject: [PATCH 12/16] debug: delete SearchITest.java --- .../io/weaviate/integration/SearchITest.java | 531 ------------------ 1 file changed, 531 deletions(-) delete mode 100644 src/it/java/io/weaviate/integration/SearchITest.java diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java deleted file mode 100644 index f42290c74..000000000 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ /dev/null @@ -1,531 +0,0 @@ -package io.weaviate.integration; - -import java.io.IOException; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; - -import org.assertj.core.api.Assertions; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.TestRule; - -import io.weaviate.ConcurrentTest; -import io.weaviate.client6.v1.api.WeaviateApiException; -import io.weaviate.client6.v1.api.WeaviateClient; -import io.weaviate.client6.v1.api.collections.Property; -import io.weaviate.client6.v1.api.collections.ReferenceProperty; -import io.weaviate.client6.v1.api.collections.VectorConfig; -import io.weaviate.client6.v1.api.collections.Vectors; -import io.weaviate.client6.v1.api.collections.WeaviateMetadata; -import io.weaviate.client6.v1.api.collections.WeaviateObject; -import io.weaviate.client6.v1.api.collections.data.Reference; -import io.weaviate.client6.v1.api.collections.query.GroupBy; -import io.weaviate.client6.v1.api.collections.query.Metadata; -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.SortBy; -import io.weaviate.client6.v1.api.collections.query.Where; -import io.weaviate.containers.Container; -import io.weaviate.containers.Container.ContainerGroup; -import io.weaviate.containers.Contextionary; -import io.weaviate.containers.Img2VecNeural; -import io.weaviate.containers.Weaviate; - -public class SearchITest extends ConcurrentTest { - private static final ContainerGroup compose = Container.compose( - Weaviate.custom() - .withContextionaryUrl(Contextionary.URL) - .withImageInference(Img2VecNeural.URL, Img2VecNeural.MODULE) - .build(), - Container.IMG2VEC_NEURAL, - Container.CONTEXTIONARY); - @ClassRule // Bind containers to the lifetime of the test - public static final TestRule _rule = compose.asTestRule(); - private static final WeaviateClient client = compose.getClient(); - - private static final String COLLECTION = unique("Things"); - private static final String VECTOR_INDEX = "bring_your_own"; - private static final List CATEGORIES = List.of("red", "green"); - - /** - * One of the inserted vectors which will be used as target vector for search. - */ - private static float[] searchVector; - - @BeforeClass - public static void beforeAll() throws IOException { - createTestCollection(); - var created = populateTest(10); - searchVector = created.values().iterator().next(); - } - - @Test - public void testNearVector() { - var things = client.collections.use(COLLECTION); - var result = things.query.nearVector(searchVector, - opt -> opt - .distance(2f) - .limit(3) - .returnMetadata(Metadata.DISTANCE)); - - Assertions.assertThat(result.objects()).hasSize(3); - float maxDistance = Collections.max(result.objects(), - Comparator.comparing(obj -> obj.metadata().distance())).metadata().distance(); - Assertions.assertThat(maxDistance).isLessThanOrEqualTo(2f); - } - - @Test - public void testNearVector_groupBy() { - var things = client.collections.use(COLLECTION); - var result = things.query.nearVector(searchVector, - opt -> opt.distance(10f), - GroupBy.property("category", 2, 5)); - - Assertions.assertThat(result.groups()) - .as("group per category").containsOnlyKeys(CATEGORIES) - .hasSizeLessThanOrEqualTo(2) - .allSatisfy((category, group) -> { - Assertions.assertThat(group) - .as("group name").returns(category, QueryResponseGroup::name); - Assertions.assertThat(group.numberOfObjects()) - .as("[%s] has 1+ object", category).isLessThanOrEqualTo(5L); - }); - - Assertions.assertThat(result.objects()) - .as("object belongs a group") - .allMatch(obj -> result.groups().get(obj.belongsToGroup()).objects().contains(obj)); - } - - /** - * Insert 10 objects with random vectors. - * - * @return IDs of inserted objects and their corresponding vectors. - */ - private static Map populateTest(int n) throws IOException { - var created = new HashMap(); - - var things = client.collections.use(COLLECTION); - for (int i = 0; i < n; i++) { - var vector = randomVector(10, -.01f, .001f); - var object = things.data.insert( - Map.of("category", CATEGORIES.get(i % CATEGORIES.size())), - metadata -> metadata - .uuid(randomUUID()) - .vectors(Vectors.of(VECTOR_INDEX, vector))); - - created.put(object.metadata().uuid(), vector); - } - - return created; - } - - /** - * Create {@link COLLECTION} with {@link VECTOR_INDEX} vector index. - * - * @throws IOException - */ - private static void createTestCollection() throws IOException { - client.collections.create(COLLECTION, cfg -> cfg - .properties(Property.text("category")) - .vectorConfig(VectorConfig.selfProvided(VECTOR_INDEX))); - } - - @Test - public void testNearText() throws IOException { - var nsSongs = ns("Songs"); - client.collections.create(nsSongs, - col -> col - .properties(Property.text("title")) - .vectorConfig(VectorConfig.text2vecContextionary())); - - var songs = client.collections.use(nsSongs); - var submarine = songs.data.insert(Map.of("title", "Yellow Submarine")); - songs.data.insert(Map.of("title", "Run Through The Jungle")); - songs.data.insert(Map.of("title", "Welcome To The Jungle")); - - var result = songs.query.nearText("forest", - opt -> opt - .distance(0.5f) - .moveTo(.98f, to -> to.concepts("tropical")) - .moveAway(.4f, away -> away.uuids(submarine.metadata().uuid())) - .returnProperties("title")); - - Assertions.assertThat(result.objects()).hasSize(2) - .extracting(WeaviateObject::properties).allSatisfy( - properties -> Assertions.assertThat(properties) - .allSatisfy((_k, v) -> Assertions.assertThat((String) v).contains("Jungle"))); - } - - @Test - public void testNearText_groupBy() throws IOException { - var vectorizer = VectorConfig.text2vecContextionary(); - - var nsArtists = ns("Artists"); - client.collections.create(nsArtists, - col -> col - .properties(Property.text("name")) - .vectorConfig(vectorizer)); - - var artists = client.collections.use(nsArtists); - var beatles = artists.data.insert(Map.of("name", "Beatles")); - var ccr = artists.data.insert(Map.of("name", "CCR")); - - var nsSongs = ns("Songs"); - client.collections.create(nsSongs, - col -> col - .properties(Property.text("title")) - .references(ReferenceProperty.to("performedBy", nsArtists)) - .vectorConfig(vectorizer)); - - var songs = client.collections.use(nsSongs); - songs.data.insert(Map.of("title", "Yellow Submarine"), - s -> s.reference("performedBy", Reference.objects(beatles))); - songs.data.insert(Map.of("title", "Run Through The Jungle"), - s -> s.reference("performedBy", Reference.objects(ccr))); - - var result = songs.query.nearText("nature", - opt -> opt.returnProperties("title"), - GroupBy.property("performedBy", 2, 1)); - - Assertions.assertThat(result.groups()).hasSize(2) - .containsOnlyKeys( - "weaviate://localhost/%s/%s".formatted(nsArtists, beatles.metadata().uuid()), - "weaviate://localhost/%s/%s".formatted(nsArtists, ccr.metadata().uuid())); - } - - @Test - public void testNearImage() throws IOException { - var nsCats = ns("Cats"); - - client.collections.create(nsCats, - collection -> collection - .properties( - Property.text("breed"), - Property.blob("img")) - .vectorConfig(VectorConfig.img2vecNeural( - i2v -> i2v.imageFields("img")))); - - var cats = client.collections.use(nsCats); - cats.data.insert(Map.of( - "breed", "ragdoll", - "img", EncodedMedia.IMAGE)); - - var got = cats.query.nearImage(EncodedMedia.IMAGE, - opt -> opt.returnProperties("breed")); - - Assertions.assertThat(got.objects()).hasSize(1).first() - .extracting(WeaviateObject::properties, InstanceOfAssertFactories.MAP) - .extractingByKey("breed").isEqualTo("ragdoll"); - } - - @Test - public void testFetchObjectsWithFilters() throws IOException { - var nsHats = ns("Hats"); - - client.collections.create(nsHats, - collection -> collection - .properties( - Property.text("colour"), - Property.integer("size"))); - - var hats = client.collections.use(nsHats); - - /* blackHat */ hats.data.insert(Map.of("colour", "black", "size", 6)); - var redHat = hats.data.insert(Map.of("colour", "red", "size", 5)); - var greenHat = hats.data.insert(Map.of("colour", "green", "size", 1)); - var hugeHat = hats.data.insert(Map.of("colour", "orange", "size", 40)); - - var got = hats.query.fetchObjects( - query -> query.where( - Where.or( - Where.property("colour").eq("orange"), - Where.and( - Where.property("size").gte(1), - Where.property("size").lt(6))))); - - Assertions.assertThat(got.objects()) - .extracting(hat -> hat.metadata().uuid()) - .containsOnly( - redHat.metadata().uuid(), - greenHat.metadata().uuid(), - hugeHat.metadata().uuid()); - - } - - @Test - public void testFetchObjectsWithSort() throws Exception { - var nsNumbers = ns("Numbers"); - - // Arrange - client.collections.create(nsNumbers, - c -> c.properties(Property.integer("value"))); - - var numbers = client.collections.use(nsNumbers); - - var one = numbers.data.insert(Map.of("value", 1L)); - var two = numbers.data.insert(Map.of("value", 2L)); - var three = numbers.data.insert(Map.of("value", 3L)); - - // Act: sort ascending - var asc = numbers.query.fetchObjects( - q -> q.sort(SortBy.property("value"))); - - Assertions.assertThat(asc.objects()) - .as("value asc") - .hasSize(3) - .extracting(WeaviateObject::properties) - .extracting(object -> object.get("value")) - .containsExactly(1L, 2L, 3L); - - // Act: sort descending - var desc = numbers.query.fetchObjects( - q -> q.sort(SortBy.property("value").desc())); - - Assertions.assertThat(desc.objects()) - .as("value desc") - .hasSize(3) - .extracting(WeaviateObject::properties) - .extracting(object -> object.get("value")) - .containsExactly(3L, 2L, 1L); - - // Act: sort by creation time asc - var created = numbers.query.fetchObjects( - q -> q.sort(SortBy.creationTime())); - - Assertions.assertThat(created.objects()) - .as("create time asc") - .hasSize(3) - .extracting(WeaviateObject::uuid) - .containsExactly(one.uuid(), two.uuid(), three.uuid()); - - // Act: sort by updated time desc - numbers.data.update(one.uuid(), upd -> upd.properties(Map.of("value", -1L))); - Thread.sleep(10); - numbers.data.update(two.uuid(), upd -> upd.properties(Map.of("value", -2L))); - Thread.sleep(10); - numbers.data.update(three.uuid(), upd -> upd.properties(Map.of("value", -3L))); - - var updated = numbers.query.fetchObjects( - q -> q.sort( - // Both sort operators imply ordering 3-2-1 - SortBy.lastUpdateTime().desc(), - SortBy.property("value").asc())); - - Assertions.assertThat(updated.objects()) - .as("last update time desc + value asc") - .hasSize(3) - .extracting(WeaviateObject::uuid) - .containsExactly(three.uuid(), two.uuid(), one.uuid()); - } - - @Test - public void testBm25() throws IOException, InterruptedException, ExecutionException { - var nsWords = ns("Words"); - - client.collections.create(nsWords, - collection -> collection - .properties( - Property.text("relevant"), - Property.text("irrelevant"))); - - var words = client.collections.use(nsWords); - - /* notWant */ words.data.insert(Map.of("relevant", "elefant", "irrelevant", "dollar bill")); - var want = words.data.insert(Map.of("relevant", "a dime a dollar", "irrelevant", "euro")); - - var dollarWorlds = words.query.bm25( - "dollar", - bm25 -> bm25.queryProperties("relevant")); - - Assertions.assertThat(dollarWorlds.objects()) - .hasSize(1) - .extracting(WeaviateObject::metadata).extracting(QueryMetadata::uuid) - .containsOnly(want.metadata().uuid()); - } - - /** - * Minimal test to verify async functionality works as expected. - * We will extend our testing framework at a later stage to automatically - * test both sync/async clients. - */ - @Test - public void testBm25_async() throws Exception, InterruptedException, ExecutionException { - var nsWords = ns("Words"); - - try (final var async = client.async()) { - async.collections.create(nsWords, - collection -> collection - .properties( - Property.text("relevant"), - Property.text("irrelevant"))) - .get(); - - var words = async.collections.use(nsWords); - - /* notWant */ words.data.insert(Map.of("relevant", "elefant", "irrelevant", "dollar bill")).get(); - var want = words.data.insert(Map.of("relevant", "a dime a dollar", "irrelevant", "euro")).get(); - - var dollarWorlds = words.query.bm25( - "dollar", - bm25 -> bm25.queryProperties("relevant")).get(); - - Assertions.assertThat(dollarWorlds.objects()) - .hasSize(1) - .extracting(WeaviateObject::metadata).extracting(QueryMetadata::uuid) - .containsOnly(want.metadata().uuid()); - } - } - - @Test - public void testNearObject() throws IOException { - // Arrange - var nsAnimals = ns("Animals"); - - client.collections.create(nsAnimals, - collection -> collection - .properties(Property.text("kind")) - .vectorConfig(VectorConfig.text2vecContextionary())); - - var animals = client.collections.use(nsAnimals); - - // Terrestrial animals - var cat = animals.data.insert(Map.of("kind", "cat")); - var lion = animals.data.insert(Map.of("kind", "lion")); - // Aquatic animal - animals.data.insert(Map.of("kind", "dolphin")); - - // Act - var terrestrial = animals.query.nearObject(cat.metadata().uuid(), - q -> q.excludeSelf().limit(1)); - - // Assert - Assertions.assertThat(terrestrial.objects()) - .hasSize(1) - .extracting(WeaviateObject::metadata).extracting(WeaviateMetadata::uuid) - .containsOnly(lion.metadata().uuid()); - } - - @Test - public void testHybrid() throws IOException { - // Arrange - var nsHobbies = ns("Hobbies"); - - client.collections.create(nsHobbies, - collection -> collection - .properties(Property.text("name"), Property.text("description")) - .vectorConfig(VectorConfig.text2vecContextionary())); - - var hobbies = client.collections.use(nsHobbies); - - var skiing = hobbies.data.insert(Map.of("name", "skiing", "description", "winter sport")); - hobbies.data.insert(Map.of("name", "jetskiing", "description", "water sport")); - - // Act - var winterSport = hobbies.query.hybrid("winter", - hybrid -> hybrid - .returnMetadata(Metadata.SCORE, Metadata.EXPLAIN_SCORE)); - - // Assert - Assertions.assertThat(winterSport.objects()) - .hasSize(1) - .extracting(WeaviateObject::metadata).extracting(WeaviateMetadata::uuid) - .containsOnly(skiing.metadata().uuid()); - - var first = winterSport.objects().get(0); - Assertions.assertThat(first.metadata().score()) - .as("metadata::score").isNotNull(); - Assertions.assertThat(first.metadata().explainScore()) - .as("metadata::explainScore").isNotNull(); - } - - @Test(expected = WeaviateApiException.class) - public void testBadRequest() throws IOException { - // Arrange - var nsThings = ns("Things"); - - client.collections.create(nsThings, - collection -> collection - .properties(Property.text("name")) - .vectorConfig(VectorConfig.text2vecContextionary())); - - var things = client.collections.use(nsThings); - var balloon = things.data.insert(Map.of("name", "balloon")); - - things.query.nearObject(balloon.uuid(), q -> q.limit(-1)); - } - - @Test(expected = WeaviateApiException.class) - public void testBadRequest_async() throws Throwable { - // Arrange - var nsThings = ns("Things"); - - try (final var async = client.async()) { - async.collections.create(nsThings, - collection -> collection - .properties(Property.text("name")) - .vectorConfig(VectorConfig.text2vecContextionary())) - .join(); - - var things = async.collections.use(nsThings); - var balloon = things.data.insert(Map.of("name", "balloon")).join(); - - try { - things.query.nearObject(balloon.uuid(), q -> q.limit(-1)).join(); - } catch (CompletionException e) { - throw e.getCause(); // CompletableFuture exceptions are always wrapped - } - } - } - - @Test - public void testMetadataAll() throws IOException { - // Arrange - var nsThings = ns("Things"); - client.collections.create(nsThings, - c -> c - .properties(Property.text("name")) - .vectors(Vectorizers.text2vecContextionary( - t2v -> t2v.sourceProperties("name")))); - - var things = client.collections.use(nsThings); - var frisbee = things.data.insert(Map.of("name", "orange disc")); - - // Act - var gotHybrid = things.query.hybrid("orange", q -> q - .queryProperties("name") - .returnMetadata(Metadata.ALL)); - - var gotNearText = things.query.nearText("frisbee", q -> q - .returnMetadata(Metadata.ALL)); - - // Assert - var metadataHybrid = Assertions.assertThat(gotHybrid.objects()) - .hasSize(1) - .extracting(WeaviateObject::metadata) - .first().actual(); - - Assertions.assertThat(metadataHybrid.uuid()).as("uuid").isNotNull().isEqualTo(frisbee.uuid()); - Assertions.assertThat(metadataHybrid.creationTimeUnix()).as("creationTimeUnix").isNotNull(); - Assertions.assertThat(metadataHybrid.lastUpdateTimeUnix()).as("lastUpdateTimeUnix").isNotNull(); - Assertions.assertThat(metadataHybrid.score()).as("score").isNotNull(); - Assertions.assertThat(metadataHybrid.explainScore()).as("explainScore").isNotNull().isNotEqualTo(""); - - var metadataNearText = Assertions.assertThat(gotNearText.objects()) - .hasSize(1) - .extracting(WeaviateObject::metadata) - .first().actual(); - - Assertions.assertThat(metadataNearText.uuid()).as("uuid").isNotNull().isEqualTo(frisbee.uuid()); - Assertions.assertThat(metadataNearText.creationTimeUnix()).as("creationTimeUnix").isNotNull(); - Assertions.assertThat(metadataNearText.lastUpdateTimeUnix()).as("lastUpdateTimeUnix").isNotNull(); - Assertions.assertThat(metadataNearText.distance()).as("distance").isNotNull(); - Assertions.assertThat(metadataNearText.certainty()).as("certainty").isNotNull(); - } -} From ec0a921f92710872a55a4f023490e01b13901900 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 16 Sep 2025 12:43:32 +0200 Subject: [PATCH 13/16] Revert "debug: delete SearchITest.java" This reverts commit 5e8dc8f84b621a2eb8598999f80d40fb45014002. --- .../io/weaviate/integration/SearchITest.java | 531 ++++++++++++++++++ 1 file changed, 531 insertions(+) create mode 100644 src/it/java/io/weaviate/integration/SearchITest.java diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java new file mode 100644 index 000000000..f42290c74 --- /dev/null +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -0,0 +1,531 @@ +package io.weaviate.integration; + +import java.io.IOException; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TestRule; + +import io.weaviate.ConcurrentTest; +import io.weaviate.client6.v1.api.WeaviateApiException; +import io.weaviate.client6.v1.api.WeaviateClient; +import io.weaviate.client6.v1.api.collections.Property; +import io.weaviate.client6.v1.api.collections.ReferenceProperty; +import io.weaviate.client6.v1.api.collections.VectorConfig; +import io.weaviate.client6.v1.api.collections.Vectors; +import io.weaviate.client6.v1.api.collections.WeaviateMetadata; +import io.weaviate.client6.v1.api.collections.WeaviateObject; +import io.weaviate.client6.v1.api.collections.data.Reference; +import io.weaviate.client6.v1.api.collections.query.GroupBy; +import io.weaviate.client6.v1.api.collections.query.Metadata; +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.SortBy; +import io.weaviate.client6.v1.api.collections.query.Where; +import io.weaviate.containers.Container; +import io.weaviate.containers.Container.ContainerGroup; +import io.weaviate.containers.Contextionary; +import io.weaviate.containers.Img2VecNeural; +import io.weaviate.containers.Weaviate; + +public class SearchITest extends ConcurrentTest { + private static final ContainerGroup compose = Container.compose( + Weaviate.custom() + .withContextionaryUrl(Contextionary.URL) + .withImageInference(Img2VecNeural.URL, Img2VecNeural.MODULE) + .build(), + Container.IMG2VEC_NEURAL, + Container.CONTEXTIONARY); + @ClassRule // Bind containers to the lifetime of the test + public static final TestRule _rule = compose.asTestRule(); + private static final WeaviateClient client = compose.getClient(); + + private static final String COLLECTION = unique("Things"); + private static final String VECTOR_INDEX = "bring_your_own"; + private static final List CATEGORIES = List.of("red", "green"); + + /** + * One of the inserted vectors which will be used as target vector for search. + */ + private static float[] searchVector; + + @BeforeClass + public static void beforeAll() throws IOException { + createTestCollection(); + var created = populateTest(10); + searchVector = created.values().iterator().next(); + } + + @Test + public void testNearVector() { + var things = client.collections.use(COLLECTION); + var result = things.query.nearVector(searchVector, + opt -> opt + .distance(2f) + .limit(3) + .returnMetadata(Metadata.DISTANCE)); + + Assertions.assertThat(result.objects()).hasSize(3); + float maxDistance = Collections.max(result.objects(), + Comparator.comparing(obj -> obj.metadata().distance())).metadata().distance(); + Assertions.assertThat(maxDistance).isLessThanOrEqualTo(2f); + } + + @Test + public void testNearVector_groupBy() { + var things = client.collections.use(COLLECTION); + var result = things.query.nearVector(searchVector, + opt -> opt.distance(10f), + GroupBy.property("category", 2, 5)); + + Assertions.assertThat(result.groups()) + .as("group per category").containsOnlyKeys(CATEGORIES) + .hasSizeLessThanOrEqualTo(2) + .allSatisfy((category, group) -> { + Assertions.assertThat(group) + .as("group name").returns(category, QueryResponseGroup::name); + Assertions.assertThat(group.numberOfObjects()) + .as("[%s] has 1+ object", category).isLessThanOrEqualTo(5L); + }); + + Assertions.assertThat(result.objects()) + .as("object belongs a group") + .allMatch(obj -> result.groups().get(obj.belongsToGroup()).objects().contains(obj)); + } + + /** + * Insert 10 objects with random vectors. + * + * @return IDs of inserted objects and their corresponding vectors. + */ + private static Map populateTest(int n) throws IOException { + var created = new HashMap(); + + var things = client.collections.use(COLLECTION); + for (int i = 0; i < n; i++) { + var vector = randomVector(10, -.01f, .001f); + var object = things.data.insert( + Map.of("category", CATEGORIES.get(i % CATEGORIES.size())), + metadata -> metadata + .uuid(randomUUID()) + .vectors(Vectors.of(VECTOR_INDEX, vector))); + + created.put(object.metadata().uuid(), vector); + } + + return created; + } + + /** + * Create {@link COLLECTION} with {@link VECTOR_INDEX} vector index. + * + * @throws IOException + */ + private static void createTestCollection() throws IOException { + client.collections.create(COLLECTION, cfg -> cfg + .properties(Property.text("category")) + .vectorConfig(VectorConfig.selfProvided(VECTOR_INDEX))); + } + + @Test + public void testNearText() throws IOException { + var nsSongs = ns("Songs"); + client.collections.create(nsSongs, + col -> col + .properties(Property.text("title")) + .vectorConfig(VectorConfig.text2vecContextionary())); + + var songs = client.collections.use(nsSongs); + var submarine = songs.data.insert(Map.of("title", "Yellow Submarine")); + songs.data.insert(Map.of("title", "Run Through The Jungle")); + songs.data.insert(Map.of("title", "Welcome To The Jungle")); + + var result = songs.query.nearText("forest", + opt -> opt + .distance(0.5f) + .moveTo(.98f, to -> to.concepts("tropical")) + .moveAway(.4f, away -> away.uuids(submarine.metadata().uuid())) + .returnProperties("title")); + + Assertions.assertThat(result.objects()).hasSize(2) + .extracting(WeaviateObject::properties).allSatisfy( + properties -> Assertions.assertThat(properties) + .allSatisfy((_k, v) -> Assertions.assertThat((String) v).contains("Jungle"))); + } + + @Test + public void testNearText_groupBy() throws IOException { + var vectorizer = VectorConfig.text2vecContextionary(); + + var nsArtists = ns("Artists"); + client.collections.create(nsArtists, + col -> col + .properties(Property.text("name")) + .vectorConfig(vectorizer)); + + var artists = client.collections.use(nsArtists); + var beatles = artists.data.insert(Map.of("name", "Beatles")); + var ccr = artists.data.insert(Map.of("name", "CCR")); + + var nsSongs = ns("Songs"); + client.collections.create(nsSongs, + col -> col + .properties(Property.text("title")) + .references(ReferenceProperty.to("performedBy", nsArtists)) + .vectorConfig(vectorizer)); + + var songs = client.collections.use(nsSongs); + songs.data.insert(Map.of("title", "Yellow Submarine"), + s -> s.reference("performedBy", Reference.objects(beatles))); + songs.data.insert(Map.of("title", "Run Through The Jungle"), + s -> s.reference("performedBy", Reference.objects(ccr))); + + var result = songs.query.nearText("nature", + opt -> opt.returnProperties("title"), + GroupBy.property("performedBy", 2, 1)); + + Assertions.assertThat(result.groups()).hasSize(2) + .containsOnlyKeys( + "weaviate://localhost/%s/%s".formatted(nsArtists, beatles.metadata().uuid()), + "weaviate://localhost/%s/%s".formatted(nsArtists, ccr.metadata().uuid())); + } + + @Test + public void testNearImage() throws IOException { + var nsCats = ns("Cats"); + + client.collections.create(nsCats, + collection -> collection + .properties( + Property.text("breed"), + Property.blob("img")) + .vectorConfig(VectorConfig.img2vecNeural( + i2v -> i2v.imageFields("img")))); + + var cats = client.collections.use(nsCats); + cats.data.insert(Map.of( + "breed", "ragdoll", + "img", EncodedMedia.IMAGE)); + + var got = cats.query.nearImage(EncodedMedia.IMAGE, + opt -> opt.returnProperties("breed")); + + Assertions.assertThat(got.objects()).hasSize(1).first() + .extracting(WeaviateObject::properties, InstanceOfAssertFactories.MAP) + .extractingByKey("breed").isEqualTo("ragdoll"); + } + + @Test + public void testFetchObjectsWithFilters() throws IOException { + var nsHats = ns("Hats"); + + client.collections.create(nsHats, + collection -> collection + .properties( + Property.text("colour"), + Property.integer("size"))); + + var hats = client.collections.use(nsHats); + + /* blackHat */ hats.data.insert(Map.of("colour", "black", "size", 6)); + var redHat = hats.data.insert(Map.of("colour", "red", "size", 5)); + var greenHat = hats.data.insert(Map.of("colour", "green", "size", 1)); + var hugeHat = hats.data.insert(Map.of("colour", "orange", "size", 40)); + + var got = hats.query.fetchObjects( + query -> query.where( + Where.or( + Where.property("colour").eq("orange"), + Where.and( + Where.property("size").gte(1), + Where.property("size").lt(6))))); + + Assertions.assertThat(got.objects()) + .extracting(hat -> hat.metadata().uuid()) + .containsOnly( + redHat.metadata().uuid(), + greenHat.metadata().uuid(), + hugeHat.metadata().uuid()); + + } + + @Test + public void testFetchObjectsWithSort() throws Exception { + var nsNumbers = ns("Numbers"); + + // Arrange + client.collections.create(nsNumbers, + c -> c.properties(Property.integer("value"))); + + var numbers = client.collections.use(nsNumbers); + + var one = numbers.data.insert(Map.of("value", 1L)); + var two = numbers.data.insert(Map.of("value", 2L)); + var three = numbers.data.insert(Map.of("value", 3L)); + + // Act: sort ascending + var asc = numbers.query.fetchObjects( + q -> q.sort(SortBy.property("value"))); + + Assertions.assertThat(asc.objects()) + .as("value asc") + .hasSize(3) + .extracting(WeaviateObject::properties) + .extracting(object -> object.get("value")) + .containsExactly(1L, 2L, 3L); + + // Act: sort descending + var desc = numbers.query.fetchObjects( + q -> q.sort(SortBy.property("value").desc())); + + Assertions.assertThat(desc.objects()) + .as("value desc") + .hasSize(3) + .extracting(WeaviateObject::properties) + .extracting(object -> object.get("value")) + .containsExactly(3L, 2L, 1L); + + // Act: sort by creation time asc + var created = numbers.query.fetchObjects( + q -> q.sort(SortBy.creationTime())); + + Assertions.assertThat(created.objects()) + .as("create time asc") + .hasSize(3) + .extracting(WeaviateObject::uuid) + .containsExactly(one.uuid(), two.uuid(), three.uuid()); + + // Act: sort by updated time desc + numbers.data.update(one.uuid(), upd -> upd.properties(Map.of("value", -1L))); + Thread.sleep(10); + numbers.data.update(two.uuid(), upd -> upd.properties(Map.of("value", -2L))); + Thread.sleep(10); + numbers.data.update(three.uuid(), upd -> upd.properties(Map.of("value", -3L))); + + var updated = numbers.query.fetchObjects( + q -> q.sort( + // Both sort operators imply ordering 3-2-1 + SortBy.lastUpdateTime().desc(), + SortBy.property("value").asc())); + + Assertions.assertThat(updated.objects()) + .as("last update time desc + value asc") + .hasSize(3) + .extracting(WeaviateObject::uuid) + .containsExactly(three.uuid(), two.uuid(), one.uuid()); + } + + @Test + public void testBm25() throws IOException, InterruptedException, ExecutionException { + var nsWords = ns("Words"); + + client.collections.create(nsWords, + collection -> collection + .properties( + Property.text("relevant"), + Property.text("irrelevant"))); + + var words = client.collections.use(nsWords); + + /* notWant */ words.data.insert(Map.of("relevant", "elefant", "irrelevant", "dollar bill")); + var want = words.data.insert(Map.of("relevant", "a dime a dollar", "irrelevant", "euro")); + + var dollarWorlds = words.query.bm25( + "dollar", + bm25 -> bm25.queryProperties("relevant")); + + Assertions.assertThat(dollarWorlds.objects()) + .hasSize(1) + .extracting(WeaviateObject::metadata).extracting(QueryMetadata::uuid) + .containsOnly(want.metadata().uuid()); + } + + /** + * Minimal test to verify async functionality works as expected. + * We will extend our testing framework at a later stage to automatically + * test both sync/async clients. + */ + @Test + public void testBm25_async() throws Exception, InterruptedException, ExecutionException { + var nsWords = ns("Words"); + + try (final var async = client.async()) { + async.collections.create(nsWords, + collection -> collection + .properties( + Property.text("relevant"), + Property.text("irrelevant"))) + .get(); + + var words = async.collections.use(nsWords); + + /* notWant */ words.data.insert(Map.of("relevant", "elefant", "irrelevant", "dollar bill")).get(); + var want = words.data.insert(Map.of("relevant", "a dime a dollar", "irrelevant", "euro")).get(); + + var dollarWorlds = words.query.bm25( + "dollar", + bm25 -> bm25.queryProperties("relevant")).get(); + + Assertions.assertThat(dollarWorlds.objects()) + .hasSize(1) + .extracting(WeaviateObject::metadata).extracting(QueryMetadata::uuid) + .containsOnly(want.metadata().uuid()); + } + } + + @Test + public void testNearObject() throws IOException { + // Arrange + var nsAnimals = ns("Animals"); + + client.collections.create(nsAnimals, + collection -> collection + .properties(Property.text("kind")) + .vectorConfig(VectorConfig.text2vecContextionary())); + + var animals = client.collections.use(nsAnimals); + + // Terrestrial animals + var cat = animals.data.insert(Map.of("kind", "cat")); + var lion = animals.data.insert(Map.of("kind", "lion")); + // Aquatic animal + animals.data.insert(Map.of("kind", "dolphin")); + + // Act + var terrestrial = animals.query.nearObject(cat.metadata().uuid(), + q -> q.excludeSelf().limit(1)); + + // Assert + Assertions.assertThat(terrestrial.objects()) + .hasSize(1) + .extracting(WeaviateObject::metadata).extracting(WeaviateMetadata::uuid) + .containsOnly(lion.metadata().uuid()); + } + + @Test + public void testHybrid() throws IOException { + // Arrange + var nsHobbies = ns("Hobbies"); + + client.collections.create(nsHobbies, + collection -> collection + .properties(Property.text("name"), Property.text("description")) + .vectorConfig(VectorConfig.text2vecContextionary())); + + var hobbies = client.collections.use(nsHobbies); + + var skiing = hobbies.data.insert(Map.of("name", "skiing", "description", "winter sport")); + hobbies.data.insert(Map.of("name", "jetskiing", "description", "water sport")); + + // Act + var winterSport = hobbies.query.hybrid("winter", + hybrid -> hybrid + .returnMetadata(Metadata.SCORE, Metadata.EXPLAIN_SCORE)); + + // Assert + Assertions.assertThat(winterSport.objects()) + .hasSize(1) + .extracting(WeaviateObject::metadata).extracting(WeaviateMetadata::uuid) + .containsOnly(skiing.metadata().uuid()); + + var first = winterSport.objects().get(0); + Assertions.assertThat(first.metadata().score()) + .as("metadata::score").isNotNull(); + Assertions.assertThat(first.metadata().explainScore()) + .as("metadata::explainScore").isNotNull(); + } + + @Test(expected = WeaviateApiException.class) + public void testBadRequest() throws IOException { + // Arrange + var nsThings = ns("Things"); + + client.collections.create(nsThings, + collection -> collection + .properties(Property.text("name")) + .vectorConfig(VectorConfig.text2vecContextionary())); + + var things = client.collections.use(nsThings); + var balloon = things.data.insert(Map.of("name", "balloon")); + + things.query.nearObject(balloon.uuid(), q -> q.limit(-1)); + } + + @Test(expected = WeaviateApiException.class) + public void testBadRequest_async() throws Throwable { + // Arrange + var nsThings = ns("Things"); + + try (final var async = client.async()) { + async.collections.create(nsThings, + collection -> collection + .properties(Property.text("name")) + .vectorConfig(VectorConfig.text2vecContextionary())) + .join(); + + var things = async.collections.use(nsThings); + var balloon = things.data.insert(Map.of("name", "balloon")).join(); + + try { + things.query.nearObject(balloon.uuid(), q -> q.limit(-1)).join(); + } catch (CompletionException e) { + throw e.getCause(); // CompletableFuture exceptions are always wrapped + } + } + } + + @Test + public void testMetadataAll() throws IOException { + // Arrange + var nsThings = ns("Things"); + client.collections.create(nsThings, + c -> c + .properties(Property.text("name")) + .vectors(Vectorizers.text2vecContextionary( + t2v -> t2v.sourceProperties("name")))); + + var things = client.collections.use(nsThings); + var frisbee = things.data.insert(Map.of("name", "orange disc")); + + // Act + var gotHybrid = things.query.hybrid("orange", q -> q + .queryProperties("name") + .returnMetadata(Metadata.ALL)); + + var gotNearText = things.query.nearText("frisbee", q -> q + .returnMetadata(Metadata.ALL)); + + // Assert + var metadataHybrid = Assertions.assertThat(gotHybrid.objects()) + .hasSize(1) + .extracting(WeaviateObject::metadata) + .first().actual(); + + Assertions.assertThat(metadataHybrid.uuid()).as("uuid").isNotNull().isEqualTo(frisbee.uuid()); + Assertions.assertThat(metadataHybrid.creationTimeUnix()).as("creationTimeUnix").isNotNull(); + Assertions.assertThat(metadataHybrid.lastUpdateTimeUnix()).as("lastUpdateTimeUnix").isNotNull(); + Assertions.assertThat(metadataHybrid.score()).as("score").isNotNull(); + Assertions.assertThat(metadataHybrid.explainScore()).as("explainScore").isNotNull().isNotEqualTo(""); + + var metadataNearText = Assertions.assertThat(gotNearText.objects()) + .hasSize(1) + .extracting(WeaviateObject::metadata) + .first().actual(); + + Assertions.assertThat(metadataNearText.uuid()).as("uuid").isNotNull().isEqualTo(frisbee.uuid()); + Assertions.assertThat(metadataNearText.creationTimeUnix()).as("creationTimeUnix").isNotNull(); + Assertions.assertThat(metadataNearText.lastUpdateTimeUnix()).as("lastUpdateTimeUnix").isNotNull(); + Assertions.assertThat(metadataNearText.distance()).as("distance").isNotNull(); + Assertions.assertThat(metadataNearText.certainty()).as("certainty").isNotNull(); + } +} From 490137228c63edf75355938574a90544f8b2c6e6 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 16 Sep 2025 12:45:05 +0200 Subject: [PATCH 14/16] fix: update naming --- src/it/java/io/weaviate/integration/SearchITest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index f42290c74..418fb3189 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -491,7 +491,7 @@ public void testMetadataAll() throws IOException { client.collections.create(nsThings, c -> c .properties(Property.text("name")) - .vectors(Vectorizers.text2vecContextionary( + .vectorConfig(VectorConfig.text2vecContextionary( t2v -> t2v.sourceProperties("name")))); var things = client.collections.use(nsThings); From 56666eaeeaa793c36b24047c9e02ceb7d30355ef Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 16 Sep 2025 13:02:20 +0200 Subject: [PATCH 15/16] feat: set max gRPC message size based on server's /meta information --- .../client6/v1/api/InstanceMetadata.java | 2 +- .../client6/v1/api/WeaviateClient.java | 6 ++++++ .../client6/v1/api/WeaviateClientAsync.java | 6 ++++++ .../client6/v1/internal/TransportOptions.java | 12 ++++++------ .../v1/internal/grpc/DefaultGrpcTransport.java | 6 ++++++ .../v1/internal/grpc/GrpcChannelOptions.java | 18 +++++++++++++++++- 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/InstanceMetadata.java b/src/main/java/io/weaviate/client6/v1/api/InstanceMetadata.java index 9664e8490..c3ce05765 100644 --- a/src/main/java/io/weaviate/client6/v1/api/InstanceMetadata.java +++ b/src/main/java/io/weaviate/client6/v1/api/InstanceMetadata.java @@ -8,5 +8,5 @@ public record InstanceMetadata( @SerializedName("hostname") String hostName, @SerializedName("version") String version, @SerializedName("modules") Map modules, - @SerializedName("grpcMaxMessageSize") Long grpcMaxMessageSize) { + @SerializedName("grpcMaxMessageSize") Integer grpcMaxMessageSize) { } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java index f4d76ea51..0101dc122 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java @@ -56,8 +56,10 @@ public WeaviateClient(Config config) { // avoid publishing the object before it's fully initialized. var _restTransport = new DefaultRestTransport(restOpt); boolean isLive = false; + InstanceMetadata meta = null; try { isLive = _restTransport.performRequest(null, IsLiveRequest._ENDPOINT); + meta = _restTransport.performRequest(null, InstanceMetadataRequest._ENDPOINT); } catch (IOException e) { throw new WeaviateConnectException(e); } @@ -72,6 +74,10 @@ public WeaviateClient(Config config) { throw ex; } + if (meta.grpcMaxMessageSize() != null) { + grpcOpt = grpcOpt.withMaxMessageSize(meta.grpcMaxMessageSize()); + } + this.restTransport = _restTransport; this.grpcTransport = new DefaultGrpcTransport(grpcOpt); this.alias = new WeaviateAliasClient(restTransport); diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java index 0d73628d4..7255fbc6e 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java @@ -59,8 +59,10 @@ public WeaviateClientAsync(Config config) { // avoid publishing the object before it's fully initialized. var _restTransport = new DefaultRestTransport(restOpt); boolean isLive = false; + InstanceMetadata meta = null; try { isLive = _restTransport.performRequest(null, IsLiveRequest._ENDPOINT); + meta = _restTransport.performRequest(null, InstanceMetadataRequest._ENDPOINT); } catch (IOException e) { throw new WeaviateConnectException(e); } @@ -75,6 +77,10 @@ public WeaviateClientAsync(Config config) { throw ex; } + if (meta.grpcMaxMessageSize() != null) { + grpcOpt = grpcOpt.withMaxMessageSize(meta.grpcMaxMessageSize()); + } + this.restTransport = _restTransport; this.grpcTransport = new DefaultGrpcTransport(grpcOpt); this.alias = new WeaviateAliasClientAsync(restTransport); diff --git a/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java b/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java index 8700bee47..60d3db0d5 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java @@ -4,12 +4,12 @@ import javax.net.ssl.TrustManagerFactory; public abstract class TransportOptions { - private final String scheme; - private final String host; - private final int port; - private final TokenProvider tokenProvider; - private final H headers; - private final TrustManagerFactory trustManagerFactory; + protected final String scheme; + protected final String host; + protected final int port; + protected final TokenProvider tokenProvider; + protected final H headers; + protected final TrustManagerFactory trustManagerFactory; protected TransportOptions(String scheme, String host, int port, H headers, TokenProvider tokenProvider, TrustManagerFactory tmf) { diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java index a9a50c200..bb4a8aa88 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java @@ -36,6 +36,12 @@ public DefaultGrpcTransport(GrpcChannelOptions transportOptions) { var futureStub = WeaviateGrpc.newFutureStub(channel) .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(transportOptions.headers())); + if (transportOptions.maxMessageSize() != null) { + var max = transportOptions.maxMessageSize(); + blockingStub.withMaxInboundMessageSize(max).withMaxOutboundMessageSize(max); + futureStub.withMaxInboundMessageSize(max).withMaxOutboundMessageSize(max); + } + if (transportOptions.tokenProvider() != null) { this.callCredentials = new TokenCallCredentials(transportOptions.tokenProvider()); blockingStub = blockingStub.withCallCredentials(callCredentials); diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java index 6e32d9738..e59893412 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java @@ -9,9 +9,25 @@ import io.weaviate.client6.v1.internal.TransportOptions; public class GrpcChannelOptions extends TransportOptions { + private final Integer maxMessageSize; + public GrpcChannelOptions(String scheme, String host, int port, Map headers, TokenProvider tokenProvider, TrustManagerFactory tmf) { - super(scheme, host, port, buildMetadata(headers), tokenProvider, tmf); + this(scheme, host, port, buildMetadata(headers), tokenProvider, tmf, null); + } + + private GrpcChannelOptions(String scheme, String host, int port, Metadata headers, + TokenProvider tokenProvider, TrustManagerFactory tmf, Integer maxMessageSize) { + super(scheme, host, port, headers, tokenProvider, tmf); + this.maxMessageSize = maxMessageSize; + } + + public GrpcChannelOptions withMaxMessageSize(int maxMessageSize) { + return new GrpcChannelOptions(scheme, host, port, headers, tokenProvider, trustManagerFactory, maxMessageSize); + } + + public Integer maxMessageSize() { + return maxMessageSize; } private static final Metadata buildMetadata(Map headers) { From da953d6582985abaa85d4ded4af7feab2f57d84a Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Tue, 16 Sep 2025 13:20:26 +0200 Subject: [PATCH 16/16] test: add another test case for Vectors::toString --- .../weaviate/client6/v1/api/collections/VectorsTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/io/weaviate/client6/v1/api/collections/VectorsTest.java b/src/test/java/io/weaviate/client6/v1/api/collections/VectorsTest.java index b28b5d36d..817d79b15 100644 --- a/src/test/java/io/weaviate/client6/v1/api/collections/VectorsTest.java +++ b/src/test/java/io/weaviate/client6/v1/api/collections/VectorsTest.java @@ -17,4 +17,13 @@ public void testToString_2d() { var got = vector.toString(); Assertions.assertThat(got).isEqualTo("Vectors(default=[[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]])"); } + + @Test + public void testToString_multiple() { + var title = Vectors.of("title", new float[] { 1, 2, 3 }); + var body = Vectors.of("body", new float[][] { { 1, 2, 3 }, { 1, 2, 3 } }); + var vectors = new Vectors(title, body); + var got = vectors.toString(); + Assertions.assertThat(got).isEqualTo("Vectors(title=[1.0, 2.0, 3.0], body=[[1.0, 2.0, 3.0], [1.0, 2.0, 3.0]])"); + } }