Bug: several query engines' _aquery() silently delegates to synchronous _query(), blocking the event loop
Bug Description
While working on SQLJoinQueryEngine (#22400), I noticed several other query engines have async def _aquery() implementations that simply call the synchronous _query() — so aquery() is async in name only and every step runs blocking work directly on the event loop:
retry_query_engine.py—RetryQueryEngine._aquery(~line 73) andRetrySourceQueryEngine._aquery(~line 144):return self._query(query_bundle)retry_source_query_engine.py—RetrySourceQueryEngine._aquery(~line 90): same patternflare/base.py—FLAREQueryEngine._aquery(~line 259): same pattern (retrieval + LLM generation all sync)router_query_engine.py—ToolRetrieverRouterQueryEngine._aquery(~line 312): same patterngraph_query_engine.py—GraphQueryEngine._aquery(~line 53): calls sync_query_index(...)cogniswitch_query_engine.py—CogniswitchQueryEngine._aquery(~line 61): calls syncquery_knowledge()
Meanwhile SubQuestionQueryEngine shows the intended pattern (separate _query_subq / _aquery_subq, see sub_question_query_engine.py), and SQLJoinQueryEngine is being fixed via asyncio.to_thread in #22400.
Impact
Same class of problem as the SQLJoinQueryEngine case: applications calling these engines' aquery() from an async context (e.g. FastAPI) get sequential synchronous LLM/retrieval calls on the event loop, serializing concurrent requests and risking timeouts.
Proposed fix
For engines whose internals are fully synchronous, offload with asyncio.to_thread(self._query, query_bundle) (same approach as #22400). Where async primitives exist (retriever aretrieve, LLM achat), prefer a true async path.
Happy to open PRs for these — would maintainers prefer one PR per engine, or a single core PR covering all of them?
Source: run-llama/llama_index