Language-agnostic hybrid-retrieval score fusion (TM2C2 + RRF) and Unicode query normalization — pure-Python, store-agnostic.
Project description
rankweave
Language-agnostic hybrid-retrieval score fusion — pure-Python, store-agnostic.
rankweave decides how to combine the scores from a lexical channel
(character-trigram / BM25 / learned-sparse) and a semantic channel
(dense embeddings) into one ranking. It ships two research-grounded
fusion strategies and the query-side Unicode normalization that makes
character-level lexical matching language-agnostic. It has no
dependencies (stdlib only) and no opinion about your store — bring
your own channels; rankweave fuses their scores.
It is extracted, unchanged in behavior, from the Context Search engine of naruon, following the lab's ONE SOURCE MULTI USE convention: standalone product and submodule-importable.
Why
A convex combination of theoretically min-max normalized scores
(TM2C2) beats Reciprocal Rank Fusion in- and out-of-domain, is robust
for alpha ∈ [0.6, 0.8] with no training data, and — unlike rank
fusion — preserves the score distribution (Bruch, Gai & Ingber 2023).
RRF remains available for channels that expose only ranks. See
docs/research/ for the grounding.
Install
pip install rankweave
Quickstart
from rankweave import FusionSettings, fuse_channel_scores, normalize_search_text
# 1) Normalize the query the same way you normalize indexed documents
# (NFC compose; do accent-folding + lowercasing on the store side too).
query = normalize_search_text(" Trần Hưng Đạo 회의 ") # -> "Trần Hưng Đạo 회의"
# 2) Run your own lexical + dense channels, then fuse per candidate.
settings = FusionSettings() # TM2C2, semantic weight alpha = 0.7
score = fuse_channel_scores(
word_similarity_score=0.62, # lexical channel score in [0, 1]
cosine_distance=0.30, # dense channel distance in [0, 2]
channel_ranks={"lexical": 1, "dense": 1},
settings=settings,
) # -> bounded [0, 1] fused score
A channel that did not return a candidate contributes its theoretical
minimum (absent evidence is the infimum, not an imputed value). Pass
FusionSettings(strategy_name="reciprocal_rank_fusion") to fuse by rank
instead — then only channel_ranks matters.
API
| Symbol | Purpose |
|---|---|
FusionSettings |
Immutable strategy + parameters (strategy_name, semantic_weight_alpha, rank_constant_eta). |
fuse_channel_scores(...) |
Fuse one candidate's channel evidence into a single score under the chosen strategy. |
convex_combination_score(...) |
TM2C2 over already-normalized [0, 1] scores. |
reciprocal_rank_fusion_score(ranks, eta=60) |
RRF over 1-based per-channel ranks. |
theoretical_min_max_normalize(score, bounds) |
Scale a score to [0, 1] using a scoring function's theoretical bounds. |
normalize_search_text(text) |
NFC-compose + whitespace-collapse + length-cap a query. |
WORD_SIMILARITY_THEORETICAL_BOUNDS, COSINE_DISTANCE_THEORETICAL_BOUNDS |
(lower, upper) tuples for the common lexical/dense pairing. |
The normalization contract
Character-trigram lexical retrieval is language-agnostic only if query
and documents fold identically. normalize_search_text owns the
query side (NFC). Do accent-folding + lowercasing on the store side,
in one place, and call it from both — e.g. a PostgreSQL IMMUTABLE
wrapper lower(unaccent(normalize(text, NFC))) used in a pg_trgm GiST
expression index. rankweave stays out of your store so the two sides
cannot silently diverge.
Research grounding
- Bruch, Gai & Ingber (2023). An Analysis of Fusion Functions for Hybrid Retrieval. ACM TOIS 42(1). arXiv:2210.11934. — TM2C2 > RRF; theoretical-normalization stability; the fusion desiderata (monotonicity, homogeneity, boundedness, Lipschitz continuity, sample efficiency) this library's defaults satisfy.
- Cormack, Clarke & Büttcher (2009). Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR 2009. — RRF definition, η = 60.
- UAX #15, Unicode Normalization Forms — NFC composition.
PDFs and a citation manifest live in docs/research/.
Development
pip install -e ".[dev]"
python -m pytest -q # 27 tests, no external services
python -m ruff check .
License
Apache-2.0 — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rankweave-0.1.0.tar.gz.
File metadata
- Download URL: rankweave-0.1.0.tar.gz
- Upload date:
- Size: 12.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
841d4cabd4f96bb67575f9500f9af08030efc04c065c0c2de9ac7b45535b20c8
|
|
| MD5 |
d05695c2103b19f043bb1bff28977f9e
|
|
| BLAKE2b-256 |
aa919710d0f2230278441dc9a9834921494f2afb92e12caa4b6f236fd8b25b6b
|
File details
Details for the file rankweave-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rankweave-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bbc9a1da237bfdfc376150ec55a94628e808499778a03f3beb1ab8c238c0bd75
|
|
| MD5 |
da9df2c7e7823933b54f4f2c15c28615
|
|
| BLAKE2b-256 |
dc21949661a1ab5e8034b66f40cd72764a0ce9694b2b48b66af02e9db61ea6d8
|