async def process_api(
        self,
        block_fn: BlockFunction | int,
        inputs: list[Any],
        state: SessionState | None = None,
        request: Request | list[Request] | None = None,
        iterator: AsyncIterator | None = None,
        session_hash: str | None = None,
        event_id: str | None = None,
        event_data: EventData | None = None,
        in_event_listener: bool = True,
        simple_format: bool = False,
        explicit_call: bool = False,
        root_path: str | None = None,
    ) -> dict[str, Any]:
        """
        Processes API calls from the frontend. First preprocesses the data,
        then runs the relevant function, then postprocesses the output.

        Parameters:
            fn_index: Index of function to run.
            inputs: input data received from the frontend
            state: data stored from stateful components for session (key is input block id)
            request: the gr.Request object containing information about the network request (e.g. IP address, headers, query parameters, username)
            iterators: the in-progress iterators for each generator function (key is function index)
            event_id: id of event that triggered this API call
            event_data: data associated with the event trigger itself
            in_event_listener: whether this API call is being made in response to an event listener
            explicit_call: whether this call is being made directly by calling the Blocks function, instead of through an event listener or API route
            root_path: if provided, the root path of the server. All file URLs will be prefixed with this path.

        Returns a dictionary with the following keys:
            - "data": the output data from the function
            - "is_generating": whether the function is generating output
            - "iterator": the iterator for the function
            - "duration": the duration of the function call
            - "average_duration": the average duration of the function call
            - "render_config": the render config for the function
        """
        if isinstance(block_fn, int):
            block_fn = self.fns[block_fn]
        batch = block_fn.batch
        state_ids_to_track, hashed_values = self.get_state_ids_to_track(block_fn, state)
        changed_state_ids = []
        LocalContext.blocks.set(self)

        if batch:
            max_batch_size = block_fn.max_batch_size
            batch_sizes = [len(inp) for inp in inputs]
            batch_size = batch_sizes[0]
            if inspect.isasyncgenfunction(block_fn.fn) or inspect.isgeneratorfunction(
                block_fn.fn
            ):
                raise ValueError("Gradio does not support generators in batch mode.")
            if not all(x == batch_size for x in batch_sizes):
                raise ValueError(
                    f"All inputs to a batch function must have the same length but instead have sizes: {batch_sizes}."
                )
            if batch_size > max_batch_size:
                raise ValueError(
                    f"Batch size ({batch_size}) exceeds the max_batch_size for this function ({max_batch_size})"
                )
            inputs = [
                await self.preprocess_data(block_fn, list(i), state, explicit_call)
                for i in zip(*inputs, strict=False)
            ]
            result = await self.call_function(
                block_fn,
                list(zip(*inputs, strict=False)),
                None,
                request,
                event_id,
                event_data,
                in_event_listener,
                state,
            )
            preds = result["prediction"]
            data = [
                await self.postprocess_data(block_fn, list(o), state)
                for o in zip(*preds, strict=False)
            ]
            if root_path is not None:
                data = processing_utils.add_root_url(data, root_path, None)  # type: ignore
            data = list(zip(*data, strict=False))
            is_generating, iterator = None, None
        else:
            old_iterator = iterator
            if old_iterator:
                inputs = []
            else:
                inputs = await self.preprocess_data(
                    block_fn, inputs, state, explicit_call
                )
            was_generating = old_iterator is not None
            result = await self.call_function(
                block_fn,
                inputs,
                old_iterator,
                request,
                event_id,
                event_data,
                in_event_listener,
                state,
            )
            data = await self.postprocess_data(block_fn, result["prediction"], state)
            if state:
                changed_state_ids = [
                    state_id
                    for hash_value, state_id in zip(
                        hashed_values, state_ids_to_track, strict=False
                    )
                    if hash_value != utils.deep_hash(state[state_id])
                ]

            if root_path is not None:
                data = processing_utils.add_root_url(data, root_path, None)
            is_generating, iterator = result["is_generating"], result["iterator"]
            if is_generating or was_generating:
                run = id(old_iterator) if was_generating else id(iterator)
                data = await self.handle_streaming_outputs(
                    block_fn,
                    data,
                    session_hash=session_hash,
                    run=run,
                    root_path=root_path,
                    final=not is_generating,
                )
                data = self.handle_streaming_diffs(
                    block_fn,
                    data,
                    session_hash=session_hash,
                    run=run,
                    final=not is_generating,
                    simple_format=simple_format,
                )

        block_fn.total_runtime += result["duration"]
        block_fn.total_runs += 1
        output = {
            "data": data,
            "is_generating": is_generating,
            "iterator": iterator,
            "duration": result["duration"],
            "average_duration": block_fn.total_runtime / block_fn.total_runs,
            "render_config": None,
            "changed_state_ids": changed_state_ids,
        }
        if block_fn.renderable and state:
            output["render_config"] = state.blocks_config.get_config(
                block_fn.renderable
            )
            output["render_config"]["render_id"] = block_fn.renderable._id
            if root_path is not None:
                output["render_config"] = processing_utils.add_root_url(
                    output["render_config"], root_path, None
                )

        return output

        Processes API calls from the frontend. First preprocesses the data,
        then runs the relevant function, then postprocesses the output.

        Parameters:
            fn_index: Index of function to run.
            inputs: input data received from the frontend
            state: data stored from stateful components for session (key is input block id)
            request: the gr.Request object containing information about the network request (e.g. IP address, headers, query parameters, username)
            iterators: the in-progress iterators for each generator function (key is function index)
            event_id: id of event that triggered this API call
            event_data: data associated with the event trigger itself
            in_event_listener: whether this API call is being made in response to an event listener
            explicit_call: whether this call is being made directly by calling the Blocks function, instead of through an event listener or API route
            root_path: if provided, the root path of the server. All file URLs will be prefixed with this path.

        Returns a dictionary with the following keys:
            - "data": the output data from the function
            - "is_generating": whether the function is generating output
            - "iterator": the iterator for the function
            - "duration": the duration of the function call
            - "average_duration": the average duration of the function call
            - "render_config": the render config for the function
        
gradio/blocks.py