(query: string, results: SearchResult[])
| 186 | * Returns the full result array with the top-K portion re-ordered. |
| 187 | */ |
| 188 | export async function rerank(query: string, results: SearchResult[]): Promise<SearchResult[]> { |
| 189 | if (results.length <= 1) return results; |
| 190 | if (!isAmbiguous(results)) return results; |
| 191 | |
| 192 | await ensureModelLoaded(); |
| 193 | rerankerHealth = 'active'; |
| 194 | |
| 195 | const toRerank = results.slice(0, Math.min(RERANK_TOP_K, results.length)); |
| 196 | const rest = results.slice(toRerank.length); |
| 197 | |
| 198 | // Score each result against the query using the cross-encoder |
| 199 | const scored: Array<{ result: SearchResult; crossScore: number }> = []; |
| 200 | |
| 201 | for (const result of toRerank) { |
| 202 | const passage = buildPassage(result); |
| 203 | const crossScore = await scorePair(query, passage); |
| 204 | scored.push({ result, crossScore }); |
| 205 | } |
| 206 | |
| 207 | // Sort by cross-encoder score (descending) |
| 208 | scored.sort((a, b) => b.crossScore - a.crossScore); |
| 209 | |
| 210 | // Rebuild the result array: reranked top-K + unchanged rest |
| 211 | // Sigmoid normalizes raw logits to [0,1] so downstream quality gating works |
| 212 | const sigmoid = (x: number) => 1 / (1 + Math.exp(-x)); |
| 213 | const reranked = scored.map(({ result, crossScore }) => ({ |
| 214 | ...result, |
| 215 | score: sigmoid(crossScore) |
| 216 | })); |
| 217 | |
| 218 | return [...reranked, ...rest]; |
| 219 | } |
no test coverage detected