diff --git a/haystack/tools/tool.py b/haystack/tools/tool.py index 2fa314659..478f8a931 100644 --- a/haystack/tools/tool.py +++ b/haystack/tools/tool.py @@ -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) diff --git a/releasenotes/notes/raise-error-async-tool-56dc668744034656.yaml b/releasenotes/notes/raise-error-async-tool-56dc668744034656.yaml new file mode 100644 index 000000000..908db506c --- /dev/null +++ b/releasenotes/notes/raise-error-async-tool-56dc668744034656.yaml @@ -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. diff --git a/test/tools/test_from_function.py b/test/tools/test_from_function.py index 3ad2c94a2..ccedd329c 100644 --- a/test/tools/test_from_function.py +++ b/test/tools/test_from_function.py @@ -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" diff --git a/test/tools/test_tool.py b/test/tools/test_tool.py index bc8c85cb0..0725274fc 100644 --- a/test/tools/test_tool.py +++ b/test/tools/test_tool.py @@ -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", [