langchain-ai/langchain · error · NotImplementedError

Tool does not support sync invocation.

Error message

Tool does not support sync invocation.

What it means

Tool._run raises NotImplementedError when self.func is None, i.e. the tool was constructed with only a coroutine. Sync invocation (.invoke/.run) has no implementation to call; only async invocation (.ainvoke) works.

Source

Thrown at libs/core/langchain_core/tools/simple.py:124

        """Use the tool.

        Args:
            *args: Positional arguments to pass to the tool
            config: Configuration for the run
            run_manager: Optional callback manager to use for the run
            **kwargs: Keyword arguments to pass to the tool

        Returns:
            The result of the tool execution
        """
        if self.func:
            if run_manager and signature(self.func).parameters.get("callbacks"):
                kwargs["callbacks"] = run_manager.get_child()
            if config_param := _get_runnable_config_param(self.func):
                kwargs[config_param] = config
            return self.func(*args, **kwargs)
        msg = "Tool does not support sync invocation."
        raise NotImplementedError(msg)

    async def _arun(
        self,
        *args: Any,
        config: RunnableConfig,
        run_manager: AsyncCallbackManagerForToolRun | None = None,
        **kwargs: Any,
    ) -> Any:
        """Use the tool asynchronously.

        Args:
            *args: Positional arguments to pass to the tool
            config: Configuration for the run
            run_manager: Optional callback manager to use for the run
            **kwargs: Keyword arguments to pass to the tool

        Returns:
            The result of the tool execution

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Provide a sync func alongside the coroutine
  2. Or call it asynchronously: await tool.ainvoke(...) and run the agent with .ainvoke/.astream
  3. In ToolNode/agent contexts, use the async path (await node.ainvoke(...)) so the coroutine is used

Example fix

# before
tool = Tool.from_function(func=None, coroutine=my_async_fn, ...)  # func omitted
tool.invoke("x")  # NotImplementedError

# after
result = asyncio.run(tool.ainvoke("x"))
# or provide a sync bridge:
def sync_fn(x):
    return asyncio.run(my_async_fn(x))
tool = Tool(name="t", func=sync_fn, coroutine=my_async_fn, description="...")
Defensive patterns

Strategy: type-guard

Validate before calling

def supports_sync(tool) -> bool:
    return getattr(tool, "func", None) is not None

def invoke_maybe_async(tool, payload):
    if supports_sync(tool):
        return tool.invoke(payload)
    import asyncio
    return asyncio.run(tool.ainvoke(payload))

Type guard

def is_sync_capable(tool: object) -> bool:
    return callable(getattr(tool, "func", None))

Try / catch

try:
    out = tool.invoke(payload)
except NotImplementedError:
    out = asyncio.run(tool.ainvoke(payload))

Prevention

When it happens

Trigger: StructuredTool-style Tool created with coroutine=async_fn and func=None, then called via tool.invoke(...), tool.run(...), or ToolNode with a sync executor / agent using .invoke().

Common situations: Building async-only tools (I/O-bound APIs) but running the agent loop synchronously; a library expecting sync tools receiving an async-only one; CI tests calling .invoke on async tools.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/3d1e3cc434b59bbb. Report an issue: GitHub.