(
queryVariants: QueryVariant[],
candidateLimit: number,
filters: SearchFilters | undefined,
useSemanticSearch: boolean,
useKeywordSearch: boolean,
semanticWeight: number,
keywordWeight: number
)
| 832 | } |
| 833 | |
| 834 | private async collectHybridMatches( |
| 835 | queryVariants: QueryVariant[], |
| 836 | candidateLimit: number, |
| 837 | filters: SearchFilters | undefined, |
| 838 | useSemanticSearch: boolean, |
| 839 | useKeywordSearch: boolean, |
| 840 | semanticWeight: number, |
| 841 | keywordWeight: number |
| 842 | ): Promise<{ |
| 843 | semantic: Map<string, { chunk: CodeChunk; ranks: Array<{ rank: number; weight: number }> }>; |
| 844 | keyword: Map<string, { chunk: CodeChunk; ranks: Array<{ rank: number; weight: number }> }>; |
| 845 | }> { |
| 846 | const semanticRanks: Map< |
| 847 | string, |
| 848 | { chunk: CodeChunk; ranks: Array<{ rank: number; weight: number }> } |
| 849 | > = new Map(); |
| 850 | const keywordRanks: Map< |
| 851 | string, |
| 852 | { chunk: CodeChunk; ranks: Array<{ rank: number; weight: number }> } |
| 853 | > = new Map(); |
| 854 | |
| 855 | // RRF uses ranks instead of scores for fusion robustness |
| 856 | if (useSemanticSearch && this.embeddingProvider && this.storageProvider) { |
| 857 | try { |
| 858 | for (const variant of queryVariants) { |
| 859 | const vectorResults = await this.semanticSearch(variant.query, candidateLimit, filters); |
| 860 | |
| 861 | // Assign ranks based on retrieval order (0-indexed) |
| 862 | vectorResults.forEach((result, index) => { |
| 863 | const id = result.chunk.id; |
| 864 | const rank = index; // 0-indexed rank |
| 865 | const weight = semanticWeight * variant.weight; |
| 866 | const existing = semanticRanks.get(id); |
| 867 | |
| 868 | if (existing) { |
| 869 | existing.ranks.push({ rank, weight }); |
| 870 | } else { |
| 871 | semanticRanks.set(id, { |
| 872 | chunk: result.chunk, |
| 873 | ranks: [{ rank, weight }] |
| 874 | }); |
| 875 | } |
| 876 | }); |
| 877 | } |
| 878 | } catch (error) { |
| 879 | if (error instanceof IndexCorruptedError) { |
| 880 | throw error; // Propagate to handler for auto-heal |
| 881 | } |
| 882 | console.warn('Semantic search failed:', error); |
| 883 | } |
| 884 | } |
| 885 | |
| 886 | if (useKeywordSearch && this.fuseIndex) { |
| 887 | try { |
| 888 | for (const variant of queryVariants) { |
| 889 | const keywordResults = await this.keywordSearch(variant.query, candidateLimit, filters); |
| 890 | |
| 891 | // Assign ranks based on retrieval order (0-indexed) |
no test coverage detected