mirror of
https://github.com/deepset-ai/haystack.git
synced 2026-01-05 03:28:09 +00:00
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:
parent
cef856486b
commit
d4e623d069
@ -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)
|
||||
|
||||
@ -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.
|
||||
@ -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"
|
||||
|
||||
@ -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",
|
||||
[
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user