Repository navigation
fix(community): initialize InfoQuest TLS clients off the event loop - #7899
Conversation
InfoQuest's fetch, web search and image search constructed httpx.AsyncClient inline in their coroutines, so the default transport loaded the TLS trust store synchronously on the agent event loop (~125 ms per call in a local probe). RAGFlow, LightRAG, You.com, GroundRoute and Webz already construct their clients with asyncio.to_thread; InfoQuest was the missed sibling. Move the three constructions to asyncio.to_thread and cover all three methods in the blocking-I/O TLS test. Requests, cleanup, timeouts and responses stay on the event loop. Co-Authored-By: Claude Code <[email protected]>
willem-bd
left a comment
There was a problem hiding this comment.
Extends (does not duplicate) the merged #7889 sweep to the remaining InfoQuest sites: all three client constructions (fetch, web_search_raw_results, image_search_raw_results) now build httpx.AsyncClient via asyncio.to_thread, offloading the synchronous TLS trust-store load while keeping requests and async with cleanup on the loop — the same semantics as #7889 and #7738, with no endpoint/header/timeout changes. The regression test is well designed: it asserts TLS context creation happens off the loop thread, send runs on it, exactly one client is used, and it is closed, and assert contexts would also catch a hypothetical lazy-context regression since send is fully replaced. One non-blocking note on the doc line below.
| Read-only knowledge: [contract](docs/KNOWLEDGE.md) covers independent retrieval | ||
| tools, scope intersection and extension-owned providers. | ||
| HTTPX clients for RAGFlow, LightRAG, You.com, GroundRoute and Webz use | ||
| HTTPX clients for RAGFlow, LightRAG, You.com, GroundRoute, Webz and InfoQuest use |
There was a problem hiding this comment.
Nit/suggestion (pre-existing, out of scope for this PR): the enumerated list reads as exhaustive, but at this head several other community providers still construct httpx.AsyncClient inline on the event loop — crawl4ai/crawl4ai_client.py:35, jsonify/tools.py:116, searxng/searxng_client.py:144, browserless/browserless_client.py:145 and :235, jina_ai/jina_client.py:129 and :192. Since this PR is itself a straggler recovered from the #7738/#7889 sweeps, a follow-up sweep (or a qualifying phrase like "among others") would stop the list from implying completion. No change required here.
The list is not exhaustive; several community clients still construct their client inline. Use non-exhaustive wording until the follow-up sweep lands. Co-Authored-By: Claude Code <[email protected]>
|
Good point — qualified the list in |
|
CI note: the |
|
Fixed a docs slip from the last |
Why
InfoQuest's
fetch,web_search_raw_resultsandimage_search_raw_resultsconstructedhttpx.AsyncClientinline in their coroutines. httpx builds the default transport inAsyncClient.__init__, and that synchronously loads the TLS trust store (~125 ms per call in a local probe) on the agent event loop. Requests were already asynchronous; only setup was blocking.RAGFlow and LightRAG (#7738) and You.com, GroundRoute and Webz (#7889) already construct their clients with
asyncio.to_thread. The #7738 review explicitly namedinfoquest/infoquest_client.py:82among the remaining clients and suggested a follow-up; #7889 was limited to its three adapters. InfoQuest migrated tohttpx.AsyncClientin #5782, before both sweeps.What changed
async with await asyncio.to_thread(httpx.AsyncClient, follow_redirects=True) as client:, matching the merged siblings. Endpoints, headers, payloads, timeouts, redirects, error paths and response handling are unchanged; requests andasync withcleanup stay on the event loop.backend/tests/blocking_io/test_search_client_ssl.pygains a parametrized regression for all three methods: it asserts TLS context creation runs off the loop thread,sendruns on it, and the client is closed exactly once.backend/AGENTS.md:139andREADME.md:253now list InfoQuest among the providers that initialize HTTPX clients off-loop.Surface area
Backend harness runtime (community search provider); no API shape, response, or valid-call behavior change. It adds one executor dispatch per request, the same trade-off the merged siblings carry.
Verification
From
backend/:pytest tests/blocking_io/test_search_client_ssl.py -q→ 8 passed.a46d9221with the new test → 3 failed, 5 passed; each fails withAssertionError: TLS trust loading blocks the event loop(forfetch, the provider catches the exception and returnsError: fetch API failed: TLS trust loading blocks the event loop...).pytest tests/blocking_io/test_search_client_ssl.py tests/test_infoquest_client.py tests/test_infoquest_http_timeout.py -q→ 75 passed.ssl.create_default_contexton a worker thread (on_loop=False);sendand transport__aexit__on the loop (on_loop=True).ruff check/ruff format --checkclean;scripts/check_agent_guidance.py --base-ref a46d9221 --head-ref 0116d6cdexits 0 (only pre-existing soft-chain warnings).AI assistance
Tool(s) used: Claude Code
How you used it: AI reproduced the event-loop TLS load, wrote the regression test (red on parent) and the
to_threadchange, and ran the verification above. An independent review reran the red/green and thread probe in a separate worktree.🤖 Generated with Claude Code