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 executionView on GitHub (pinned to e32fa9a52e)
Solutions
- Provide a sync func alongside the coroutine
- Or call it asynchronously: await tool.ainvoke(...) and run the agent with .ainvoke/.astream
- 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
- Standardize on the async path (.ainvoke) in applications that use any async tools
- Always provide a sync func when authoring tools for shared libraries
- In tests, choose invoke vs ainvoke based on whether tool.func is set
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
- StructuredTool does not support sync invocation.
- Runnable must have an object schema.
- Function must have either a docstring or description when in
- Too many arguments for tool decorator. A decorator
- Runnable without name for tool constructor
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/3d1e3cc434b59bbb.
Report an issue: GitHub.