diff --git a/src/main/java/io/stargate/sgv2/jsonapi/exception/RequestException.java b/src/main/java/io/stargate/sgv2/jsonapi/exception/RequestException.java index d5b8fc9266..de7bb8a8a4 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/exception/RequestException.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/exception/RequestException.java @@ -55,6 +55,7 @@ public enum Code implements ErrorCode { INVALID_CREATE_COLLECTION_FIELD, INVALID_RERANK_OVERRIDE, + MISSING_RERANK_ON_TEXT, MISSING_RERANK_QUERY_TEXT, REQUEST_NOT_JSON, diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/FindAndRerankOperationBuilder.java b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/FindAndRerankOperationBuilder.java index 1e40365891..0ea03bc478 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/FindAndRerankOperationBuilder.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/FindAndRerankOperationBuilder.java @@ -451,7 +451,7 @@ private PathMatchLocator passageLocator() { // user has to provide a field to rerank on finalRerankField = rerankOn; } else { - throw new IllegalArgumentException("rerankOn() - rerankOn required and not specified"); + throw RequestException.Code.MISSING_RERANK_ON_TEXT.get(); } return PathMatchLocator.forPath(finalRerankField); diff --git a/src/main/resources/errors.yaml b/src/main/resources/errors.yaml index 38906b8fe8..a9f62a652e 100644 --- a/src/main/resources/errors.yaml +++ b/src/main/resources/errors.yaml @@ -281,6 +281,24 @@ request-errors: Resend the command with a valid reranking service override, or omit the override to use the collection's default reranking configuration. + # unscoped because this touches both the sort and the options + - scope: + code: MISSING_RERANK_ON_TEXT + title: Rerank field is missing + body: |- + The findAndRerank command is missing the field to read from each document for the reranking step. + + Reranking involves using a model to compare passages of text to a user query. + + For each document to rerank, a certain field is read to extract the associated passage. + + If using `$vectorize`, the name of the rerankOn field defaults to "$vectorize"; + otherwise, there is no default and the field name must be supplied. + + The field to read must be specified as `rerankOn` in the options clause, e.g. `{"rerankOn": "title"}`. + + Resend the command including the name of the field on which reranking should be done. + # unscoped because this touches both the sort and the options - scope: code: MISSING_RERANK_QUERY_TEXT diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/FindAndRerankOperationBuilderTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/FindAndRerankOperationBuilderTest.java index 3ca81fc205..dfcbf8581d 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/FindAndRerankOperationBuilderTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/FindAndRerankOperationBuilderTest.java @@ -19,12 +19,20 @@ import io.stargate.sgv2.jsonapi.config.constants.RerankingConstants; import io.stargate.sgv2.jsonapi.exception.RequestException; import io.stargate.sgv2.jsonapi.exception.SchemaException; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.VectorColumnDefinition; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.VectorConfig; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.VectorizeDefinition; import io.stargate.sgv2.jsonapi.service.provider.ApiModelSupport; import io.stargate.sgv2.jsonapi.service.reranking.configuration.RerankingProvidersConfig; import io.stargate.sgv2.jsonapi.service.reranking.configuration.RerankingProvidersConfigImpl; import io.stargate.sgv2.jsonapi.service.reranking.operation.RerankingProvider; +import io.stargate.sgv2.jsonapi.service.schema.EmbeddingSourceModel; +import io.stargate.sgv2.jsonapi.service.schema.SimilarityFunction; +import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionLexicalDefSchemaFactory; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionRerankDef; +import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionRerankDefSchemaFactory; import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionSchemaObject; +import io.stargate.sgv2.jsonapi.service.schema.collections.IdConfig; import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; import jakarta.inject.Inject; import java.util.List; @@ -242,15 +250,123 @@ void acceptsBoundaryValues() throws Exception { .build(); } + @Test + void failsWhenMissingRerankOnAndNotVectorizeSort() throws Exception { + var commandContext = commandContext(); + var command = + command( + """ + { + "findAndRerank": { + "sort": { "$hybrid": { "$vector": [0.1, 0.2, 0.3], "$lexical": "text" } }, + "options": { + "rerankQuery": "text" + } + } + } + """); + + assertMissingRerankOnText( + "error when no rerankOn and not vectorize sort", commandContext, command); + } + + @Test + void failsWhenBlankRerankOnAndNotVectorizeSort() throws Exception { + var commandContext = commandContext(); + var command = + command( + """ + { + "findAndRerank": { + "sort": { "$hybrid": { "$vector": [0.1, 0.2, 0.3], "$lexical": "text" } }, + "options": { + "rerankOn": " ", + "rerankQuery": "text" + } + } + } + """); + + assertMissingRerankOnText( + "error when blank rerankOn and not vectorize sort", commandContext, command); + } + + @Test + void failsWhenMissingRerankOnWithLexicalSortOnVectorizeCollection() throws Exception { + var commandContext = commandContextWithVectorize(); + var command = + command( + """ + { + "findAndRerank": { + "sort": { "$hybrid": { "$lexical": "text" } }, + "options": { + "rerankQuery": "text" + } + } + } + """); + + assertMissingRerankOnText( + "error when no rerankOn on vectorize collection with lexical sort", + commandContext, + command); + } + + private void assertMissingRerankOnText( + String context, + CommandContext commandContext, + FindAndRerankCommand command) { + var ex = + org.junit.jupiter.api.Assertions.assertThrowsExactly( + RequestException.class, + () -> + new FindAndRerankOperationBuilder(commandContext) + .withCommand(command) + .withFindCommandResolver(findCommandResolver) + .build(), + context); + + assertThat(ex.code) + .as("error code is " + RequestException.Code.MISSING_RERANK_ON_TEXT.name()) + .isEqualTo(RequestException.Code.MISSING_RERANK_ON_TEXT.name()); + } + private FindAndRerankCommand command(String json) throws Exception { return objectMapper.readValue(json, FindAndRerankCommand.class); } private CommandContext commandContext() { - var commandContext = - testConstants.collectionContext( - CommandName.FIND_AND_RERANK, - testConstants.VECTOR_LEXICAL_RERANK_COLLECTION_SCHEMA_OBJECT); + return commandContext(testConstants.VECTOR_LEXICAL_RERANK_COLLECTION_SCHEMA_OBJECT); + } + + private CommandContext commandContextWithVectorize() { + var collectionSchema = + new CollectionSchemaObject( + testConstants.COLLECTION_IDENTIFIER, + IdConfig.defaultIdConfig(), + VectorConfig.fromColumnDefinitions( + List.of( + new VectorColumnDefinition( + io.stargate.sgv2.jsonapi.config.constants.DocumentConstants.Fields + .VECTOR_EMBEDDING_TEXT_FIELD, + -1, + SimilarityFunction.COSINE, + EmbeddingSourceModel.OTHER, + new VectorizeDefinition("custom", "custom", null, null)))), + null, + CollectionLexicalDefSchemaFactory.FOR_TESTING_ENABLED.currentVersion(null), + CollectionRerankDefSchemaFactory.FOR_TESTING_ENABLED.currentVersion( + new CollectionRerankDef( + true, + new CollectionRerankDef.RerankServiceDef( + "nvidia", "nvidia/llama-3.2-nv-rerankqa-1b-v2", null, null)))); + return commandContext(collectionSchema); + } + + private CommandContext commandContext( + CollectionSchemaObject schemaObject) { + var commandContext = testConstants.collectionContext(CommandName.FIND_AND_RERANK, schemaObject); var rerankingProvidersConfig = mock(RerankingProvidersConfig.class); var modelConfig = mock(RerankingProvidersConfig.RerankingProviderConfig.ModelConfig.class);