deepset-ai/haystack · error

This FunctionHook only has an `async_function` and cannot ru

Error message

This FunctionHook only has an `async_function` and cannot run in a synchronous Agent run. Use the Agent's async run methods, or provide a synchronous `function`.

What it means

A FunctionHook created with only an `async_function` has no synchronous implementation. When the Agent is run through sync entry points (e.g. agent.run), hooks must execute synchronously, so run() raises RuntimeError telling you to use async run methods or supply a sync `function`.

Source

Thrown at haystack/hooks/from_function.py:78

            )
        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

    def run(self, state: State) -> None:
        """
        Run the synchronous function against the live `State`.

        :param state: The Agent's live `State`, mutated in place by the wrapped function.
        :raises RuntimeError: If the hook only has an `async_function`; use the Agent's async run methods instead.
        """
        if self.function is None:
            raise RuntimeError(
                "This FunctionHook only has an `async_function` and cannot run in a synchronous Agent run. "
                "Use the Agent's async run methods, or provide a synchronous `function`."
            )
        self.function(state)

    async def run_async(self, state: State) -> None:
        """
        Await the async function if set, otherwise call the synchronous function.

        :param state: The Agent's live `State`, mutated in place by the wrapped function.
        """
        if self.async_function is not None:
            await self.async_function(state)
        else:
            self.function(state)  # type: ignore[misc]  # guaranteed non-None: at least one is always set

    def to_dict(self) -> dict[str, Any]:
        """

View on GitHub (pinned to e318778c9b)

Solutions

  1. Run the Agent with its async methods (agent.run_async / await the async run)
  2. Provide a synchronous counterpart: FunctionHook(function=sync_fn, async_function=async_fn)
  3. Wrap the async work in a sync function with asyncio.run if async-only is not an option

Example fix

// before
hook = FunctionHook(async_function=my_async_hook)
agent.run(query)  # RuntimeError
// after
hook = FunctionHook(function=my_sync_hook, async_function=my_async_hook)
agent.run(query)  # works, sync path uses my_sync_hook
Defensive patterns

Strategy: try-catch

Validate before calling

if hook.function is None:
    result = await agent.run_async(query)
else:
    result = agent.run(query)

Try / catch

try:
    result = agent.run(query)
except RuntimeError as e:
    if "only has an `async_function`" in str(e):
        result = await agent.run_async(query)
    else:
        raise

Prevention

When it happens

Trigger: Constructing FunctionHook(async_function=...) (or from_function with only the async callable) and then running the Agent synchronously, causing FunctionHook.run to see self.function is None.

Common situations: Async-only hooks in codebases that also invoke the agent via sync CLI/scripts/tests; mixing sync and async pipelines where the sync path was forgotten.

Related errors


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