Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
/**
* {@link StdDeserializer} for the {@link FindAndRerankSort}.
*
* <p>Note: This only checks the shape of the sort clause, the other validation is handled when
* resolving the command to an operation.
* <p>Checks the JSON structure and mutually exclusive vector keys. Semantic validation is handled
* when resolving the command to an operation.
*/
public class FindAndRerankSortClauseDeserializer extends StdDeserializer<FindAndRerankSort> {

Expand Down Expand Up @@ -72,8 +72,7 @@ public FindAndRerankSort deserialize(
* Deserialize the sort clause from an object node.
*
* @param sort The sort clause as an object node, no check for nulls.
* @return The {@link FindAndRerankSort} that reflects the request without validation, e.g.
* checking that a vectorize or vector sort is provided but not both.
* @return The {@link FindAndRerankSort} that reflects the request after structural validation.
*/
private static FindAndRerankSort deserialize(JsonParser jsonParser, ObjectNode sort)
throws JsonMappingException {
Expand Down Expand Up @@ -105,6 +104,12 @@ private static FindAndRerankSort deserializeHybridObject(
JsonParser jsonParser, ObjectNode hybridObject) throws JsonMappingException {

var sortMatch = MATCH_SORT_FIELDS.matchAndThrow(hybridObject, jsonParser, ERROR_CONTEXT);
if (hybridObject.has(VECTOR_EMBEDDING_FIELD) && hybridObject.has(VECTOR_EMBEDDING_TEXT_FIELD)) {
throw new JsonMappingException(
jsonParser,
"$vector and $vectorize cannot be used together in a $hybrid sort",
jsonParser.currentLocation());
}
CommandFeatures commandFeatures = CommandFeatures.of(CommandFeature.HYBRID);

var vectorizeText =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,6 @@ private static List<FindAndRerankPlan.RerankLeg> multiLegs(

// the vector leg reads with the vector from the vectorize step when there is text to vectorize,
// otherwise with the vector from the command
// NOTE: when the sort has both, $vectorize wins and $vector is ignored
// TODO: GH #2616 - reject a sort that has both $vectorize and $vector
legs.add(
new FindAndRerankPlan.RerankLeg(
RerankLegType.VECTOR_LEG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,6 @@ private static Stream<Arguments> validSortsTestCases() {
new FindAndRerankSort(
RerankLegMode.MULTI_LEG, null, null, null, null, CommandFeatures.of(HYBRID))),
// ----
// maximum fields, resolver works out the valid combinations
Arguments.of(
"""
{ "$hybrid" : { "$vectorize" : "vectorize sort", "$lexical" : "lexical sort", "$vector" : [1.1, 2.2, 3.3]} }
""",
new FindAndRerankSort(
RerankLegMode.MULTI_LEG,
null,
"vectorize sort",
"lexical sort",
new float[] {1.1f, 2.2f, 3.3f},
CommandFeatures.of(HYBRID, LEXICAL, VECTOR, VECTORIZE))),
Arguments.of(
"""
{ "$hybrid" : { "$vectorize" : "vectorize sort", "$lexical" : "lexical sort"} }
Expand Down Expand Up @@ -298,50 +286,73 @@ private static Stream<Arguments> validSortsTestCases() {
// $vector variations
Arguments.of(
"""
{ "$hybrid" : { "$vectorize" : "vectorize", "$lexical" : "lexical", "$vector" : null} }
{ "$hybrid" : { "$lexical" : "lexical", "$vector" : null} }
""",
new FindAndRerankSort(
RerankLegMode.MULTI_LEG,
null,
"vectorize",
null,
"lexical",
null,
CommandFeatures.of(HYBRID, LEXICAL, VECTORIZE, VECTOR))),
CommandFeatures.of(HYBRID, LEXICAL, VECTOR))),
Arguments.of(
"""
{ "$hybrid" : { "$vectorize" : "vectorize", "$lexical" : "lexical", "$vector" : [0.1, 0.2, 0.3]} }
{ "$hybrid" : { "$lexical" : "lexical", "$vector" : [0.1, 0.2, 0.3]} }
""",
new FindAndRerankSort(
RerankLegMode.MULTI_LEG,
null,
"vectorize",
null,
"lexical",
new float[] {0.1f, 0.2f, 0.3f},
CommandFeatures.of(HYBRID, LEXICAL, VECTORIZE, VECTOR))),
CommandFeatures.of(HYBRID, LEXICAL, VECTOR))),
Arguments.of(
"""
{ "$hybrid" : { "$vectorize" : "vectorize", "$lexical" : "lexical", "$vector" : {"$binary": "%s"}} }
{ "$hybrid" : { "$lexical" : "lexical", "$vector" : {"$binary": "%s"}} }
"""
.formatted(emptyVectorBase64),
new FindAndRerankSort(
RerankLegMode.MULTI_LEG,
null,
"vectorize",
null,
"lexical",
emptyVector,
CommandFeatures.of(HYBRID, LEXICAL, VECTORIZE, VECTOR))),
CommandFeatures.of(HYBRID, LEXICAL, VECTOR))),
Arguments.of(
"""
{ "$hybrid" : { "$vectorize" : "vectorize", "$lexical" : "lexical", "$vector" : {"$binary": "%s"}} }
{ "$hybrid" : { "$lexical" : "lexical", "$vector" : {"$binary": "%s"}} }
"""
.formatted(vectorBase64),
new FindAndRerankSort(
RerankLegMode.MULTI_LEG,
null,
"vectorize",
null,
"lexical",
vector,
CommandFeatures.of(HYBRID, LEXICAL, VECTORIZE, VECTOR))));
CommandFeatures.of(HYBRID, LEXICAL, VECTOR))));
}

@ParameterizedTest
@MethodSource("conflictingVectorSorts")
void rejectsBothVectorKeysRegardlessOfTheirValues(String vectorize, String vector)
throws Exception {
String sort =
"{\"$hybrid\": {\"$vectorize\": %s, \"$vector\": %s}}".formatted(vectorize, vector);

var error =
assertThrows(
JsonMappingException.class,
() -> new ObjectMapper().readValue(sort, FindAndRerankSort.class));

assertThat(error).hasMessageContaining("$vector and $vectorize cannot be used together");
}

private static Stream<Arguments> conflictingVectorSorts() {
return Stream.of("\"search text\"", "null", "\"\"", "\" \"")
.flatMap(
vectorize ->
Stream.of("[1, 2, 3]", "[]", "null", "{\"$binary\": \"P4AAAEAAAABAQAAA\"}")
.map(vector -> Arguments.of(vectorize, vector)));
}

@ParameterizedTest
Expand Down Expand Up @@ -391,12 +402,12 @@ private static Stream<Arguments> invalidSortsTestCases() {
// Combinations
Arguments.of(
"""
{ "$hybrid" : { "$vectorize" : 1, "$lexical" : "", "$vector" : [1.1, 2.2, 3.3]} }
{ "$hybrid" : { "$vectorize" : 1, "$lexical" : ""} }
""",
"Field $vectorize may only be of types NullNode, TextNode, but got: IntNode"),
Arguments.of(
"""
{ "$hybrid" : { "$vectorize" : "", "$lexical" : 1, "$vector" : [1.1, 2.2, 3.3]} }
{ "$hybrid" : { "$vectorize" : "", "$lexical" : 1} }
""",
"Field $lexical may only be of types NullNode, TextNode, but got: IntNode"),
Arguments.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,6 @@ static Stream<Arguments> legCases() {
Collection.VECTOR_LEXICAL,
null,
List.of(vectorLeg(DEFAULT_READ_LIMIT))),
Arguments.of(
"$vectorize and $vector, $vectorize is used",
"""
{"$hybrid": {"$vectorize": "cheese", "$vector": [0.1, 0.2, 0.3]}}
""",
RERANK_OPTIONS,
Collection.VECTORIZE_NO_LEXICAL,
"cheese",
List.of(vectorizedLeg(DEFAULT_READ_LIMIT))),
Arguments.of(
"$vectorize and $lexical",
"""
Expand Down
Loading