fix(langgraph): offload sync remote tool coroutines - #39
Merged
spichen merged 1 commit intoAug 17, 2026
Merged
Conversation
Signed-off-by: Mohamed Ali <mohamed.ali@openinnovation.ai>
fashkl
force-pushed
the
fix/spichen-offload-sync-langgraph-tools
branch
from
August 17, 2026 10:57
6ec1a87 to
f596356
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
This mirrors the upstream Oracle contribution in oracle#229 and tracks oracle#228.
LangGraph
RemoteToolconversion exposes a coroutine for sync remote-tool callables, but the coroutine currently calls the synchronous function directly. ForRemoteTool, that function executeshttpx.request(...), so async LangGraph execution can block the event loop while waiting on network I/O.This is worse than leaving
StructuredTool.coroutineunset. LangChain's ownStructuredTool._arunchecksself.coroutinefirst; when it isNone, it falls back to the base async implementation, whose source comment says it is expected to delegate_runto a separate thread. By supplying a fake async wrapper, pyagentspec bypasses that safe default and runs blocking sync I/O on the event-loop thread.This also makes
RemoteToolinconsistent with the existingApiNodeexecutor split in this codebase:ApiNodeExecutor._execute()uses synchttpx.request(...), whileApiNodeExecutor._aexecute()useshttpx.AsyncClient. RemoteTool is the outlier because its async entrypoint is only async-shaped, not async-safe.In services with event-loop based liveness/readiness handling, a slow or stuck remote tool request can therefore freeze unrelated async work long enough to trip health checks and restart the service.
Fix
Offload synchronous structured-tool coroutine execution with
asyncio.to_thread(...)while preserving native async callables unchanged.This is a small general safety net for sync callables that pyagentspec intentionally exposes through a coroutine, including the current
RemoteToolandClientToolconversion paths. A future native asyncRemoteToolimplementation usinghttpx.AsyncClientwould still be compatible with this change and could further reduce thread usage for RemoteTool specifically.Testing
test_remote_tool_coroutine_does_not_block_event_loopSKIP_LLM_TESTS=1 uv run python -m pytest -q tests/adapters/langgraph/test_tools.py -k 'remote_tool_coroutine_does_not_block_event_loop or async_server_tool_callable_converts_to_structured_tool_coroutine'SKIP_LLM_TESTS=1 uv run python -m pytest -q tests/adapters/langgraph/test_tools.pyCommit is signed off per the Oracle contribution guide.