Skip to content

[Cosmos] Use deterministic ordinal ranks for hybrid-search RRF - #50940

Open
Maya Painter (Maya-Painter) wants to merge 6 commits into
mainfrom
maya-painter-java-rrf-implementation
Open

Maya Painter (Maya-Painter) wants to merge 6 commits into
mainfrom
maya-painter-java-rrf-implementation

Conversation

@Maya-Painter

@Maya-Painter Maya Painter (Maya-Painter) commented Oct 22, 2026 •

Copy link
Copy Markdown

Description

Ports the ranking behavior from merged Azure/azure-cosmos-dotnet-v3#6130 to the Java Cosmos hybrid-search pipeline.

Component scores 10, 5, 5, 5, 0 now receive ordinal ranks 1, 2, 3, 4, 5 by default. Setting AZURE_COSMOS_HYBRID_SEARCH_USE_COMPETITION_RANKING=true selects competition ranks 1, 2, 2, 2, 5 instead, not dense ranks. The internal setting is captured once per query.

Component-score ties use the unsigned numeric document RID in the query-plan sort direction, independent of normalized weights. Final results sort by fused score descending, then numeric document RID descending.

Weights and weight / (60 + rank) remain unchanged.

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

Port ordinal and opt-in competition ranking with cached numeric document RID tie-breaking and offline regression coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 29c3522f-9887-43c5-bebb-f42ee9ba4d51
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
34 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Derive strict fusion expectations from standalone component queries and numeric document RIDs. Allow only zero-score title ties to vary in non-RRF queries, and use cleanup-compliant test database creation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 29c3522f-9887-43c5-bebb-f42ee9ba4d51
Keep the full-text reference setup and numeric RID tie-break expression on single lines as requested.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 29c3522f-9887-43c5-bebb-f42ee9ba4d51
Restore private ranking helpers and replace implementation-coupled unit tests with public-query integration coverage for ordinal and competition ranks, numeric RID ties, weights, and paging.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 29c3522f-9887-43c5-bebb-f42ee9ba4d51
Move ordinal and competition ranking coverage into HybridSearchQueryTest, reuse its database lifecycle, and remove the obsolete emulator ignore gate.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 29c3522f-9887-43c5-bebb-f42ee9ba4d51
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

@Ignore("TODO: Ignore these test cases until the public emulator is released.")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Confirming we can remove this ignore?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You're good but these tests aren't still running against emulator but against a real account.

Preserve both the ordinal hybrid-search release note and main's effective partition-key sub-range fix.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 29c3522f-9887-43c5-bebb-f42ee9ba4d51
@Maya-Painter
Maya Painter (Maya-Painter) marked this pull request as ready for review October 27, 2026 17:18
Copilot AI balanced review requested due to automatic review settings October 27, 2026 17:18

Copilot AI 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.

🟡 Changes recommended

The score-matrix sizing change lacks regression coverage for nonempty results with an empty first component-score array or all-empty arrays.

1 open issue
What changed in this PR

Updates Cosmos hybrid-search reciprocal-rank fusion to use deterministic ordinal ranks by default, with an optional competition-ranking mode.

Changes:

  • Uses unsigned numeric document RIDs to resolve component-score and fused-score ties.
  • Captures the ranking setting per query and sizes score matrices from the query plan.
  • Enables and expands integration tests, updates resource hygiene, and documents the behavior change.
File Description
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​query/​HybridSearchDocumentQueryExecutionContext.java Implements ranking modes, deterministic tie-breaking, and plan-based score dimensions.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​query/​hybridsearch/​HybridSearchQueryResult.java Caches the decoded numeric document RID.
sdk/​cosmos/​azure-cosmos/​src/​main/​java/​com/​azure/​cosmos/​implementation/​Configs.java Adds the competition-ranking configuration setting.
sdk/​cosmos/​azure-cosmos/​CHANGELOG.md Documents ranking modes and tie-breaking behavior.
sdk/​cosmos/​azure-cosmos-tests/​src/​test/​resources/​test-resource-hygiene-baseline.properties Removes the hybrid-search test’s resource-hygiene exception.
sdk/​cosmos/​azure-cosmos-tests/​src/​test/​java/​com/​azure/​cosmos/​rx/​HybridSearchQueryTest.java Enables integration tests and adds ranking, paging, and payload assertions.

Review effort: Balanced

#### Breaking Changes

#### Bugs Fixed
* Hybrid search now uses deterministic ordinal reciprocal-rank fusion: component scores `10, 5, 5, 5, 0` receive ranks `1, 2, 3, 4, 5`. Set `AZURE_COSMOS_HYBRID_SEARCH_USE_COMPETITION_RANKING=true` to use competition ranks `1, 2, 2, 2, 5` instead. Component ties use numeric document RID in the query-plan sort direction; fused-score ties use numeric document RID descending.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't this a breakign change? One with an Opt-out to move back to previous behavior - but still abreaking change? Should this be an opt-in instead in the current major version?

#### Breaking Changes

#### Bugs Fixed
* Hybrid search now uses deterministic ordinal reciprocal-rank fusion: component scores `10, 5, 5, 5, 0` receive ranks `1, 2, 3, 4, 5`. Set `AZURE_COSMOS_HYBRID_SEARCH_USE_COMPETITION_RANKING=true` to use competition ranks `1, 2, 2, 2, 5` instead. Component ties use numeric document RID in the query-plan sort direction; fused-score ties use numeric document RID descending.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We usually don't recommend using any VM / env variable options in the changelog. The idea behind adding these VM options / env variables is to allow a backdoor entry / exit in case something goes wrong. I wouldn't advertise it unless there is any issue.

"COSMOS.HYBRID_SEARCH_USE_COMPETITION_RANKING";
private static final String HYBRID_SEARCH_USE_COMPETITION_RANKING_VARIABLE =
"AZURE_COSMOS_HYBRID_SEARCH_USE_COMPETITION_RANKING";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would be good to also have a default variable with value of false for consistency sake like others in this file. Allows a bit of better debuggability.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pleas emake this non-breaking (new option as an opt-in - no opt-out to revert back to current behavior)

// Competition ties share the first position; the next distinct score skips tied positions.
if (!useCompetitionRanking || (index > 0
&& componentScores.get(componentIndex).get(index).getScore().doubleValue()
!= componentScores.get(componentIndex).get(index - 1).getScore().doubleValue())) {

@jeet1995 Abhijeet Mohanty (jeet1995) Oct 30, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we use Double.compare(currentScore, previousScore) != 0 here so competition-rank tie detection uses the same score-equality semantics as ComponentWeight's comparator?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants