deepset-ai/haystack · error

A FunctionHook requires at least one of `function` or `async

Error message

A FunctionHook requires at least one of `function` or `async_function` to be set.

What it means

FunctionHook wraps a sync and/or async callable and raises ValueError in __init__ (haystack/hooks/from_function.py:50) when neither function nor async_function is provided. A hook with no callable would have no behavior to run, so construction fails immediately.

Source

Thrown at haystack/hooks/from_function.py:50

    """

    def __init__(
        self,
        function: Callable[[State], None] | None = None,
        async_function: Callable[[State], Awaitable[None]] | None = None,
    ) -> None:
        """
        Initialize the hook with a synchronous function, an async function, or both.

        :param function: The synchronous function invoked by `run`. Must be a regular function — coroutine functions
            should be passed to `async_function` instead. Either `function` or `async_function` (or both) must be set.
        :param async_function: Optional coroutine function awaited by `run_async`. When only `async_function` is set,
            `run` raises a `RuntimeError`. When only `function` is set, `run_async` calls `function`.
        :raises ValueError: If neither is set, if `function` is a coroutine function, if `async_function` is not, or
            if a provided function does not declare a `State`-typed parameter.
        """
        if function is None and async_function is None:
            raise ValueError("A FunctionHook requires at least one of `function` or `async_function` to be set.")
        if function is not None and inspect.iscoroutinefunction(function):
            raise ValueError(
                f"`function` must be a synchronous function. '{function.__name__}' is a coroutine function. "
                "Pass it as `async_function` instead."
            )
        if async_function is not None and not inspect.iscoroutinefunction(async_function):
            raise ValueError(
                f"`async_function` must be a coroutine function defined with `async def`. "
                f"Got '{getattr(async_function, '__name__', repr(async_function))}'."
            )
        for func in (function, async_function):
            if func is not None and not _takes_single_state_argument(func):
                raise ValueError(
                    f"Hook function '{func.__name__}' must take a single parameter annotated with `State` "
                    "(e.g. `def my_hook(state: State) -> None`)."
                )
        self.function = function
        self.async_function = async_function

View on GitHub (pinned to e318778c9b)

Solutions

  1. Pass at least one callable: FunctionHook(function=my_sync_fn) or FunctionHook(async_function=my_async_fn).
  2. Check why the variable holding the function is None (import failure, wrong name).
  3. Guard at the call site: assert callable(fn) before constructing.

Example fix

// before
hook = FunctionHook()  # both None
// after
hook = FunctionHook(function=my_sync_fn, async_function=my_async_fn)
Defensive patterns

Strategy: validation

Validate before calling

def make_function_hook(fn=None, async_fn=None):
    if fn is None and async_fn is None:
        raise ValueError("provide function and/or async_function")
    return FunctionHook(function=fn, async_function=async_fn)

Type guard

def is_callable_or_none(f) -> bool:
    return f is None or callable(f)

Try / catch

try:
    hook = FunctionHook(function=fn, async_function=afn)
except ValueError as e:
    if "at least one of" in str(e):
        logger.error("FunctionHook built with no callable; check wiring of %r/%r", fn, afn)
        raise
    raise

Prevention

When it happens

Trigger: FunctionHook() with both arguments None/omitted; passing a variable that is None at call time; conditionally assigning the callable and having the condition fail.

Common situations: Refactoring where the callable was deleted or renamed; loading a hook spec from config where the function reference didn't resolve; typos so the kwarg never receives the function.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/861480a5648ceb7a. Report an issue: GitHub.