From 93cd6153c16a39b6608176d83db8fdd7ea3250ce Mon Sep 17 00:00:00 2001 From: Charon Date: Sat, 11 Apr 2026 17:33:49 +0200 Subject: [PATCH] fix: hybrid retrieval graceful degradation with Promise.allSettled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace Promise.all with Promise.allSettled in hybridRetrieval() so that if either vector or BM25 search throws, the other search's results are still used instead of failing the entire query. Behavior: - One search fails → other search results returned, failed side logged - Both fail → throws with failureStage=hybrid.parallelSearch - Diagnostics correctly reflect 0 for failed search counts Fixes Bug 3 from community code audit. --- src/retriever.ts | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/retriever.ts b/src/retriever.ts index 769c248b..67f4c787 100644 --- a/src/retriever.ts +++ b/src/retriever.ts @@ -916,24 +916,49 @@ export class MemoryRetriever { trace?.startStage("parallel_search", []); failureStage = "hybrid.parallelSearch"; - const [vectorResults, bm25Results] = await Promise.all([ + const settledResults = await Promise.allSettled([ this.runVectorSearch( queryVector, candidatePoolSize, scopeFilter, category, - ).catch((error) => { - throw attachFailureStage(error, "hybrid.vectorSearch"); - }), + ), this.runBM25Search( bm25Query, candidatePoolSize, scopeFilter, category, - ).catch((error) => { - throw attachFailureStage(error, "hybrid.bm25Search"); - }), + ), ]); + + const vectorResult_ = settledResults[0]; + const bm25Result_ = settledResults[1]; + + let vectorResults: RetrievalResult[]; + let bm25Results: RetrievalResult[]; + + if (vectorResult_.status === "rejected") { + const error = attachFailureStage(vectorResult_.reason, "hybrid.vectorSearch"); + console.warn(`[Retriever] vector search failed: ${error.message}`); + vectorResults = []; + } else { + vectorResults = vectorResult_.value; + } + + if (bm25Result_.status === "rejected") { + const error = attachFailureStage(bm25Result_.reason, "hybrid.bm25Search"); + console.warn(`[Retriever] bm25 search failed: ${error.message}`); + bm25Results = []; + } else { + bm25Results = bm25Result_.value; + } + + if (vectorResults.length === 0 && bm25Results.length === 0) { + throw attachFailureStage( + new Error("both vector and BM25 search failed"), + "hybrid.parallelSearch", + ); + } if (diagnostics) { diagnostics.vectorResultCount = vectorResults.length; diagnostics.bm25ResultCount = bm25Results.length;