fix: Raise error when async function is passed to Tool (#10264)

* fix: Raise error when async function is passed to Tool

- Added validation in Tool.__post_init__ to check if function is async
- Added validation in create_tool_from_function for early error detection
- Updated docstring to clarify that functions must be synchronous
- Added tests for both Tool init and @tool decorator with async functions

Closes #9580

* fix: Remove redundant async check from create_tool_from_function

Per reviewer feedback, the async validation is only needed in the
Tool class itself, since create_tool_from_function creates a Tool.

- Remove async check from create_tool_from_function
- Update docstrings to remove async-related notes
- Remove redundant tests for async functions in from_function tests
- Add release note

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add docstring note and tests for async function validation

- Updated Tool class docstring to indicate function must be synchronous
- Added test_from_function_async_raises_error test
- Added test_tool_decorator_async_raises_error test

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
lif 2025-12-19 19:40:38 +08:00 committed by GitHub
parent cef856486b
commit d4e623d069
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 41 additions and 0 deletions

View File

@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: Apache-2.0
import inspect
from dataclasses import asdict, dataclass
from typing import Any, Callable, Optional
@ -34,6 +35,7 @@ class Tool:
A JSON schema defining the parameters expected by the Tool.
:param function:
The function that will be invoked when the Tool is called.
Must be a synchronous function; async functions are not supported.
:param outputs_to_string:
Optional dictionary defining how a tool outputs should be converted into a string.
If the source is provided only the specified output key is sent to the handler.
@ -74,6 +76,14 @@ class Tool:
outputs_to_state: Optional[dict[str, dict[str, Any]]] = None
def __post_init__(self):
# Check that the function is not a coroutine (async function)
if inspect.iscoroutinefunction(self.function):
raise ValueError(
f"Async functions are not supported as tools. "
f"The function '{self.function.__name__}' is a coroutine function. "
f"Please use a synchronous function instead."
)
# Check that the parameters define a valid JSON schema
try:
Draft202012Validator.check_schema(self.parameters)

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Raise a ``ValueError`` when an async function is passed to the ``Tool`` class. Async functions are not supported
as tools. This change provides a clear error message instead of silent failures where coroutines are never awaited.

View File

@ -286,3 +286,21 @@ def test_remove_title_from_schema_handle_no_title_in_top_level():
"properties": {"parameter1": {"type": "string"}, "parameter2": {"type": "integer"}},
"type": "object",
}
def test_from_function_async_raises_error():
async def async_get_weather(city: str) -> str:
"""Get weather report for a city."""
return f"Weather report for {city}: 20°C, sunny"
with pytest.raises(ValueError, match="Async functions are not supported as tools"):
create_tool_from_function(async_get_weather)
def test_tool_decorator_async_raises_error():
with pytest.raises(ValueError, match="Async functions are not supported as tools"):
@tool
async def async_get_weather(city: str) -> str:
"""Get weather report for a city."""
return f"Weather report for {city}: 20°C, sunny"

View File

@ -21,6 +21,10 @@ def format_string(text: str) -> str:
parameters = {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
async def async_get_weather(city: str) -> str:
return f"Weather report for {city}: 20°C, sunny"
class TestTool:
def test_init(self):
tool = Tool(
@ -39,6 +43,10 @@ class TestTool:
with pytest.raises(ValueError):
Tool(name="irrelevant", description="irrelevant", parameters=params, function=get_weather_report)
def test_init_async_function_raises_error(self):
with pytest.raises(ValueError, match="Async functions are not supported as tools"):
Tool(name="weather", description="Get weather report", parameters=parameters, function=async_get_weather)
@pytest.mark.parametrize(
"outputs_to_state",
[