RFC-52: Add score fusion (revision 14)#160
Conversation
| Each strategy's tuning parameters are exposed in the same way the SDK exposes traditional FTS query parameters (fluent-style methods on the object, or an options block, following the SDK's existing convention): | ||
|
|
||
| * `rankConstant` (`uint32`, RRF only). Sent as `params.score_rank_constant`. The server defaults it to `60`. Not exposed on `rsf()`. | ||
| * `windowSize` (`uint32`). Sent as `params.score_window_size`. How many results per list are considered for fusion. The server defaults it to the request `size` (`limit`). |
There was a problem hiding this comment.
It does? Weird. Seems the whole point is to have a significantly larger window than the final result size, so you know you're fusing a good set of results from both sources.
| * `rankConstant` (`uint32`, RRF only). Sent as `params.score_rank_constant`. The server defaults it to `60`. Not exposed on `rsf()`. | ||
| * `windowSize` (`uint32`). Sent as `params.score_window_size`. How many results per list are considered for fusion. The server defaults it to the request `size` (`limit`). | ||
|
|
||
| `rrf()` exposes both `rankConstant` and `windowSize`; `rsf()` exposes only `windowSize`. As with `numCandidates` (`k`) and `knn_operator`, the SDK names differ from the JSON field names. In SDKs where a single class exposes both `rrf()` and `rsf()` rather than separate types, `rankConstant` cannot be hidden on `rsf()`; if it is set there, the SDK must ignore it and not send it. |
There was a problem hiding this comment.
where a single class exposes both
rrf()andrsf()
I would explicitly ban that here. It already doesn't really work, and won't scale if more scoring options get added later.
|
|
||
| * Cluster does not support fusion. Detectable only through the cluster capability (see FeatureNotAvailable handling). Once that capability exists, the SDK raises `FeatureNotAvailableException` before sending; until then, the request silently degrades to additive scoring with no error. | ||
|
|
||
| * Non-hybrid request (only an FTS query, or only a vector search). The SDK does not check for this; it sends the `score` field regardless, and the request is a silent no-op rather than an error. |
There was a problem hiding this comment.
While not a blocker, personally I would InvalidArgument this. It just helps guide the user to the correct use of a complex FTS API.
| ## Score fusion | ||
| This is a feature being added to Couchbase Server 8.1 in the FTS service. It extends hybrid search, where a traditional FTS query is combined with one or more vector queries. | ||
|
|
||
| As with vector search, all SDK additions related to it should be annotated with the platform equivalent of @Stability.Volatile, as changes may be required following user feedback. |
There was a problem hiding this comment.
N.B. Vector search is no longer volatile, the section above probably needs to be updated as well.
Do we still want search fusion to be volatile?
There was a problem hiding this comment.
Based on the discussion below for the ns_server cap, I think it would make sense to keep search fusion volatile at least until the ns_server cluster cap is added. When we implement the cluster cap check it will technically be a breaking change, so if it's 'volatile' that would be acceptable.
There was a problem hiding this comment.
Yup this makes sense. The cap check landing later is a breaking change (silent fallback becomes an exception), and volatile lets us add it without a deprecation cycle
There was a problem hiding this comment.
Still unsure about Volatile to be honest, mainly because of the "boost" reuse
But otherwise resolved with the new ns_server cluster cap
What does everyone think about this?
|
|
||
| If neither is set, the SDK sends no `score` field and the server keeps its existing additive-boost behaviour, so existing queries are unaffected. | ||
|
|
||
| A unified `scoring(...)` setter was rejected: `disableScoring` is already shipped across the SDKs, so unifying would force a deprecation everywhere. |
There was a problem hiding this comment.
A unified scoring(...) setter sounds great to me (at least in Java and Kotlin and any other language where it makes sense). Is there a link to a discussion somewhere that might change my mind?
There was a problem hiding this comment.
OOI what would you put in there?
There was a problem hiding this comment.
OOI what would you put in there?
@programmatix When I wrote that comment, I was thinking it would follow the usual Scala/Kotlin pattern:
scoring(SearchScoring.none() / SearchScoring.somethingElse())
but I'm backing away from this, since it doesn't really solve the problem of invalid arguments.
For Kotlin, we can make it impossible to specify an invalid arg; the scoring fusion can be an optional parameter of the method that builds the hybrid query. For example:
cluster.search(
indexName = "myIndex",
spec = SearchSpec.mixedMode(
searchQuery = SearchSpec.queryString("foo"),
vectorQuery = SearchSpec.vector("someField", vector),
scoreFusion = SearchScoreFusion.rsf(windowSize = ...) // <-- HERE!
)
)
There was a problem hiding this comment.
Nb I like a unified scoring method, in discussions with @anirudhlakhotia and think this is going to make it in.
Something like SearchOptions.searchOptions().scoring(SearchScoring.reciprocalRank())
There was a problem hiding this comment.
We would need to soft deprecate disableScoring across all SDKs though, but this is clean and lets us extend the method to other non-fusion scoring methods if we have any in the future
|
|
||
| A unified `scoring(...)` setter was rejected: `disableScoring` is already shipped across the SDKs, so unifying would force a deprecation everywhere. | ||
|
|
||
| ### The boost field as a fusion weight |
There was a problem hiding this comment.
Does an SDK developer need to know the info in this section to implement the feature, or is it just background information?
There was a problem hiding this comment.
Pull request overview
Adds an RFC section describing “score fusion” for hybrid (FTS + vector) search, including the wire encoding, SDK API surface (SearchScoreFusion), and option placement on SearchOptions.
Changes:
- Documented score-fusion strategies (RRF/RSF), tuning parameters, and JSON encoding (
score+params). - Specified the SDK API shape (
SearchScoreFusion) andSearchOptions.scoreFusion(...)behavior and validation. - Updated the RFC revision history to include Revision #14.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { | ||
| "//": "FTS query; its top-level boost is its fusion weight.", | ||
| "query": { "match": "wireless headphones", "field": "desc", "boost": 2.0 }, | ||
| "//": "Vector query; its boost is the vector side's fusion weight.", | ||
| "knn": [ { "field": "emb", "vector": [ ... ], "k": 200, "boost": 1.0 } ], | ||
| "//": "Fusion strategy: new values on the existing score field.", | ||
| "score": "rrf", | ||
| "//": "Tuning params; omitted entirely when unset.", | ||
| "params": { "score_rank_constant": 60, "score_window_size": 200 } | ||
| } |
| ### Fusion strategies | ||
| Score fusion supports two strategies: | ||
| * Reciprocal Rank Fusion (`"rrf"`). Merges by rank rather than raw score, and works well with the server defaults. The recommended strategy. | ||
| * Relative Score Fusion (`"rsf"`). Merges by normalised score rather than rank. |
| * April 2nd, 2025 - Revision #13 (by Jared Casey) | ||
| * Added prefilter option to vector search. | ||
|
|
||
| * TBD, 2026 - Revision #14 (by Anirudh Lakhotia) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
rfc/0052-sdk3-full-text-search.md:1191
- The revision history entry uses "TBD" for the date. This makes the document’s change log ambiguous once merged; please replace it with an actual date (or omit the entry until the date is known).
* TBD, 2026 - Revision #14 (by Anirudh Lakhotia)
No description provided.