Run ``flow_fn`` over ``inputs`` with bounded concurrency. Args: flow_fn: Any flow function with signature ``(input, *, cfg, **kwargs) -> Awaitable[OutputT]``. inputs: Inputs to fan out over. Empty list returns an empty ``BatchResult`` immediately.
(
flow_fn: Callable[..., Awaitable[OutputT]],
inputs: list[InputT],
*,
cfg: BaseFlowCfg | None = None,
concurrency: int = 4,
on_error: Literal["raise", "skip"] = "skip",
on_progress: Callable[[int, int], None] | None = None,
**flow_kwargs: Any,
)
| 49 | |
| 50 | |
| 51 | async def batch_run( |
| 52 | flow_fn: Callable[..., Awaitable[OutputT]], |
| 53 | inputs: list[InputT], |
| 54 | *, |
| 55 | cfg: BaseFlowCfg | None = None, |
| 56 | concurrency: int = 4, |
| 57 | on_error: Literal["raise", "skip"] = "skip", |
| 58 | on_progress: Callable[[int, int], None] | None = None, |
| 59 | **flow_kwargs: Any, |
| 60 | ) -> BatchResult[OutputT]: |
| 61 | """Run ``flow_fn`` over ``inputs`` with bounded concurrency. |
| 62 | |
| 63 | Args: |
| 64 | flow_fn: Any flow function with signature |
| 65 | ``(input, *, cfg, **kwargs) -> Awaitable[OutputT]``. |
| 66 | inputs: Inputs to fan out over. Empty list returns an empty |
| 67 | ``BatchResult`` immediately. |
| 68 | cfg: Shared cfg forwarded to every call. ``None`` lets the flow |
| 69 | use its own default. |
| 70 | concurrency: Maximum number of in-flight calls. Must be ≥ 1. |
| 71 | on_error: ``"raise"`` propagates the first failure (siblings get |
| 72 | cancelled); ``"skip"`` records every failure into |
| 73 | ``errors`` and returns the batch normally. |
| 74 | on_progress: Called as ``on_progress(done, total)`` after every |
| 75 | completion (success or failure). Must be cheap and |
| 76 | non-blocking — callbacks are invoked synchronously inside |
| 77 | the worker loop. |
| 78 | **flow_kwargs: Forwarded verbatim to ``flow_fn``. ``memory=`` is |
| 79 | **forbidden** in MVP; passing it raises ``ValueError``. |
| 80 | |
| 81 | Returns: |
| 82 | ``BatchResult`` with ``results`` parallel to ``inputs`` (None for |
| 83 | failures) and ``errors`` sorted by index. |
| 84 | |
| 85 | Raises: |
| 86 | ValueError: If ``memory=`` is passed via ``flow_kwargs``, or if |
| 87 | ``concurrency < 1``. |
| 88 | Exception: Re-raised when ``on_error="raise"`` and any input |
| 89 | fails. The exception is the first one raised by a worker; |
| 90 | other workers may already be cancelled when this surfaces. |
| 91 | """ |
| 92 | if "memory" in flow_kwargs: |
| 93 | raise ValueError( |
| 94 | "batch_run does not support `memory=` in MVP. For " |
| 95 | "memory-accumulating workflows write a serial loop instead: " |
| 96 | "`for inp in inputs: await flow_fn(inp, cfg=cfg, memory=memory)`. " |
| 97 | "See design doc §4.3.5." |
| 98 | ) |
| 99 | if concurrency < 1: |
| 100 | raise ValueError(f"concurrency must be >= 1, got {concurrency}") |
| 101 | |
| 102 | sem = asyncio.Semaphore(concurrency) |
| 103 | results: list[OutputT | None] = [None] * len(inputs) |
| 104 | errors: list[tuple[int, Exception]] = [] |
| 105 | started = time.monotonic() |
| 106 | done_counter = 0 |
| 107 | |
| 108 | async def run_one(i: int, inp: InputT) -> None: |