(
query: string,
candidates: RerankCandidate[],
opts: RerankOptions = {},
)
| 56 | * (caller should then fall back to the bi-encoder order). |
| 57 | */ |
| 58 | export async function crossEncoderRerank( |
| 59 | query: string, |
| 60 | candidates: RerankCandidate[], |
| 61 | opts: RerankOptions = {}, |
| 62 | ): Promise<RerankResult[] | null> { |
| 63 | if (candidates.length === 0) return []; |
| 64 | const baseUrl = opts.baseUrl ?? process.env.QODEX_RERANK_URL ?? process.env.OLLAMA_BASE_URL ?? 'http://localhost:11434'; |
| 65 | const model = opts.model ?? process.env.QODEX_RERANK_MODEL ?? 'bge-reranker-v2-m3'; |
| 66 | const maxChars = opts.maxCharsPerDoc ?? 2000; |
| 67 | |
| 68 | const documents = candidates.map(c => |
| 69 | c.text.length > maxChars ? c.text.slice(0, maxChars) : c.text, |
| 70 | ); |
| 71 | |
| 72 | // Try the two common local reranker API shapes in turn. Both take |
| 73 | // {query, documents[]} and return per-document relevance scores. |
| 74 | const scores = await tryOllamaRerank(baseUrl, model, query, documents, opts.signal) |
| 75 | ?? await tryOpenAIRerank(baseUrl, model, query, documents, opts.signal); |
| 76 | |
| 77 | if (!scores) return null; // nothing reachable — caller falls back |
| 78 | |
| 79 | const results: RerankResult[] = candidates.map((c, i) => ({ |
| 80 | id: c.id, |
| 81 | score: scores[i] ?? 0, |
| 82 | })); |
| 83 | results.sort((a, b) => b.score - a.score); |
| 84 | return typeof opts.topN === 'number' ? results.slice(0, opts.topN) : results; |
| 85 | } |
| 86 | |
| 87 | /** Ollama-style reranker: POST /api/rerank {model, query, documents} → {results:[{index,relevance_score}]}. */ |
| 88 | async function tryOllamaRerank( |
no test coverage detected