deepset-ai/haystack · error · ValueError
`async_function` must be a coroutine function defined with `
Error message
`async_function` must be a coroutine function defined with `async def`. Got '{__name__ or repr}'. What it means
Tool.async_function must be a coroutine function created with `async def`. The Tool __post_init__ validator checks it with inspect.iscoroutinefunction and rejects anything else (sync functions, partials, lambdas, non-callables). This ensures Tool.invoke_async can await the function correctly.
Source
Thrown at haystack/tools/tool.py:126
outputs_to_state: dict[str, dict[str, Any]] | None = None
async_function: Callable | None = None
def __post_init__(self) -> None: # noqa: C901, PLR0912
# At least one of function / async_function must be set.
if self.function is None and self.async_function is None:
raise ValueError(f"Tool '{self.name}' requires at least one of `function` or `async_function` to be set.")
# `function` must be a regular (sync) function. Coroutine functions belong on `async_function`.
if self.function is not None and inspect.iscoroutinefunction(self.function):
raise ValueError(
f"`function` must be a synchronous function. "
f"The function '{self.function.__name__}' is a coroutine function. "
f"Pass it as `async_function` instead."
)
# `async_function` must be a coroutine function defined with `async def`.
if self.async_function is not None and not inspect.iscoroutinefunction(self.async_function):
raise ValueError(
f"`async_function` must be a coroutine function defined with `async def`. "
f"Got '{getattr(self.async_function, '__name__', repr(self.async_function))}'."
)
# Check that the parameters define a valid JSON schema
try:
Draft202012Validator.check_schema(self.parameters)
except SchemaError as e:
raise ValueError("The provided parameters do not define a valid JSON schema") from e
# Validate outputs structure if provided
if self.outputs_to_state is not None:
for key, config in self.outputs_to_state.items():
if not isinstance(config, dict):
raise TypeError(f"outputs_to_state configuration for key '{key}' must be a dictionary")
if "source" in config and not isinstance(config["source"], str):
raise ValueError(f"outputs_to_state source for key '{key}' must be a string.")
if "handler" in config and not callable(config["handler"]):View on GitHub (pinned to e318778c9b)
Solutions
- Define the callable with `async def` (e.g. `async def my_tool(...): ...`) and pass it as async_function.
- Verify the object is awaitable: `inspect.iscoroutinefunction(my_fn)` before constructing the Tool.
- If wrapping, use functools.partial only on Python >= 3.11 where iscoroutinefunction detects it, or wrap with `async def wrapper(*a, **kw): return await original(*a, **kw)`.
- If no async variant exists, pass async_function=None and use the sync function only.
Example fix
// before
def fetch(url):
return requests.get(url)
Tool(name="fetch", function=fetch, async_function=fetch)
// after
async def fetch(url):
...
Tool(name="fetch", function=sync_fetch, async_function=fetch) Defensive patterns
Strategy: validation
Validate before calling
import inspect assert inspect.iscoroutinefunction(my_async_fn), "async_function must be defined with 'async def'"
Type guard
def is_coroutine_fn(fn: object) -> bool:
return inspect.iscoroutinefunction(fn) Try / catch
try:
tool = Tool(name="t", function=sync_fn, async_function=maybe_async)
except ValueError as e:
if "async_function" in str(e):
tool = Tool(name="t", function=sync_fn)
else:
raise Prevention
- Always declare tool callables with `async def` when using async_function
- Check with inspect.iscoroutinefunction in unit tests for every tool definition
- Don't pass lambdas or partials as async_function on Python < 3.11
When it happens
Trigger: Constructing Tool(func=<sync>, async_function=<plain def or lambda or non-callable>) e.g. Tool(name="t", function=f, async_function=g) where g was not declared with async def; passing an async-unaware callable object implementing __call__; passing a functools.partial wrapping a coroutine function (older Python where iscoroutinefunction(partial) is False).
Common situations: Migrating a sync tool to async and reusing the same sync callable for both parameters; passing a lambda that returns a coroutine instead of being a coroutine; a typo passing func where async_function is expected; Python <3.8-style wrappers losing coroutine-ness.
Related errors
- `function` must be a synchronous function. '{function.__name
- `async_function` must be a coroutine function defined with `
- The provided parameters do not define a valid JSON schema
- outputs_to_state source for key '{key}' must be a string.
- outputs_to_state: '{name}' maps state key '{state_key}' to u
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/fd226daff87d5693.
Report an issue: GitHub.