Skip to content

plan findAndRerank before building the operation - #2650

Open
Hazel-Datastax wants to merge 17 commits into
mainfrom
hazel/farr-plan
Open

Hazel-Datastax wants to merge 17 commits into
mainfrom
hazel/farr-plan

Conversation

@Hazel-Datastax

@Hazel-Datastax Hazel-Datastax commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does:
First of the two PRs split out of #2637. The resolver now gets a FindAndRerankPlan from the new FindAndRerankPlanner and passes it to FindAndRerankOperationBuilder.

The plan is only data: an optional vectorize step, the legs to read, and the rerank step.

The checks moved from the builder to the planner and run in the same order, and the builder builds the same tasks from the plan.

The planner no longer uses the metrics flags to decide what to run (#2598), and sorts that failed with UNEXPECTED_SERVER_ERROR now fail with three new SORT errors (#2577).

Which issue(s) this PR fixes:
Fixes #2598
Fixes #2577

Checklist

  • Changes manually tested
  • Automated Tests added/updated
  • Documentation added/updated
  • CLA Signed: DataStax CLA

The sort parser copied the $hybrid text into vectorizeSort and lexicalSort,
so {"$hybrid": "text"} parsed to the same values as a $hybrid object with
both keys. The builder then used the LEXICAL metrics flag to tell an
explicit $lexical from the $hybrid text (#2598).

FindAndRerankSort now records rerankLegMode, MULTI_LEG when the sort uses
$hybrid, and the $hybrid text in hybridSort. The parser builds it with the
constructor, and the constructor only checks for null. vectorizeText() and
lexicalText() give the text each read uses. The builder decides an explicit
$lexical from lexicalSort, not from the metrics flag, so
CommandFeatures.contains() is no longer used and is removed.

normalizedText() already trims, so the parser no longer calls trim() first.

Should not change behaviour. The MCP tool schema for the sort lists the
record fields, so it also shows rerankLegMode and hybridSort.
Three findAndRerank sorts fail with UNEXPECTED_SERVER_ERROR on main, from
the IllegalArgumentException in the builder when there is no vector or
vectorize read (#2577). These SORT errors replace it:

- MISSING_VECTOR_OR_VECTORIZE_IN_HYBRID_SORT: a $hybrid sort with a
  $lexical value but no $vectorize or $vector value.
- MISSING_HYBRID_SORT_VALUE: a $hybrid sort with no value.
- UNSUPPORTED_FIND_AND_RERANK_SORT: no sort, or an empty sort.

Nothing throws them yet.
FindAndRerankPlan holds the steps a findAndRerank command runs: vectorize
the sort text when there is a VectorizeStep, read with each RerankLeg, then
rerank what the legs read with the RerankStep. It is only data, and checks
its invariants when it is built. A RerankLeg keeps the vector it is given,
as FindAndRerankSort does, and compares it by value.

RerankLegType has VECTOR_LEG, LEXICAL_LEG and FILTER_LEG. FILTER_LEG is for
the read with only the filter in #2603, the planner does not make one yet.

FindAndRerankPlanner makes the plan from the command and its context. Its
checks are the builder's checks, copied in the same order and changed only
to take the sort, the command or the context as arguments.
checkSortSupported() no longer also tests isLexicalSort(), an explicit
$lexical always has lexical text. A $hybrid sort always gets a VECTOR_LEG,
and a LEXICAL_LEG when it has lexical text and the collection has a lexical
index. A sort without a vector or vectorize value, or no sort, fails with
the errors from the previous commit.

HybridLimitsDeserializer exposes the field names it uses in its errors, so
the planner does not repeat them. TestConstants gets the schema object and
the reranking providers config that the new tests use.

Nothing calls the planner yet, the next commit does.
FindAndRerankCommandResolver makes the plan with FindAndRerankPlanner and
gives it to the builder with withPlan(). The builder no longer checks the
command, the planner does. It takes from the plan what it used to work out
from the command: the legs and their read limits, the vectorize step, the
rerank service, query, passage, projection and limit, and includeScores and
includeSortVector.

How the builder puts the tasks together does not change: the same read
tasks and deferred results, the embedding task when there is text to
vectorize, and the reranking task.

The three sorts in #2577 now fail with the SORT errors instead of
UNEXPECTED_SERVER_ERROR, after the same checks as before. The reranking
provider is now created after the rerank query, rerankOn and projection
checks, not before them. This only changes which error comes first when
the provider cannot be created.

FindAndRerankOperationBuilderTest makes the plan before it builds, as the
resolver does, and FindAndRerankPlannerTest also builds the operation from
each plan it makes.
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

📈 Unit Test Coverage Delta vs Main Branch

Metric Value
Main Branch 55.07%
This PR 55.36%
Delta 🟢 +0.29%
✅ Coverage improved!

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Unit Test Coverage Report

Overall Project 55.36% -0.01% 🍏
Files changed 99.13% 🍏

File Coverage
RerankLegMode.java 100% 🍏
FindAndRerankOperationBuilder.java 100% 🍏
FindAndRerankPlan.java 100% 🍏
RerankLegType.java 100% 🍏
RerankingQuery.java 100% 🍏
SortException.java 100% 🍏
FindAndRerankPlanner.java 99.53% -0.47% 🍏
FindAndRerankSortClauseDeserializer.java 99.46% 🍏
FindAndRerankSort.java 94.86% 🍏
FindAndRerankCommand.java 86.36% 🍏
CommandFeatures.java 81.36% 🍏
FindAndRerankCommandResolver.java 0% -14.58% ❌

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

📉 Integration Test Coverage Delta vs Main Branch (dse69-it)

Metric Value
Main Branch 73.04%
This PR 72.99%
Delta 🔴 -0.05%
⚠️ Coverage decreased

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Integration Test Coverage Report (dse69-it)

Overall Project 72.99% -0.24% 🍏
Files changed 74.71% 🍏

File Coverage
RerankLegMode.java 100% 🍏
RerankLegType.java 100% 🍏
SortException.java 100% 🍏
FindAndRerankCommandResolver.java 92.16% 🍏
FindAndRerankOperationBuilder.java 86.94% -5.86% 🍏
FindAndRerankPlanner.java 84.98% -15.02% 🍏
FindAndRerankCommand.java 81.82% 🍏
RerankingQuery.java 77.78% 🍏
CommandFeatures.java 71.19% 🍏
FindAndRerankSortClauseDeserializer.java 57.8% 🍏
FindAndRerankPlan.java 55.6% -44.4% ❌
FindAndRerankSort.java 33.14% -28.57% ❌

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

📉 Integration Test Coverage Delta vs Main Branch (hcd-it)

Metric Value
Main Branch 74.35%
This PR 74.31%
Delta 🔴 -0.04%
⚠️ Coverage decreased

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Integration Test Coverage Report (hcd-it)

Overall Project 74.31% -0.18% 🍏
Files changed 80.58% 🍏

File Coverage
RerankLegMode.java 100% 🍏
RerankLegType.java 100% 🍏
SortException.java 100% 🍏
FindAndRerankOperationBuilder.java 96.85% -3.15% 🍏
FindAndRerankPlanner.java 95.07% -4.93% 🍏
FindAndRerankCommandResolver.java 92.16% 🍏
FindAndRerankCommand.java 82.73% 🍏
RerankingQuery.java 82.22% 🍏
CommandFeatures.java 71.19% 🍏
FindAndRerankSortClauseDeserializer.java 57.8% 🍏
FindAndRerankPlan.java 57.76% -42.24% ❌
FindAndRerankSort.java 33.14% -28.57% ❌

Comment on lines +55 to +60
checkSortSupported(sort, schemaObject);
validateHybridLimits(command, operationsConfig);
var rerankServiceDef = resolveRerankServiceDef(command, commandContext);
var rerankingQuery = RerankingQuery.create(command);
var passageLocator = passageLocator(command);
var projector = command.buildProjector();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These checks moved here from FindAndRerankOperationBuilder.build() and run in the same order.

Comment on lines +66 to +70
switch (sort.rerankLegMode()) {
// TODO: GH #2602, GH #2603 - plan single leg sorts, until then only a command with no
// sort is SINGLE_LEG
case SINGLE_LEG -> throw SortException.Code.UNSUPPORTED_FIND_AND_RERANK_SORT.get();
case MULTI_LEG -> multiLegs(command, schemaObject);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, a command with no sort (or {}, null) and a $hybrid sort with no vector or vectorize failed later in buildVectorRead() with UNEXPECTED_SERVER_ERROR (#2577).

Now SINGLE_LEG fails with UNSUPPORTED_FIND_AND_RERANK_SORT, and multiLegs() fails with MISSING_HYBRID_SORT_VALUE or MISSING_VECTOR_OR_VECTORIZE_IN_HYBRID_SORT.

Comment on lines +288 to +290
private static boolean isExplicitLexicalSort(FindAndRerankSort sort) {
return sort.lexicalSort() != null;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This used to read the LEXICAL metrics flag with CommandFeatures.contains() (#2598).

Now the $hybrid text stays in hybridSort, so lexicalSort() is only set by an explicit $lexical. The result is the same as before.

})
public record FindAndRerankSort(
@JsonIgnore RerankLegMode rerankLegMode,
@JsonIgnore String hybridSort,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $hybrid text used to be copied into vectorizeSort and lexicalSort. It now stays here, so the planner can tell $hybrid text from an explicit $lexical.

vectorizeText() and lexicalText() return the text to use for both forms.

* <p>The operation does not expose its tasks, so the tests check the providers the builder creates
* and the hybridLimits it sets on the command context.
*/
class FindAndRerankOperationBuilderTest {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checks that moved to the planner are now tested in FindAndRerankPlannerTest.

This test builds from plans made in the test, so it no longer needs Quarkus. The operation does not expose its read tasks, so the test checks the providers the builder creates and the hybridLimits it sets on the command context.

public FindAndRerankOperationBuilder withPlan(FindAndRerankPlan plan) {
this.plan = plan;
return this;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The builder now gets the plan here. It still takes the command for the filter and for the #1961 page size hack in readTasks().

@Hazel-Datastax
Hazel-Datastax marked this pull request as ready for review October 25, 2026 00:22
@Hazel-Datastax
Hazel-Datastax requested a review from a team as a code owner October 25, 2026 00:22

@erichare erichare left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, Hazel. LGTM, the API regression test suggestion is nonblocking.

switch (sort.rerankLegMode()) {
// TODO: GH #2602, GH #2603 - plan single leg sorts, until then only a command with no
// sort is SINGLE_LEG
case SINGLE_LEG -> throw SortException.Code.UNSUPPORTED_FIND_AND_RERANK_SORT.get();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add API regression tests for #2577, plus the empty hybrid and missing sort cases? The planner tests cover the exceptions, but these new error codes are part of the client response.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Metrics flags decide user intent IllegalArgumentException / UNEXPECTED_SERVER_ERROR in FindAndRerank when omitting $vectorize in $hybrid

2 participants