Skip to content

fix(retriever): compute document query embeddings off the event loop - #1662

Merged
MODSetter merged 1 commit into
MODSetter:devfrom
Yigtwxx:fix/retriever-blocking-embed
Aug 6, 2026
Merged

fix(retriever): compute document query embeddings off the event loop#1662
MODSetter merged 1 commit into
MODSetter:devfrom
Yigtwxx:fix/retriever-blocking-embed

Conversation

@Yigtwxx

@Yigtwxx Yigtwxx commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

DocumentHybridSearchRetriever calls embedding_model.embed() synchronously inside its coroutines. Every sibling in the codebase offloads that call; this one file does not.

Description

A local sentence-transformers model runs a synchronous forward pass taking tens to hundreds of milliseconds. Calling it directly from a coroutine stalls the event loop — and therefore every other request on that worker — for its duration.

The correct pattern is already established in four places:

Location
app/retriever/chunks_hybrid_search.py:93 await asyncio.to_thread(embedding_model.embed, query_text)
app/retriever/chunks_hybrid_search.py:241 same
app/services/connector_service.py:187 same
app/agents/chat/multi_agent_chat/shared/retrieval/hybrid_search.py:91 same

app/retriever/documents_hybrid_search.py lines 91 and 225 were the two that were missed — the direct sibling of chunks_hybrid_search.py, in the same two methods.

Motivation and Context

Consistency with the pattern the rest of the retrieval layer already follows.

Please read this part before weighing the PR. I checked whether these lines are reachable today, and they are not:

  • vector_search and full_text_search on this class have no callers anywhere in the repo.
  • hybrid_search has exactly one caller, ConnectorService._combined_rrf, which always passes a precomputed query_embedding — so the if query_embedding is None branch does not execute.

So this is not a measured latency improvement, and I would rather say that up front than let the diff imply one. The value is that the file stops disagreeing with its four siblings, and the next caller to use these entry points does not silently reintroduce the stall. If you would rather delete the two unused methods than fix them, that is a reasonable alternative and I am happy to send that instead.

Screenshots

Not applicable — backend only.

API Changes

  • This PR includes API changes

Change Type

  • Bug fix
  • New feature
  • Performance improvement
  • Refactoring
  • Documentation
  • Dependency/Build system
  • Breaking change
  • Other (specify):

Testing Performed

  • Tested locally
  • Manual/QA verification

uv run pytest -m unit: 2845 passed, same 7 pre-existing failures as dev on this machine (git-tree and PAT static tests, unrelated and failing before this branch).

Two tests added. They use a fake embedding model that blocks a real thread on a threading.Event, then assert the coroutine has not completed while it is blocked — so the assertion is that the loop kept running, not that a particular function was called. Stashing only the source change:

                                                    dev    this branch
test_vector_search_embeds_without_blocking...      FAILED     passed
test_hybrid_search_embeds_without_blocking...      FAILED     passed

Both hang until the 5s release timeout and then fail on dev, which is the stall itself being observed.

Checklist

  • Follows project coding standards and conventions
  • Documentation updated as needed
  • Dependencies updated as needed
  • No lint/build errors or new warnings
  • All relevant tests are passing

What does not change

  • Query semantics, ranking, RRF scoring, filters, and returned shapes are identical — only where the embedding is computed moves.
  • chunks_hybrid_search.py and connector_service.py are untouched; they were already correct.
  • No config, schema, dependency, or API change.

High-level PR Summary

This PR fixes DocumentHybridSearchRetriever to compute query embeddings off the event loop using asyncio.to_thread, matching the pattern already established in four sibling files (chunks_hybrid_search.py, connector_service.py, and chat retrieval). The fix prevents potential event loop blocking when embedding models perform synchronous forward passes that can take tens to hundreds of milliseconds. While the affected code paths (vector_search and hybrid_search without precomputed embeddings) are currently unreachable in production, this change ensures consistency across the codebase and prevents future regressions when these methods are used.

⏱️ Estimated Review Time: 5-15 minutes

💡 Review Order Suggestion
Order File Path
1 surfsense_backend/app/retriever/documents_hybrid_search.py
2 surfsense_backend/tests/unit/retriever/test_query_embedding_offloading.py

Need help? Join our Discord

DocumentHybridSearchRetriever called embedding_model.embed() directly from its
coroutines. A local sentence-transformers model runs a synchronous forward pass,
so that call stalls the event loop and every other request sharing the worker
for its duration.

Every sibling already offloads it: ChucksHybridSearchRetriever does in both
vector_search and hybrid_search, and so do ConnectorService._combined_rrf and
the multi-agent chat retrieval path. This retriever was the one that did not.

Scope note: today these two lines are not reachable. vector_search and
full_text_search have no callers, and hybrid_search's only caller always passes
a precomputed query_embedding. So this is a consistency fix that keeps the next
caller from silently reintroducing the stall -- not a measured speedup, and the
PR should not be read as one.
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

@Yigtwxx is attempting to deploy a commit to the Rohan Verma's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 40edae85-77f7-4b10-a740-ec6a21dcbe94

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MODSetter
MODSetter merged commit cc8fa6e into MODSetter:dev Aug 6, 2026
1 of 3 checks passed
@Yigtwxx
Yigtwxx deleted the fix/retriever-blocking-embed branch August 6, 2026 05:58
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