Drawer 03 · Ranking model

How the order is decided

Three stages produce the number a reader sees. Reciprocal Rank Fusion merges two ranked lists into one, normalised to a maximum of 1. A reranker replaces that score with a 30 / 70 blend of the fused position and a fresh query–document judgement. A separate four-term function converts the surviving list into a single confidence figure for chat answers.

03.1Constants of the model

Register D — every ranking constant, with its declaration site
SymbolValueDeclared atEffect
DEFAULT_RRF_K60hybrid_search.py:49Rank damping. Larger flattens the difference between positions.
keyword_weight0.4main.py:196Multiplier on each Zoekt contribution.
semantic_weight0.6main.py:197Multiplier on each FAISS contribution.
use_rerankingTruemain.py:198Reranking is on for every query that survives fusion with ≥2 rows.
use_query_expansionTruemain.py:199Keyword leg runs against up to three query variants.
ORIGINAL_WEIGHT0.3reranker.py:86, 274Share of the fused score retained after reranking.
RERANK_WEIGHT0.7reranker.py:87, 275Share taken from the cross-encoder or Cohere judgement.
DEFAULT_TOP_K50 / 100reranker.py:83, 273Depth reranked: 50 locally, 100 through Cohere.
tail penalty× 0.5reranker.py:237, 461Applied to rows below the rerank depth so they sort under reranked rows.
HNSW_THRESHOLD50 000vector_store_faiss.py:54Declared; no code path constructs an HNSW index.

03.2The score, worked through

The three-stage ranking model worked through with four candidate chunks Three stacked panels. The first states the Reciprocal Rank Fusion formula and tabulates four candidate chunks with their keyword rank, semantic rank, raw fused contribution and score after normalisation by the maximum. The second states the rerank blend and tabulates the raw cross-encoder logits, their min-max normalisation across the batch, and the resulting blended score, showing that the final order differs from the fused order. The third panel draws the confidence score as two stacked bars: the four term budgets of forty, thirty, twenty and ten per cent, and beneath it the amount each term actually contributed for this result set, summing to zero point seven two. ranking model — hybrid_search.py:183 · reranker.py:133 · confidence.py:20 stage 1 — Reciprocal Rank Fusion rrf(d) = Σ w_list / (k + rank_list(d)) k = 60 w_keyword = 0.4 w_semantic = 0.6 score(d) = rrf(d) / max rrf Membership is keyed on f"{file}:{line}". A chunk found by both legs accumulates both contributions; a chunk found by one leg gets one. chunk keyword rank semantic rank raw rrf arithmetic normalised X — reranker.py:211 1 3 0.0160812 0.4/61 + 0.6/63 1.000 W — hybrid_search.py:243 3 2 0.0160266 0.4/63 + 0.6/62 0.997 Y — confidence.py:39 1 0.0098361 0.6/61 0.612 Z — zoekt.py:236 2 0.0064516 0.4/62 0.401 Fused order: X · W · Y · Z. Because normalisation divides by the batch maximum, the top row is always exactly 1.000. stage 2 — cross-encoder rerank (local path) final = 0.3 · fused + 0.7 · norm(cross_encoder(query, chunk)) norm(s) = (s − min_batch) / (max_batch − min_batch), or 0.5 if all equal Cohere's path skips the min-max step and uses relevance_score as returned; its scale is therefore comparable across queries, the local one is not. chunk fused raw logit min-max 0.3·fused + 0.7·norm final Y 0.612 +5.80 1.000 0.184 + 0.700 0.883 X 1.000 +2.10 0.486 0.300 + 0.340 0.640 W 0.997 −1.40 0.000 0.299 + 0.000 0.299 Z 0.401 +0.35 0.243 0.120 + 0.170 0.290 stage 3 — confidence for chat answers, confidence.py:20 budget — four terms, fixed shares top result 0.40 mean of top 3 · 0.30 count 0.20 files 0.10 1.00 achieved for the four chunks above (3 distinct files) 0.400 0.179 0.080 0.060 unclaimed 0.72 · term 1 reads rerank_score, which the min-max step forces to 1.000 for the strongest chunk — so on the local path this term contributes its full 0.40 whenever the best-reranked chunk is also the highest-blended one. · term 2 averages the first three rows. W's rerank_score of exactly 0.0 is falsy, so getattr(...) or getattr(...) silently substitutes its blended score of 0.299 — the lowest-ranked chunk in a batch is scored on a different scale from its peers. · terms 3 and 4 measure quantity and spread, not relevance: ten results across five files add 0.30 regardless of content.
Figure 03.1 — The ranking model end to end Logit values are illustrative; every coefficient, divisor and threshold is taken from the source. Note the reordering between stages: the chunk that won fusion (X) finishes second, and the chunk that only the semantic leg found (Y) finishes first.

03.3Reranker selection

get_reranker(prefer_cloud=True) at reranker.py:467 resolves at startup and never changes. The order of preference is Cohere if the package is importable and COHERE_API_KEY is set, else the local cross-encoder, else Cohere without a key (which degrades to pass-through), else a cross-encoder that will fail to load.

Register E — reranker implementations compared
Property CrossEncoderReranker CohereReranker
Default modelBAAI/bge-reranker-v2-m3rerank-v3.5
Fallbackcross-encoder/ms-marco-MiniLM-L-6-v2none — returns original order
Device"cpu", hard default at reranker.py:92remote
Rerank depth50100
Score scalemin–max within the batchprovider's relevance score
Retriesnone3, exponential back-off on rate limits
Failure modelogs and returns fused orderlogs and returns fused order
Loaded lazilyyes — first rerank() callyes — first client use
A constructed-then-discarded object

main.py:184 resolves a reranker through the factory and passes it into HybridSearchService. But the service constructor also carries a default, self.reranker = reranker or CrossEncoderReranker() (hybrid_search.py:81). Because the factory always returns an object, the default never fires — the two are consistent. The same is not true of device: nothing ever hands the detected GPU device to CrossEncoderReranker, so on a CUDA host the embedding model runs on the GPU and the reranker runs on the CPU.

03.4Query expansion

Expansion widens the keyword leg only. QueryExpander.expand() holds a 25-entry synonym table, builds a reverse index of it at construction, and substitutes at most two synonyms per matched word by plain string replacement. It then adds a case variant if the query contains an underscore or an interior capital. The result is capped at five variants, of which hybrid_search.py:133 uses the first three.

Register F — a sample of the synonym table, reranker.py:511
Head wordSubstitutions offeredFirst two used
functionfunc · method · def · fn · procedurefunc, method
classstruct · type · interface · objectstruct, type
errorexception · err · failure · bugexception, err
authauthentication · authorization · login · credentialsauthentication, authorization
apiendpoint · route · handler · controllerendpoint, route
databasedb · storage · repository · datastoredb, storage
getfetch · retrieve · find · load · readfetch, retrieve
asyncawait · promise · future · concurrentawait, promise
compute_service/services/reranker.py : 558-576QueryExpander.expand
queries = [query]
words = query.lower().split()

for word in words:
    if word in self.SYNONYMS:
        for syn in self.SYNONYMS[word][:2]:
            expanded = query.replace(word, syn)   # case-sensitive replace
            if expanded not in queries:
                queries.append(expanded)
Two quirks. Matching is done on the lower-cased word but substitution runs str.replace against the original query, so "Get user" matches nothing on the head word get. And replacement is substring-wide, so expanding get in "widget config" also rewrites the inside of widget. The reverse index built at reranker.py:542 is a set, so the two synonyms taken from it are chosen in unspecified order.

03.5What the reader is shown

score
The blended value after reranking — or the normalised fused value if reranking was skipped or failed. The web client renders it as Math.round(result.score * 100) followed by a percent sign (web/src/components/search/search-result-card.tsx), which reads as a probability but is a relative position within one result set.
match_type
Set from the request's search_type, not from which leg actually produced the row (cross_repo_search.py:101-111). A hybrid query labels every row hybrid, including rows only the semantic leg returned.
keyword_score / semantic_score
Carried on HybridResult and on CrossRepoResult, but dropped by models/schemas.py:25 — the HTTP response has no field for them, so the leg breakdown never reaches a client.
confidence
Present on ChatResponse only. Rounded to two decimals and computed from the chunks, not from the generated answer.
search_metadata
The full RoutingDecision, including the prose reasoning string with the three raw pattern scores, e.g. "(Scores: symbol=0.00, concept=0.90, architecture=0.00)".