{"record":{"id":"9a447f166f973bc7","repo":"deepset-ai/haystack","slug":"function-must-be-a-synchronous-function-funct","errorCode":null,"errorMessage":"`function` must be a synchronous function. '{function.__name__}' is a coroutine function. Pass it as `async_function` instead.","messagePattern":"`function` must be a synchronous function\\. '(.+?)' is a coroutine function\\. Pass it as `async_function` instead\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"haystack/hooks/from_function.py","lineNumber":52,"sourceCode":"    def __init__(\n        self,\n        function: Callable[[State], None] | None = None,\n        async_function: Callable[[State], Awaitable[None]] | None = None,\n    ) -> None:\n        \"\"\"\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:","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/hooks/from_function.py#L34-L70","documentation":"FunctionHook's __init__ validates that the `function` argument is a plain synchronous (def) function. Passing a coroutine function (defined with `async def`) as `function` would break synchronous hook execution, so haystack raises ValueError and directs you to the `async_function` parameter instead.","triggerScenarios":"Calling FunctionHook(function=my_async_def_function) or FunctionHook.from_function with a coroutine function bound to the `function` parameter; inspect.iscoroutinefunction(function) is True.","commonSituations":"Refactoring a hook from sync to async without changing the constructor argument name; copying a hook example and adding `async` to the function; passing an async callback received from elsewhere (e.g. an async LLM handler).","solutions":["Move the coroutine function to the `async_function` parameter: FunctionHook(function=None, async_function=my_async_fn)","If the hook must stay sync-only, remove `async` from the function definition or wrap the async work with asyncio.run inside a sync function","Use a factory/keyword form FunctionHook.from_function(async_function=...) if only async behavior is needed"],"exampleFix":"// before\nasync def my_hook(state: State) -> None: ...\nhook = FunctionHook(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 function is not None and inspect.iscoroutinefunction(function):\n    raise ValueError(\"pass an async def function as async_function instead\")","typeGuard":"def is_sync_function(fn) -> bool:\n    return callable(fn) and not inspect.iscoroutinefunction(fn) and not inspect.iscoroutine(fn)","tryCatchPattern":"try:\n    hook = FunctionHook(function=fn)\nexcept ValueError as e:\n    # fall back to async_function or fix the callable\n    hook = FunctionHook(async_function=fn)","preventionTips":["Check inspect.iscoroutinefunction before wiring hooks","Keep sync and async hook functions in clearly named pairs (e.g. _sync/_async)","Add a unit test constructing every hook you register"],"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"}