()
| 51 | } |
| 52 | |
| 53 | async function ensureModelLoaded(): Promise<void> { |
| 54 | if (cachedModel && cachedTokenizer) return; |
| 55 | if (initPromise) return initPromise; |
| 56 | |
| 57 | initPromise = (async () => { |
| 58 | const { AutoTokenizer, AutoModelForSequenceClassification, env } = |
| 59 | await import('@huggingface/transformers'); |
| 60 | |
| 61 | console.error(`[reranker] Loading cross-encoder: ${DEFAULT_RERANKER_MODEL}`); |
| 62 | console.error('[reranker] (First run will download the model - this may take a moment)'); |
| 63 | |
| 64 | try { |
| 65 | cachedTokenizer = await AutoTokenizer.from_pretrained(DEFAULT_RERANKER_MODEL); |
| 66 | cachedModel = await AutoModelForSequenceClassification.from_pretrained( |
| 67 | DEFAULT_RERANKER_MODEL, |
| 68 | { |
| 69 | dtype: 'q8', |
| 70 | // Limit ONNX Runtime to half cores by default — prevents system freeze during indexing. |
| 71 | session_options: { |
| 72 | intraOpNumThreads: Math.max(1, Math.floor(os.cpus().length / 2)), |
| 73 | interOpNumThreads: 1 |
| 74 | } |
| 75 | } |
| 76 | ); |
| 77 | rerankerHealth = 'fallback'; // loaded but not yet triggered |
| 78 | console.error('[reranker] Cross-encoder loaded successfully'); |
| 79 | } catch (err) { |
| 80 | const msg = err instanceof Error ? err.message : String(err); |
| 81 | const isCorrupt = /protobuf|parse|corrupt/i.test(msg); |
| 82 | resetLoadedState(); |
| 83 | |
| 84 | if (isCorrupt) { |
| 85 | // Corrupted cache is recoverable in-session once the bad model files are removed. |
| 86 | console.error(`[reranker] Cache corruption detected: ${msg}`); |
| 87 | console.error('[reranker] Clearing corrupted cache. Next call will re-download.'); |
| 88 | try { |
| 89 | const cacheDir = env.cacheDir ?? null; |
| 90 | if (cacheDir) { |
| 91 | const { rmSync, existsSync } = await import('fs'); |
| 92 | const modelCacheDir = `${cacheDir}/Xenova/ms-marco-MiniLM-L-6-v2`; |
| 93 | if (existsSync(modelCacheDir)) { |
| 94 | rmSync(modelCacheDir, { recursive: true, force: true }); |
| 95 | console.error('[reranker] Corrupted cache cleared. Will re-download on next call.'); |
| 96 | } |
| 97 | } |
| 98 | } catch { |
| 99 | // Cache clear is best-effort |
| 100 | } |
| 101 | rerankerHealth = 'unavailable'; |
| 102 | throw err; |
| 103 | } |
| 104 | |
| 105 | // Transient error (network, timeout, etc.) — allow retry on next call. |
| 106 | rerankerHealth = 'unavailable'; |
| 107 | throw err; |
| 108 | } |
| 109 | })(); |
| 110 |
no test coverage detected