@@ -414,10 +414,9 @@ export async function POST(request: NextRequest) {
414414 }
415415 }
416416
417- // Sort, normalize, and trim results to topK.
418- // When results come from multiple providers, distance scores from different
419- // embedding spaces are not directly comparable — normalize each provider's
420- // scores to 0-1 range before merging.
417+ // Normalize scores globally across all results before ranking.
418+ // Per-provider normalization would inflate a poor single-provider result
419+ // to an artificially high rank when merging across embedding spaces.
421420 const normalizeScores = ( items : SearchResult [ ] ) : SearchResult [ ] => {
422421 if ( items . length === 0 ) return items
423422 // Single result: clamp raw distance to [0,1] to preserve quality signal.
@@ -430,21 +429,9 @@ export async function POST(request: NextRequest) {
430429 return items . map ( ( r ) => ( { ...r , distance : ( r . distance - min ) / range } ) )
431430 }
432431
433- let results : SearchResult [ ]
434- if ( openaiKbIds . length > 0 && ollamaKbIds . length > 0 ) {
435- const openaiResults = normalizeScores (
436- allResults . filter ( ( r ) => openaiKbIds . includes ( r . knowledgeBaseId ) )
437- )
438- const ollamaResults = normalizeScores (
439- allResults . filter ( ( r ) => ollamaKbIds . includes ( r . knowledgeBaseId ) )
440- )
441- results = [ ...openaiResults , ...ollamaResults ]
442- . sort ( ( a , b ) => a . distance - b . distance )
443- . slice ( 0 , validatedData . topK )
444- } else {
445- // Single provider — still sort and trim to topK
446- results = allResults . sort ( ( a , b ) => a . distance - b . distance ) . slice ( 0 , validatedData . topK )
447- }
432+ const results : SearchResult [ ] = normalizeScores ( allResults )
433+ . sort ( ( a , b ) => a . distance - b . distance )
434+ . slice ( 0 , validatedData . topK )
448435
449436 // Calculate cost — only for OpenAI embedding calls
450437 let cost = null
0 commit comments