Skip to content

feat(tools): add transparent async tool support (fixes #334)#2038

Open
giulio-leone wants to merge 2 commits intohuggingface:mainfrom
giulio-leone:feat/async-tool-support
Open

feat(tools): add transparent async tool support (fixes #334)#2038
giulio-leone wants to merge 2 commits intohuggingface:mainfrom
giulio-leone:feat/async-tool-support

Conversation

@giulio-leone
Copy link

Problem

smolagents does not support async tools — defining an async forward() method returns a coroutine object instead of the actual result. This is a significant limitation for tools that need async I/O (database queries, API calls, file operations).

Related: #145 (61 reactions), #334 (8 reactions)

Solution

Add transparent async detection and execution in the Tool.__call__() method:

  1. After calling self.forward(*args, **kwargs), check if the result is awaitable via inspect.isawaitable()
  2. If no event loop is running: use asyncio.run() to execute it
  3. If inside a running event loop: use a thread pool to avoid nested event loop issues

The @tool decorator's AST parsing also now handles AsyncFunctionDef nodes, so @tool-decorated async functions work correctly.

This is fully backward compatible — sync tools work exactly as before. Async tools now 'just work' without any configuration.

Changes

  • src/smolagents/tools.py: Added _run_async() helper, async handling in Tool.__call__(), AsyncFunctionDef support in @tool decorator
  • tests/test_async_tools.py: 10 new tests covering subclass tools, decorated tools, event loop contexts, and various return types

Testing

  • 10 new tests for async tool execution all pass
  • All 70 existing non-MCP tool tests continue to pass (3 MCP integration tests were already failing pre-change due to missing python binary)

Fixes #334
Related: #145

When a Tool's forward() method is a coroutine, the result is now
automatically awaited via asyncio.run() (or a thread pool if already
inside a running event loop). This allows users to define async tools
without any extra boilerplate.

The @tool decorator also now correctly parses async function definitions.

Refs: huggingface#334
Copilot AI review requested due to automatic review settings March 2, 2026 06:39
Copy link
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

Adds first-class support for async tool implementations so tools with an async forward() (or @tool-decorated async functions) execute transparently and return concrete results instead of coroutine objects.

Changes:

  • Added _run_async() helper and awaitable handling in Tool.__call__().
  • Updated @tool decorator AST parsing to detect AsyncFunctionDef.
  • Added a dedicated test suite for async tool execution behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/smolagents/tools.py Implements awaitable detection/execution in Tool.__call__() and extends @tool AST parsing to include async function definitions.
tests/test_async_tools.py Adds new tests validating async tools work via subclassing and the @tool decorator in both sync and “inside event loop” call sites.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…neration

- Wrap non-coroutine awaitables before passing to asyncio.run()
- Add docstring explaining thread-blocking design in _run_async()
- Generate 'async def forward' for async functions in @tool decorator
- Add serialization round-trip tests for async tools

Refs: huggingface#2038
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.

Async tool support

2 participants