Skip to content
中文

RRF ranks, deduplication, and scores ​

The policy implementation follows.

ts
export function reciprocalRankFusion(
  lists: readonly RankedList[],
  options: { limit: number; k?: number },
): FusedRank[] {
  // 60 is the smoothing constant popularised by the original RRF paper and keeps rank
  // differences meaningful without letting the top hit dominate.
  const k = options.k ?? 60,
    sources = new Set<string>(),
    candidates = new Map<string, FusedRank>();
  breakWhile(
    !Number.isFinite(k) ||
      k < 0 ||
      !Number.isSafeInteger(options.limit) ||
      options.limit < 1 ||
      options.limit > 1000,
    'Invalid RRF options',
  );
  for (const list of lists) {
    const weight = list.weight ?? 1,
      sourceId = list.sourceId;
    breakWhile(
      !sourceId?.trim() ||
        sources.has(sourceId) ||
        !Array.isArray(list.ids) ||
        list.ids.some((id) => typeof id !== 'string' || !id.trim()) ||
        !Number.isFinite(weight) ||
        weight <= 0,
      'Invalid ranked source',
    );
    sources.add(sourceId);
    const seen = new Set<string>();
    for (const id of list.ids) {
      if (seen.has(id)) continue;
      seen.add(id);
      const rank = seen.size;
      const candidate = candidates.get(id) ?? {
        id,
        score: 0,
        contributions: [],
      };
      candidate.score += weight / (k + rank);
      candidate.contributions.push({ sourceId, rank, weight });
      candidates.set(id, candidate);
    }
  }
  return [...candidates.values()]
    .sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
    .slice(0, options.limit);
}

API reference, usage examples and developer guides.