From 5de04b01080bb06d8c3aa1bc95a73f50fa89d551 Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Mon, 31 Aug 2026 16:49:02 +0300 Subject: [PATCH] ARM ONLY, NEVER MERGE: retrieval score first, RRF rank second Merge-and-sort discards rank, so where raw scores tie exactly it falls back to lancedb.databases declaration order. Rank and score are independent discriminators, not the same information at different resolution: a single-branch rank-0 candidate and a rank-2 candidate with two both-branch candidates above it can carry the same score. So keep score primary, which is what buys merge-and-sort its recall, and resolve score ties by within-database position instead of by YAML order. That prefers the candidate nothing in its own collection beat, which is signal. Measures whether merge-and-sort's order sensitivity at n=4 (1.49pp against the tie-break's 0.56pp) is reducible without moving to continuous scores. Claude-Session: https://claude.ai/code/session_01WhudUtZm6qqiuv8Y1sbwSc --- haiku_rag_slim/haiku/rag/client/search.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/haiku_rag_slim/haiku/rag/client/search.py b/haiku_rag_slim/haiku/rag/client/search.py index 9c3441a6..3917a7c0 100644 --- a/haiku_rag_slim/haiku/rag/client/search.py +++ b/haiku_rag_slim/haiku/rag/client/search.py @@ -193,10 +193,15 @@ async def _fuse( scored: list[tuple[float, HaikuRAG, Chunk]] = [] for client, candidates in zip(clients, per_source, strict=True): - for rank, (chunk, _) in enumerate(candidates): - scored.append((1.0 / (_RRF_K + rank + 1), client, chunk)) - scored.sort(key=lambda item: item[0], reverse=True) - return [(client, chunk, score) for score, client, chunk in scored[:limit]] + for rank, (chunk, score) in enumerate(candidates): + scored.append((1.0 / (_RRF_K + rank + 1), score, client, chunk)) + # ARM ONLY, NEVER MERGE: retrieval score primary, RRF rank secondary. Keeps + # merge-and-sort's content-driven allocation while resolving score ties by + # within-database position instead of declaration order. Rank and score are + # independent discriminators: a single-branch rank-0 candidate and a rank-2 + # candidate with two both-branch candidates above it can carry the same score. + scored.sort(key=lambda item: (item[1], item[0]), reverse=True) + return [(client, chunk, fused) for fused, _, client, chunk in scored[:limit]] # Reciprocal rank fusion's smoothing constant, the value the literature uses.