Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a66df30
Implement #2459: allow overrides for "findAndRerank" command
tatu-at-datastax Apr 27, 2026
55216df
Add block of DEPRECATED model overrides
tatu-at-datastax Apr 27, 2026
3df34e8
Config passing clean up
tatu-at-datastax Apr 27, 2026
5d43561
Change to coerce blank Strings to nulls for options
tatu-at-datastax Apr 27, 2026
f2bdfa3
Extract helper method
tatu-at-datastax Apr 27, 2026
fd060f9
Further correctness fixes
tatu-at-datastax Apr 27, 2026
51e58a9
Unify empty/missing value handling
tatu-at-datastax Apr 27, 2026
361c726
Rework based on clarified definition (update issue description).
tatu-at-datastax Apr 29, 2026
1f72f78
Minor comment improvement
tatu-at-datastax Apr 29, 2026
2dcff8c
Streamlining
tatu-at-datastax Apr 29, 2026
930f015
Minor reordering of validation
tatu-at-datastax Apr 29, 2026
743fd15
Add unit tests
tatu-at-datastax Apr 29, 2026
5d705e9
Add basic ITs as well
tatu-at-datastax Apr 29, 2026
ddec95c
Moar testing
tatu-at-datastax Apr 29, 2026
bcdfa46
Improve ITs
tatu-at-datastax Apr 29, 2026
1eae708
Simplify tests
tatu-at-datastax Apr 29, 2026
b6afdc2
Fix failing IT
tatu-at-datastax Apr 30, 2026
f2afee0
Remove unused helper method
tatu-at-datastax Apr 30, 2026
ee799c8
Fixed wording (remove "per-request" phrasing); add null checks; guard…
tatu-at-datastax May 6, 2026
6b3d02c
Refactor to reduce code duplication
tatu-at-datastax May 6, 2026
fdf1edb
Minor clean up
tatu-at-datastax May 6, 2026
63c321f
Fix 4 failing ITs
tatu-at-datastax May 6, 2026
401feb5
Merge remote-tracking branch 'origin/main' into tatu/2459-find-and-re…
tatu-at-datastax May 7, 2026
744fc18
Add a test
tatu-at-datastax May 7, 2026
5c6e8bf
Refactoring, renaming
tatu-at-datastax May 7, 2026
c70c22b
Minor renaming
tatu-at-datastax May 7, 2026
4cfeec5
Fix handling of empty Object for reranking
tatu-at-datastax May 7, 2026
d35f1ca
Merge branch 'main' into tatu/2459-find-and-rerank-override
tatu-at-datastax May 8, 2026
72c87f6
Changed logic on "empty" Override based on code review feedback
tatu-at-datastax May 12, 2026
46acd52
Forgot to check in actual change
tatu-at-datastax May 12, 2026
32e028d
Merge branch 'main' into tatu/2459-find-and-rerank-override
tatu-at-datastax May 12, 2026
aa27d8c
Add test coverage
tatu-at-datastax May 12, 2026
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 @@ -105,7 +105,21 @@ public record Options(
@Schema(
description = "Return vector embedding used for ANN sorting.",
type = SchemaType.BOOLEAN)
boolean includeSortVector) {}
boolean includeSortVector,
// Note: intentionally NOT annotated @Valid. Jakarta Bean Validation would reject any
// missing/null provider with its generic violation message; we want the explicit
// INVALID_RERANK_OVERRIDE error code produced by CollectionRerankDef.validateServiceDesc.
// Any non-null payload — including `"rerank": {}` and payloads whose fields are all
// explicit nulls — is treated as an override attempt and validated there.
@Nullable
@Schema(
description =
"Optional override for the reranking service. Completely replaces the"
+ " collection-level reranking configuration when provided. Both 'provider' and"
+ " 'modelName' are required.",
implementation = CreateCollectionCommand.Options.RerankServiceDesc.class)
@JsonProperty("rerank")
CreateCollectionCommand.Options.RerankServiceDesc rerankServiceOverride) {}

