Skip to content

fix: hybrid retrieval graceful degradation with Promise.allSettled#581

Open
ether-btc wants to merge 1 commit intoCortexReach:masterfrom
ether-btc:fix/hybrid-promise-allSettled-fallback
Open

fix: hybrid retrieval graceful degradation with Promise.allSettled#581
ether-btc wants to merge 1 commit intoCortexReach:masterfrom
ether-btc:fix/hybrid-promise-allSettled-fallback

Conversation

@ether-btc
Copy link
Copy Markdown
Contributor

Bug 3: Hybrid Promise.all no fallback

Reported in community code audit

Problem

In , runs and in parallel. If either throws (e.g., FTS index corruption for BM25), the entire hybrid search fails — vector results are lost with no graceful degradation.

Fix

Replace with :

  • One search fails → the other search's results are returned; failed side gets empty array + logged
  • Both fail → throws with (preserves existing error telemetry)
  • Diagnostics → / correctly show for failed searches

Testing

  • TypeScript compiles cleanly (0 new errors in retriever.ts)
  • Reviewed by Staff Engineer: APPROVED
  • handles mixed + valid array correctly (BM25-only branch: , )

Files changed

  • : method (+32/-7 lines)

Audit reference: memory-lancedb-pro code audit — Bug 3, reported 2026-04-11

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.
Copy link
Copy Markdown
Collaborator

@rwmjhb rwmjhb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix — switching to Promise.allSettled is the right approach here. One blocking issue to address before merge:

Must Fix

F1 — Both-fail guard triggers on legitimate zero-result queries (src/retriever.ts:954)

// Current (incorrect)
if (vectorResults.length === 0 && bm25Results.length === 0) {
  throw attachFailureStage(new Error('both vector and BM25 search failed'), ...)
}

This condition fires when both backends succeed but return no matches — any sparse knowledge base or narrow query now throws "both vector and BM25 search failed" instead of returning []. It also masks single-backend failures: if one rejects and the other legitimately returns [], the wrong branch fires.

Fix: guard on settled status, not array length:

if (vectorResult_.status === 'rejected' && bm25Result_.status === 'rejected') {
  throw attachFailureStage(new Error('both vector and BM25 search failed'), ...)
}

Nice to Have

  • F2 (src/retriever.ts:955): The both-fail path discards original error context — vectorResult_.reason and bm25Result_.reason are console.warn'd but not propagated into the thrown error. Consider AggregateError([vectorResult_.reason, bm25Result_.reason], 'both searches failed') or embedding .message strings directly, so on-call diagnostics show why each backend failed.

  • EF2: None of the three new degradation branches (vector-fail, BM25-fail, both-fail) are covered by tests. The F1 regression in particular can't be caught by the existing test suite. Worth adding a test that injects a rejected backend alongside an empty-result fulfilled backend.

  • EF4: Base is stale — please rebase on current main before merge. src/retriever.ts is a high-risk module with active concurrent changes.


Overall direction is good — this is worth merging once the length-vs-status guard is corrected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants