Skip to content

Python: run sync tools off the event loop#5773

Open
he-yufeng wants to merge 1 commit into
microsoft:mainfrom
he-yufeng:fix/sync-tool-offload
Open

Python: run sync tools off the event loop#5773
he-yufeng wants to merge 1 commit into
microsoft:mainfrom
he-yufeng:fix/sync-tool-offload

Conversation

@he-yufeng
Copy link
Copy Markdown
Contributor

Summary

  • run synchronous Python tools in a worker thread when invoked through the async FunctionTool.invoke path
  • keep async tools on the event loop and still await sync wrappers that return awaitables
  • add a regression test that proves a blocking sync tool no longer prevents another coroutine from running

Fixes #5741.

To verify

  • uv run pytest tests/core/test_tools.py -q
  • uv run ruff check agent_framework tests/core/test_tools.py
  • uv run ruff format --check agent_framework tests/core/test_tools.py
  • uv run pyright
  • uv run mypy --config-file pyproject.toml agent_framework
  • python -m py_compile agent_framework\_tools.py tests\core\test_tools.py

Copilot AI review requested due to automatic review settings May 12, 2026 08:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Python FunctionTool.invoke async execution path so that synchronous tools run in a worker thread (avoiding event-loop stalls), while async tools continue to run on the event loop. It also adds a regression test to ensure a blocking sync tool no longer prevents other coroutines from running (fixing #5741).

Changes:

  • Add an internal async helper to route async tools to the event loop and sync tools to asyncio.to_thread().
  • Update FunctionTool.invoke to use the helper in both the observability-enabled and disabled code paths.
  • Add an asyncio/threading regression test to confirm sync tool invocation does not block the event loop.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
python/packages/core/agent_framework/_tools.py Offloads synchronous tool execution to a worker thread during async invocation via a new helper.
python/packages/core/tests/core/test_tools.py Adds a regression test validating that sync tools no longer block other event-loop tasks.

Comment on lines +540 to +547
async def _invoke_function(self, call_kwargs: Mapping[str, Any]) -> Any:
"""Run sync tools off the event loop during async invocation."""
func = self.func.func if isinstance(self.func, FunctionTool) else self.func
if inspect.iscoroutinefunction(func):
return await self.__call__(**call_kwargs)

res = await asyncio.to_thread(self.__call__, **call_kwargs)
return await res if inspect.isawaitable(res) else res
@moonbox3
Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/core/agent_framework
   _tools.py10188092%219–220, 395, 397, 410, 435–437, 445, 463, 477, 484, 491, 514, 516, 523, 531, 659, 698–700, 702, 708, 759–761, 786, 812, 816, 854–856, 860, 882, 1024–1030, 1066, 1078, 1080, 1085–1088, 1109, 1113, 1117, 1131–1133, 1480, 1566, 1594, 1616, 1620, 1750, 1754, 1800, 1861–1862, 1965, 2018, 2038, 2040, 2096, 2159, 2331–2332, 2352, 2408–2409, 2547–2548, 2615, 2620, 2627
TOTAL33846390688% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
6663 30 💤 0 ❌ 0 🔥 1m 49s ⏱️

@he-yufeng he-yufeng force-pushed the fix/sync-tool-offload branch from 2d983ec to 37330b7 Compare May 21, 2026 05:13
@he-yufeng
Copy link
Copy Markdown
Contributor Author

Rebased this branch onto current upstream/main.

Validation:

python -m pytest python\packages\core\tests\core\test_tools.py -q --basetemp .tmp\pytest-5773
python -m py_compile python\packages\core\agent_framework\_tools.py python\packages\core\tests\core\test_tools.py
git diff --check

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: Blocking synchronous tool execution freezes Responses API polling inside async event loop

4 participants