@JsonDeserialize(using = HybridLimitsDeserializer.class)
public record HybridLimits(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public enum Code implements ErrorCode<RequestException> {
HYBRID_FIELD_UNSUPPORTED_SUBFIELD_VALUE_TYPE,

INVALID_CREATE_COLLECTION_FIELD,
INVALID_RERANK_OVERRIDE,
MISSING_RERANK_QUERY_TEXT,

REQUEST_NOT_JSON,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import io.stargate.sgv2.jsonapi.service.operation.embeddings.EmbeddingTaskGroupBuilder;
import io.stargate.sgv2.jsonapi.service.operation.reranking.*;
import io.stargate.sgv2.jsonapi.service.operation.tasks.*;
import io.stargate.sgv2.jsonapi.service.provider.ApiModelSupport;
import io.stargate.sgv2.jsonapi.service.reranking.operation.RerankingProvider;
import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionRerankDef;
import io.stargate.sgv2.jsonapi.service.schema.collections.CollectionSchemaObject;
import io.stargate.sgv2.jsonapi.service.shredding.Deferrable;
import io.stargate.sgv2.jsonapi.service.shredding.DeferredAction;
Expand Down Expand Up @@ -57,6 +57,9 @@ class FindAndRerankOperationBuilder {
private FindAndRerankCommand command;
private FindCommandResolver findCommandResolver;

// lazily computed effective rerank service def (command override or collection default)
private CollectionRerankDef.RerankServiceDef effectiveRerankServiceDef;

public FindAndRerankOperationBuilder(CommandContext<CollectionSchemaObject> commandContext) {
this.commandContext = Objects.requireNonNull(commandContext, "commandContext cannot be null");

Expand All @@ -78,8 +81,9 @@ public Operation<CollectionSchemaObject> build() {

Objects.requireNonNull(command, "command cannot be null");

checkSupported();
checkSortSupported();
validateHybridLimits();
this.effectiveRerankServiceDef = resolveRerankServiceDef();

// Step 1 - we need a reranking task and the deferrable actions to do the intermediate reads
// Making the deferrables here so we can associate them with read types that will fill them
Expand Down Expand Up @@ -161,10 +165,10 @@ private void checkLimitInBounds(String field, int value, IntConfigWithBounds bou
}

/**
* Check the collection supports hybrid search with the features the request uses, throw if it
* does not
* Check that collection supports the sort features the request uses (vector / vectorize /
* lexical), throw if it does not.
*/
private void checkSupported() {
private void checkSortSupported() {

if (isVectorSort() || isVectorizeSort()) {
if (!commandContext.schemaObject().vectorConfig().vectorEnabled()) {
Expand Down Expand Up @@ -195,51 +199,62 @@ private void checkSupported() {
errVars(commandContext.schemaObject()));
}
}
}

/**
* Resolve the effective {@link CollectionRerankDef.RerankServiceDef} for this command: a command
* override replaces the collection-level config entirely; otherwise fall back to the collection's
* configured defaults. Any non-null {@code rerank} payload is treated as an override attempt and
* validated — {@code "rerank": {}} or a payload whose fields are all explicit nulls fails with
* {@link RequestException.Code#INVALID_RERANK_OVERRIDE} rather than silently falling back. Throws
* {@link RequestException.Code#UNSUPPORTED_RERANKING_COMMAND} when no override is supplied and
* the collection itself has reranking disabled.
*/
private CollectionRerankDef.RerankServiceDef resolveRerankServiceDef() {
var rerankOverride =
getOrDefault(command.options(), FindAndRerankCommand.Options::rerankServiceOverride, null);
boolean hasOverride = rerankOverride != null;

if (!commandContext.schemaObject().rerankingConfig().enabled()) {
// TODO: more info in the error
if (!commandContext.schemaObject().rerankingConfig().enabled() && !hasOverride) {
throw RequestException.Code.UNSUPPORTED_RERANKING_COMMAND.get();
Comment thread
tatu-at-datastax marked this conversation as resolved.
}
// Read is not supported for rerank model at END_OF_LIFE support status.

var rerankingProvidersConfig = commandContext.rerankingProviderFactory().getRerankingConfig();
var modelConfig =
rerankingProvidersConfig.filterByRerankServiceDef(
commandContext.schemaObject().rerankingConfig().rerankServiceDef());
// Validate if the model is END_OF_LIFE
if (modelConfig.apiModelSupport().status() == ApiModelSupport.SupportStatus.END_OF_LIFE) {
throw SchemaException.Code.END_OF_LIFE_AI_MODEL.get(
Map.of(
"model",
modelConfig.name(),
"modelStatus",
modelConfig.apiModelSupport().status().name(),
"message",
modelConfig
.apiModelSupport()
.message()
.orElse("The model is no longer supported (reached its end-of-life).")));

if (hasOverride) {
return CollectionRerankDef.validateServiceDesc(
rerankingProvidersConfig,
rerankOverride.provider(),
rerankOverride.modelName(),
rerankOverride.authentication(),
rerankOverride.parameters(),
RequestException.Code.INVALID_RERANK_OVERRIDE);
}
// Collection defaults: check END_OF_LIFE since model may have become EOL after creation.
var serviceDef = commandContext.schemaObject().rerankingConfig().rerankServiceDef();
CollectionRerankDef.checkExistingModelStatus(rerankingProvidersConfig, serviceDef);
return serviceDef;
}

private TaskGroupAndDeferrables<RerankingTask<CollectionSchemaObject>, CollectionSchemaObject>
rerankTasks(List<RerankingTask.DeferredCommandWithSource> deferredCommandResults) {

// Previous code will check reranking is supported
var providerConfig = commandContext.schemaObject().rerankingConfig().rerankServiceDef();
Objects.requireNonNull(
effectiveRerankServiceDef,
"effectiveRerankServiceDef must be resolved before rerankTasks()");
RerankingProvider rerankingProvider =
commandContext
.rerankingProviderFactory()
.create(
commandContext.requestContext().tenant(),
commandContext.requestContext().authToken(),
providerConfig.provider(),
providerConfig.modelName(),
providerConfig.authentication(),
effectiveRerankServiceDef.provider(),
effectiveRerankServiceDef.modelName(),
effectiveRerankServiceDef.authentication(),
commandContext.commandName());

// todo: move to a builder pattern, mosty to make it easier to manage the task position and
// retry
// policy
// todo: move to a builder pattern, mostly to make it easier to manage the task position and
// retry policy
int commandLimit =
getOrDefault(
command.options(),
Expand Down Expand Up @@ -427,15 +442,14 @@ private PathMatchLocator passageLocator() {
var rerankOn = getOrDefault(command.options(), FindAndRerankCommand.Options::rerankOn, null);
var isRerankOn = rerankOn != null && !rerankOn.isBlank();

String finalRerankField = null;
String finalRerankField;

if (isVectorizeSort()) {
// use the vectorize field, unless the user has overridden
finalRerankField = isRerankOn ? rerankOn : VECTOR_EMBEDDING_TEXT_FIELD;
} else if (isRerankOn) {
// user has to provide a field to rererank on
// user has to provide a field to rerank on
finalRerankField = rerankOn;

} else {
throw new IllegalArgumentException("rerankOn() - rerankOn required and not specified");
}
Expand Down
Loading
Loading