diff --git a/src/it/java/io/weaviate/integration/DataITest.java b/src/it/java/io/weaviate/integration/DataITest.java index 827236163..f5c1957fe 100644 --- a/src/it/java/io/weaviate/integration/DataITest.java +++ b/src/it/java/io/weaviate/integration/DataITest.java @@ -46,7 +46,9 @@ public void testCreateGetDelete() throws IOException { var object = artists.query.byId(id, query -> query .returnProperties("name") - .returnMetadata(Metadata.ID, Metadata.VECTOR)); + .returnMetadata( + Metadata.UUID, Metadata.VECTOR, + Metadata.CREATION_TIME_UNIX, Metadata.LAST_UPDATE_TIME_UNIX)); Assertions.assertThat(artists.data.exists(id)) .as("object exists after insert").isTrue(); @@ -62,6 +64,11 @@ public void testCreateGetDelete() throws IOException { Assertions.assertThat(obj.properties()) .as("has expected properties") .containsEntry("name", "john doe"); + + Assertions.assertThat(obj.metadata().creationTimeUnix()) + .as("creationTimeUnix").isNotNull(); + Assertions.assertThat(obj.metadata().lastUpdateTimeUnix()) + .as("lastUpdateTimeUnix").isNotNull(); }); artists.data.delete(id); @@ -249,7 +256,7 @@ public void testUpdate() throws IOException { .returnMetadata(Metadata.VECTOR) .returnReferences( QueryReference.single("writtenBy", - writtenBy -> writtenBy.returnMetadata(Metadata.ID)))); + writtenBy -> writtenBy.returnMetadata(Metadata.UUID)))); Assertions.assertThat(updIvanhoe).get() .satisfies(book -> { @@ -377,7 +384,7 @@ public void testReferenceAddMany() throws IOException { var goodburgAirports = cities.query.byId(goodburg.metadata().uuid(), city -> city.returnReferences( QueryReference.single("hasAirports", - airport -> airport.returnMetadata(Metadata.ID)))); + airport -> airport.returnMetadata(Metadata.UUID)))); Assertions.assertThat(goodburgAirports).get() .as("Goodburg has 3 airports") diff --git a/src/it/java/io/weaviate/integration/ReferencesITest.java b/src/it/java/io/weaviate/integration/ReferencesITest.java index 8ee613b4f..b28bb907f 100644 --- a/src/it/java/io/weaviate/integration/ReferencesITest.java +++ b/src/it/java/io/weaviate/integration/ReferencesITest.java @@ -95,9 +95,9 @@ public void testReferences() throws IOException { var gotAlex = artists.query.byId(alex.metadata().uuid(), opt -> opt.returnReferences( QueryReference.multi("hasAwards", nsOscar, - ref -> ref.returnMetadata(Metadata.ID)), + ref -> ref.returnMetadata(Metadata.UUID)), QueryReference.multi("hasAwards", nsGrammy, - ref -> ref.returnMetadata(Metadata.ID)))); + ref -> ref.returnMetadata(Metadata.UUID)))); Assertions.assertThat(gotAlex).get() .as("Artists: fetch by id including hasAwards references") @@ -166,7 +166,7 @@ public void testNestedReferences() throws IOException { .returnReferences( QueryReference.single("presentedBy", r -> r.returnProperties("ceo"))) // Grammy ID - .returnMetadata(Metadata.ID)))); + .returnMetadata(Metadata.UUID)))); Assertions.assertThat(gotAlex).get() .as("Artists: fetch by id including nested references") diff --git a/src/it/java/io/weaviate/integration/SearchITest.java b/src/it/java/io/weaviate/integration/SearchITest.java index 3f67240a5..c6283e651 100644 --- a/src/it/java/io/weaviate/integration/SearchITest.java +++ b/src/it/java/io/weaviate/integration/SearchITest.java @@ -358,10 +358,20 @@ public void testHybrid() throws IOException { hobbies.data.insert(Map.of("name", "jetskiing", "description", "water sport")); // Act - var winterSport = hobbies.query.hybrid("winter"); + var winterSport = hobbies.query.hybrid("winter", + hybrid -> hybrid + .returnMetadata(Metadata.UUID, 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(); } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/BaseQueryOptions.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/BaseQueryOptions.java index 3a2815864..ffda94ab9 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/BaseQueryOptions.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/BaseQueryOptions.java @@ -152,7 +152,7 @@ final void appendTo(WeaviateProtoSearchGet.SearchRequest.Builder req) { var metadata = WeaviateProtoSearchGet.MetadataRequest.newBuilder(); if (returnMetadata.isEmpty()) { - Metadata.ID.appendTo(metadata); + Metadata.UUID.appendTo(metadata); } else { returnMetadata.forEach(m -> m.appendTo(metadata)); } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/ById.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/ById.java index f2e37f6a4..3c256b3b5 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/ById.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/ById.java @@ -74,10 +74,9 @@ public void appendTo(WeaviateProtoSearchGet.SearchRequest.Builder req) { var metadata = WeaviateProtoSearchGet.MetadataRequest.newBuilder(); if (returnMetadata.isEmpty()) { - Metadata.ID.appendTo(metadata); - } else { - returnMetadata.forEach(m -> m.appendTo(metadata)); + returnMetadata.add(Metadata.UUID); } + returnMetadata.forEach(m -> m.appendTo(metadata)); req.setMetadata(metadata); if (!returnProperties.isEmpty() || !returnReferences.isEmpty()) { diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/Metadata.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/Metadata.java index 62396632f..3a0574b56 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/Metadata.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/Metadata.java @@ -1,5 +1,6 @@ package io.weaviate.client6.v1.api.collections.query; +import io.weaviate.client6.v1.api.collections.vectorindex.Distance; import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateProtoSearchGet; /** @@ -10,8 +11,101 @@ public interface Metadata { void appendTo(WeaviateProtoSearchGet.MetadataRequest.Builder metadata); - public static final Metadata ID = MetadataField.ID; + /** Include UUID of the object in the metadata response. */ + public static final Metadata UUID = MetadataField.UUID; + /** Include associated vector in the metadata response. */ public static final Metadata VECTOR = MetadataField.VECTOR; + /** Include object creation time in the metadata response. */ + public static final Metadata CREATION_TIME_UNIX = MetadataField.CREATION_TIME_UNIX; + /** Include last update time in the metadata response. */ + public static final Metadata LAST_UPDATE_TIME_UNIX = MetadataField.LAST_UPDATE_TIME_UNIX; + /** + * Include raw distance determined as part of the vector search. + * The units will correspond to the distance metric configured for the vector + * index; by default {@link Distance#COSINE}. + * + *

+ * Distance is only applicable to vector search results, + * i.e. all {@code Near-} queries. Hybrid search will not return a distance, + * as the BM25-VectorSearch fusion algorithm transforms the distance metric. + * + * @see + * Distance metric in Hybrid search + */ public static final Metadata DISTANCE = MetadataField.DISTANCE; + /** + * Include certainty in the metadata response. + * + *

+ * Certainty is an opinionated measure that always returns a number + * between 0 and 1. It is therefore usable with fixed-range distance metrics, + * such as {@code cosine}. + * + * @see + * Distance vs. Certainty + */ public static final Metadata CERTAINTY = MetadataField.CERTAINTY; + /** + * Include {@code BM25F} score of the search result in the metadata response. + * + *

+ * {@link Metadata#SCORE} and {@link Metadata#EXPLAIN_SCORE} are only relevant + * for Hybrid and BM25 search. + */ + public static final Metadata SCORE = MetadataField.SCORE; + /** + * Include the result score broken down into components. + * The output is an unstructured string that is mostly useful for debugging + * search results. + * + *

+ * {@link Metadata#SCORE} and {@link Metadata#EXPLAIN_SCORE} are only relevant + * for Hybrid and BM25 search. + */ + public static final Metadata EXPLAIN_SCORE = MetadataField.EXPLAIN_SCORE; + + /** + * MetadataField are collection properties that can be requested for any object. + */ + enum MetadataField implements Metadata { + UUID, + VECTOR, + CREATION_TIME_UNIX, + LAST_UPDATE_TIME_UNIX, + DISTANCE, + CERTAINTY, + SCORE, + EXPLAIN_SCORE; + + public void appendTo(WeaviateProtoSearchGet.MetadataRequest.Builder metadata) { + switch (this) { + case UUID: + metadata.setUuid(true); + break; + case VECTOR: + metadata.setVector(true); + break; + case CREATION_TIME_UNIX: + metadata.setCreationTimeUnix(true); + break; + case LAST_UPDATE_TIME_UNIX: + metadata.setLastUpdateTimeUnix(true); + break; + case DISTANCE: + metadata.setDistance(true); + break; + case CERTAINTY: + metadata.setCertainty(true); + break; + case EXPLAIN_SCORE: + metadata.setExplainScore(true); + break; + case SCORE: + metadata.setScore(true); + break; + } + } + } } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/MetadataField.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/MetadataField.java deleted file mode 100644 index 3f2fcdf82..000000000 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/MetadataField.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.weaviate.client6.v1.api.collections.query; - -import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateProtoSearchGet; - -/** - * MetadataField are collection properties that can be requested for any object. - */ -public enum MetadataField implements Metadata { - ID, - VECTOR, - DISTANCE, - CERTAINTY; - - public void appendTo(WeaviateProtoSearchGet.MetadataRequest.Builder metadata) { - switch (this) { - case ID: - metadata.setUuid(true); - break; - case VECTOR: - metadata.setVector(true); - break; - case DISTANCE: - metadata.setDistance(true); - break; - case CERTAINTY: - metadata.setCertainty(true); - break; - } - } -} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryMetadata.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryMetadata.java index d54678e67..3573292ab 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryMetadata.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryMetadata.java @@ -4,23 +4,57 @@ import io.weaviate.client6.v1.api.collections.WeaviateMetadata; import io.weaviate.client6.v1.internal.ObjectBuilder; -public record QueryMetadata(String uuid, Float distance, Float certainty, Vectors vectors) implements WeaviateMetadata { +public record QueryMetadata(String uuid, + Vectors vectors, + Long creationTimeUnix, + Long lastUpdateTimeUnix, + Float distance, + Float certainty, + Float score, + String explainScore) implements WeaviateMetadata { private QueryMetadata(Builder builder) { - this(builder.uuid, builder.distance, builder.certainty, builder.vectors); + this( + builder.uuid, + builder.vectors, + builder.creationTimeUnix, + builder.lastUpdateTimeUnix, + builder.distance, + builder.certainty, + builder.score, + builder.explainScore); } public static class Builder implements ObjectBuilder { private String uuid; + private Vectors vectors; + private Long creationTimeUnix; + private Long lastUpdateTimeUnix; private Float distance; private Float certainty; - private Vectors vectors; + private Float score; + private String explainScore; public final Builder uuid(String uuid) { this.uuid = uuid; return this; } + public final Builder vectors(Vectors vectors) { + this.vectors = vectors; + return this; + } + + public final Builder creationTimeUnix(Long creationTimeUnix) { + this.creationTimeUnix = creationTimeUnix; + return this; + } + + public final Builder lastUpdateTimeUnix(Long lastUpdateTimeUnix) { + this.lastUpdateTimeUnix = lastUpdateTimeUnix; + return this; + } + public final Builder distance(Float distance) { this.distance = distance; return this; @@ -31,8 +65,13 @@ public final Builder certainty(Float certainty) { return this; } - public final Builder vectors(Vectors vectors) { - this.vectors = vectors; + public final Builder score(Float score) { + this.score = score; + return this; + } + + public final Builder explainScore(String explainScore) { + this.explainScore = explainScore; return this; } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java index 12121f678..22ebbfa7c 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java @@ -86,16 +86,33 @@ private static WeaviateObject unmarshalResultObjec WeaviateProtoSearchGet.PropertiesResult propertiesResult, WeaviateProtoSearchGet.MetadataResult metadataResult, CollectionDescriptor descriptor) { - var res = unmarshalReferences(propertiesResult, metadataResult, descriptor); + var object = unmarshalWithReferences(propertiesResult, metadataResult, descriptor); var metadata = new QueryMetadata.Builder() - .uuid(res.metadata().uuid()) - .distance(metadataResult.getDistance()) - .certainty(metadataResult.getCertainty()) - .vectors(res.metadata().vectors()); - return new WeaviateObject<>(descriptor.name(), res.properties(), res.references(), metadata.build()); + .uuid(object.metadata().uuid()) + .vectors(object.metadata().vectors()); + + if (metadataResult.getCreationTimeUnixPresent()) { + metadata.creationTimeUnix(metadataResult.getCreationTimeUnix()); + } + if (metadataResult.getLastUpdateTimeUnixPresent()) { + metadata.lastUpdateTimeUnix(metadataResult.getLastUpdateTimeUnix()); + } + if (metadataResult.getDistancePresent()) { + metadata.distance(metadataResult.getDistance()); + } + if (metadataResult.getCertaintyPresent()) { + metadata.certainty(metadataResult.getCertainty()); + } + if (metadataResult.getScorePresent()) { + metadata.score(metadataResult.getScore()); + } + if (metadataResult.getExplainScorePresent()) { + metadata.explainScore(metadataResult.getExplainScore()); + } + return new WeaviateObject<>(descriptor.name(), object.properties(), object.references(), metadata.build()); } - private static WeaviateObject unmarshalReferences( + private static WeaviateObject unmarshalWithReferences( WeaviateProtoSearchGet.PropertiesResult propertiesResult, WeaviateProtoSearchGet.MetadataResult metadataResult, CollectionDescriptor descriptor) { @@ -114,7 +131,7 @@ private static WeaviateObject unmarshalReferences (map, ref) -> { var refObjects = ref.getPropertiesList().stream() .map(property -> { - var reference = unmarshalReferences( + var reference = unmarshalWithReferences( property, property.getMetadata(), // TODO: this should be possible to configure for ODM? CollectionDescriptor.ofMap(property.getTargetCollection())); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/vectorindex/Distance.java b/src/main/java/io/weaviate/client6/v1/api/collections/vectorindex/Distance.java index 602e90693..8938327db 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/vectorindex/Distance.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/vectorindex/Distance.java @@ -2,6 +2,13 @@ import com.google.gson.annotations.SerializedName; +/** + * Distance metrics supported for vector search. + * + * @see Availabe + * distance metrics in Weaviate + */ public enum Distance { @SerializedName("cosine") COSINE,