cocoindex-io/cocoindex · error · ValueError

Async functions are not supported by @coco.fn decorator when

Error message

Async functions are not supported by @coco.fn decorator when batching or runner is specified. Please use @coco.fn.as_async instead.

What it means

_SyncFunctionBuilder only supports sync functions; when batching or runner is configured and an async def function is passed, it raises because async functions must go through the async builder (@coco.fn.as_async).

Source

Thrown at python/cocoindex/_internal/function.py:1892

            sync_fn,
            memo=self._memo,
            memo_key=self._memo_key,
            batching=self._batching,
            max_batch_size=self._max_batch_size,
            runner=self._runner,
            version=self._version,
            logic_tracking=self._logic_tracking,
            deps=self._deps,
        )
        functools.update_wrapper(wrapper, fn)
        return wrapper


# Only supports sync function -> sync function
class _SyncFunctionBuilder(_GenericFunctionBuilder):
    def __call__(self, fn: Callable[P, R_co]) -> SyncFunction[P, R_co]:
        if inspect.iscoroutinefunction(fn):
            raise ValueError(
                "Async functions are not supported by @coco.fn decorator "
                "when batching or runner is specified. "
                "Please use @coco.fn.as_async instead."
            )
        return self._build_sync(fn)


# Supports sync function -> sync function and async function -> async function
class _AutoFunctionBuilder(_GenericFunctionBuilder):
    def __init__(
        self,
        *,
        memo: bool = False,
        memo_key: MemoKeySpec = None,
        version: int | None = None,
        logic_tracking: LogicTracking = "full",
        deps: Any = None,
    ) -> None:

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Use @coco.fn.as_async(batching=True) (or with runner=) instead of @coco.fn.
  2. Or make the function synchronous def if it performs no awaiting, keeping @coco.fn.
  3. Drop batching/runner if plain per-item async processing is intended.

Example fix

// before
@coco.fn(batching=True)
async def embed(texts): ...
// after
@coco.fn.as_async(batching=True)
async def embed(texts): ...
Defensive patterns

Strategy: validation

Validate before calling

import inspect
if inspect.iscoroutinefunction(fn) and (batching or runner is not None):
    deco = coco.fn.as_async  # pick async builder up front

Type guard

def is_async_fn(fn): return inspect.iscoroutinefunction(fn)

Try / catch

try:
    return coco.fn(batching=True)(fn)
except ValueError:
    return coco.fn.as_async(batching=True)(fn)

Prevention

When it happens

Trigger: Applying @coco.fn(batching=True) or @coco.fn(runner=...) to an async def function — inspect.iscoroutinefunction(fn) is True so __call__ raises before building.

Common situations: Naturally writing the processor as async def (common for I/O-bound work like embedding calls) but reaching for plain @coco.fn with batching options copied from an example.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/0eb5570a081fdd8a. Report an issue: GitHub.