Skip to content

Benchmark reciprocal rank fusion against the current hybrid fusion #1748

Open

Description

@phernandez

Summary

Hybrid search merges its full-text and vector legs with fused = max(f, v) + 0.3 × min(f, v) (FUSION_FORMULA_VERSION = "max+0.3*min/v1", repository/search_reader.py). Evaluate reciprocal rank fusion (RRF) against it on the retrieval benchmarks, and adopt RRF only if it measures better.

This is deliberately kept out of the #1746 search rework. That rework changes cost, not ranking, so its before-and-after benchmarks must match exactly. Changing fusion at the same time would mix a quality change into a performance change, and a recall shift could not be attributed to either.

Why look at it

  • The current formula mixes two scales. Full-text scores are divided by the query's best full-text score, so the top lexical hit is always 1.0 however weak it is. Vector scores are raw cosine similarity, about 0.5 to 0.95. A weak keyword match can outrank a strong semantic one, which overlaps with Hybrid search and the similar-notes advisory return weak vector matches #1718 (weak vector matches in hybrid).
  • RRF ranks by position only, score = Σ 1 / (k + rank) with k ≈ 60, so the scales stop mattering. It also discards magnitude: a barely related vector hit at rank 1 counts as much as a near-exact one.

Candidates

  1. The current formula (baseline).
  2. RRF with k = 60, plus a small sweep of k.
  3. The current formula with calibrated full-text scores, for example a fixed bm25 / ts_rank normalization in place of the per-query maximum.
  4. Any of the above with the Hybrid search and the similar-notes advisory return weak vector matches #1718 weak-match floor applied.

Measure

  • test-int/test_search_performance_benchmark.py::test_benchmark_search_quality_recall_by_mode (hit@1, recall@5, MRR@10 for hybrid)
  • just test-semantic-report (semantic quality and reranker quality)
  • benchmarks/ LoCoMo suite: recall@5/10, MRR, content_hit (just bench-run-long with BM_LOCAL_PATH)

Run with the reranker off and on, since a reranker reorders the fused prefix and can mask fusion differences.

Acceptance

Adopt a new formula only if it does not regress any mode on any benchmark and improves hybrid hit@1 or MRR. Bump FUSION_FORMULA_VERSION when it changes.

Related

Activity

  1. claude commented on Oct 11, 2026

    @claude
    Contributor

    Claude finished @phernandez's task in 22s —— View job


    Triage

    • Type: Enhancement (benchmark and evaluation of an alternative hybrid-search fusion; label enhancement applied)
    • Component: Search / retrieval (repository layer, repository/search_reader.py) plus the benchmark suites. This isn't a Cloud issue.
    • Priority: Medium. It's a quality improvement with a clear acceptance gate and no breakage.
    • Complexity: Medium. The code change is small. The work is building a comparable harness that can swap fusion strategies and run it across three suites (test_benchmark_search_quality_recall_by_mode, just test-semantic-report, the LoCoMo benchmarks/ suite). Each needs runs with the reranker off and on, and the candidates need a k sweep and the optional Hybrid search and the similar-notes advisory return weak vector matches #1718 floor.

    Duplicates: I found none. The related issues are #1746 (search rework), #1718 (weak vector matches in hybrid) and #950 (reranking, closed). The issue already scopes this separately from #1746 so that ranking changes don't get mixed into the performance benchmarks.

    Notes for whoever picks this up:

    • Candidates 2–4 need a way to select the fusion strategy at runtime, such as an env or config switch, or a benchmark-only hook. That way every candidate runs against the same index without rebuilding it.
    • Dropping the per-query max normalization (candidate 3) changes score scale. Check what consumers of FUSION_FORMULA_VERSION and any score thresholds assume before adopting it.
    • The acceptance rule is "no regression on any mode on any benchmark". Record the baseline numbers first. Decide up front how to treat noise, e.g. fixed seeds and a tolerance for LoCoMo.

    No questions are blocking. I did triage only and made no code changes.

  2. vitoramaral10 commented on Nov 5, 2026

    @vitoramaral10

    A real-world datapoint from a non-English knowledge base, in case it helps the evaluation.

    Setup: basic-memory 1.0.0, SQLite + sqlite-vec, 167 notes (~10k vector chunks) written in Portuguese, embeddings bge-m3 (1024 dims) through an OpenAI-compatible server, reranker off, default semantic_min_similarity (0.55).

    Probe: 6 paraphrased questions whose answer note shares few or no words with the question, plus 4 exact-name lookups. A hit means the expected note is in the top 3.

    mode paraphrases (6) exact names (4)
    text 2 –
    semantic 4 –
    hybrid, current fusion (max+0.3*min/v1) 2 –
    hybrid, RRF k=60 (local patch of fuse()) 5 4

    In the cases where semantic mode found the answer, hybrid with the current formula followed the text-mode ranking instead. This is the effect described above: the top FTS hit is normalized to 1.0 even when it only matched common words, while the vector score stays raw cosine. Portuguese questions carry many function words that unicode61 keeps, so FTS matches are noisy and the normalized top hit wins almost every time.

    The sample is tiny and is not a benchmark. I'm posting it because the effect looks larger on non-English corpora, so a non-English set in the benchmark might be worth including. The patch replaced the fused-score line with Σ 1 / (k + 1 + rank) over both legs; nothing downstream depended on the score scale.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions