| import type { RRFResult, RerankedResult, FinalResult } from "../types"; |
| import { |
| BLEND_TAIL_RRF_WEIGHT, |
| BLEND_TOP10_RRF_WEIGHT, |
| BLEND_TOP3_RRF_WEIGHT, |
| } from "../constants"; |
|
|
| const LOW_RANK_PROMOTION_OVERRIDE = 0.9; |
| const LOW_RANK_MAX_PROMOTION = 1; |
| const SCORE_EPSILON = 1e-6; |
|
|
| function getRrfWeight(rank: number): number { |
| if (rank <= 3) return BLEND_TOP3_RRF_WEIGHT; |
| if (rank <= 10) return BLEND_TOP10_RRF_WEIGHT; |
| return BLEND_TAIL_RRF_WEIGHT; |
| } |
|
|
| |
| export function blendScores( |
| rrfResults: RRFResult[], |
| rerankScores: Map<string, number>, |
| ): FinalResult[] { |
| const blended: RerankedResult[] = rrfResults.map((result, index) => { |
| const rank = index + 1; |
| const rrfWeight = getRrfWeight(rank); |
| const positionScore = 1 / rank; |
| const rerankScore = rerankScores.get(result.docId) ?? 0; |
| let blendedScore = |
| rrfWeight * positionScore + (1 - rrfWeight) * rerankScore; |
|
|
| |
| |
| if (rank > 3 && rerankScore < LOW_RANK_PROMOTION_OVERRIDE) { |
| const guardedRank = Math.max(1, rank - LOW_RANK_MAX_PROMOTION); |
| const promotionCap = (1 / guardedRank) - SCORE_EPSILON; |
| blendedScore = Math.min(blendedScore, promotionCap); |
| } |
|
|
| return { |
| ...result, |
| rerankScore, |
| blendedScore, |
| }; |
| }); |
|
|
| |
| blended.sort((a, b) => b.blendedScore - a.blendedScore); |
|
|
| |
| const seen = new Set<string>(); |
| const final: FinalResult[] = []; |
| for (const result of blended) { |
| if (seen.has(result.docId)) continue; |
| seen.add(result.docId); |
| final.push({ |
| filepath: result.filepath, |
| title: result.title, |
| bestChunk: result.bestChunk, |
| score: result.blendedScore, |
| docId: result.docId, |
| }); |
| } |
|
|
| return final; |
| } |
|
|