{"record":{"id":"2acd25f8311c1a90","repo":"deepset-ai/haystack","slug":"async-function-must-be-a-coroutine-function-defi","errorCode":null,"errorMessage":"`async_function` must be a coroutine function defined with `async def`. Got '{getattr(async_function, '__name__', repr(async_function))}'.","messagePattern":"`async_function` must be a coroutine function defined with `async def`\\. Got '(.+?)'\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"haystack/hooks/from_function.py","lineNumber":57,"sourceCode":"        \"\"\"\n        Initialize the hook with a synchronous function, an async function, or both.\n\n        :param function: The synchronous function invoked by `run`. Must be a regular function — coroutine functions\n            should be passed to `async_function` instead. Either `function` or `async_function` (or both) must be set.\n        :param async_function: Optional coroutine function awaited by `run_async`. When only `async_function` is set,\n            `run` raises a `RuntimeError`. When only `function` is set, `run_async` calls `function`.\n        :raises ValueError: If neither is set, if `function` is a coroutine function, if `async_function` is not, or\n            if a provided function does not declare a `State`-typed parameter.\n        \"\"\"\n        if function is None and async_function is None:\n            raise ValueError(\"A FunctionHook requires at least one of `function` or `async_function` to be set.\")\n        if function is not None and inspect.iscoroutinefunction(function):\n            raise ValueError(\n                f\"`function` must be a synchronous function. '{function.__name__}' is a coroutine function. \"\n                \"Pass it as `async_function` instead.\"\n            )\n        if async_function is not None and not inspect.iscoroutinefunction(async_function):\n            raise ValueError(\n                f\"`async_function` must be a coroutine function defined with `async def`. \"\n                f\"Got '{getattr(async_function, '__name__', repr(async_function))}'.\"\n            )\n        for func in (function, async_function):\n            if func is not None and not _takes_single_state_argument(func):\n                raise ValueError(\n                    f\"Hook function '{func.__name__}' must take a single parameter annotated with `State` \"\n                    \"(e.g. `def my_hook(state: State) -> None`).\"\n                )\n        self.function = function\n        self.async_function = async_function\n\n    def run(self, state: State) -> None:\n        \"\"\"\n        Run the synchronous function against the live `State`.\n\n        :param state: The Agent's live `State`, mutated in place by the wrapped function.\n        :raises RuntimeError: If the hook only has an `async_function`; use the Agent's async run methods instead.","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/hooks/from_function.py#L39-L75","documentation":"FunctionHook validates that `async_function` is actually a coroutine function declared with `async def`. If you pass a plain synchronous function to `async_function`, it cannot be awaited correctly during async Agent runs, so __init__ raises ValueError naming the offending callable.","triggerScenarios":"Calling FunctionHook(async_function=plain_sync_fn) where inspect.iscoroutinefunction(plain_sync_fn) is False; e.g. swapping parameters or passing the sync hook to the wrong keyword.","commonSituations":"Parameter mix-up after editing a hook declaration; forgetting `async def` when writing a new async hook; passing a bound method or partial that wraps sync code.","solutions":["Declare the async hook with `async def` so it is a coroutine function","If the hook is intentionally synchronous, pass it as `function` instead of `async_function`","Check functools.partial/wrapped callables — pass the actual `async def` function"],"exampleFix":"// before\ndef my_hook(state: State) -> None: ...\nhook = FunctionHook(async_function=my_hook)  # ValueError\n// after\nasync def my_hook(state: State) -> None: ...\nhook = FunctionHook(async_function=my_hook)","handlingStrategy":"validation","validationCode":"import inspect\nif async_function is not None and not inspect.iscoroutinefunction(async_function):\n    raise ValueError(\"async_function must be declared with async def\")","typeGuard":"def is_coroutine_function(fn) -> bool:\n    return callable(fn) and inspect.iscoroutinefunction(fn)","tryCatchPattern":"try:\n    hook = FunctionHook(async_function=afn, function=fn)\nexcept ValueError as e:\n    raise TypeError(f\"bad hook callable: {e}\") from e","preventionTips":["Always declare async hooks with `async def`","Double-check keyword argument names when constructing FunctionHook","Unwrap partials/wrappers so the underlying async def is passed"],"tags":["python","async","validation","hooks"],"backgroundTag":"async-function-passed-to-sync-api","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}