async def inspect(
        self,
        inputs: Dict[str, np.array],
        output_names: List[str] = None,
        num_predict_intermediate_outputs: Optional[int] = None,
        ignore_const_ops: bool = True,
    ) -> AsyncIterator[Tuple[str, Optional[np.array]]]:
        """
        Retrieves intermediate outputs from the model for given inputs.
        
        Parameters
        ----------
        inputs : Dict[str, np.array]
            Input data for the model.

        output_names : List[str]
            Names of outputs to retrieve. Defaults to all outputs.

        num_predict_intermediate_outputs : Optional[int]
            The number of intermediate outputs to retrieve in each ``MLModel`` prediction call. Defaults to None.

        ignore_const_ops : bool
            Whether to ignore constant operations. Defaults to True.

        Returns
        -------
        AsyncIterator[Tuple[str, Optional[np.array]]]
            An iterator of tuples containing output names and their values.

        Yields
        ------
        Tuple[str, Optional[np.array]]
            A tuple of (output_name, output_value) for each requested output.

        Examples
        --------
        .. sourcecode:: python

            inspector = coremltools.models.ml_program.experimental.debugging_utils.MLModelInspector(
                model
            )
            input_data = {"input_1": np.random.rand(1, 3, 224, 224).astype(np.float32)}
            # The intermediate outputs we want to inspect
            output_names = ["conv1_output", "relu1_output", "pool1_output"]
            async for output_name, output_value in inspector.inspect(
                inputs=input_data, output_names=output_names
            ):
                print(f"Name: {output_name}")
                print(f"Output: {output_value}")
        """

        def batch(iterable, size: int):
            l = len(iterable)
            for index in range(0, l, size):
                yield iterable[index : min(index + size, l)]

        def find_index(lst, value, start):
            if start < 0 or start >= len(lst):
                raise ValueError(f"Invalid start index: {start}")
            try:
                return lst.index(value, start)
            except ValueError:
                return None

        if output_names is None:
            output_names = self.output_names
        else:
            invalid_names = [
                name for name in output_names if name not in self._output_name_to_op_map
            ]
            if len(invalid_names) > 0:
                raise ValueError(
                    f"Invalid output names ({invalid_names}). Available output names are: {list(reversed(self._output_name_to_op_map.keys()))}"
                )

        if len(output_names) == 0:
            return

        output_names = list(OrderedDict.fromkeys(output_names))
        num_predict_intermediate_outputs = (
            num_predict_intermediate_outputs
            if num_predict_intermediate_outputs is not None
            else len(output_names)
        )
        for curr_output_names in batch(output_names, num_predict_intermediate_outputs):
            """
            Handle potential missing outputs gracefully:
            1. Iterate through the requested output names in the given order.
            2. For each output name:
                - If the value is available in the outputs, include it.
                - If the value is not found, use None as the value.
            """
            processed_index = -1
            async for model, processed_output_names in self._generate_models_with_outputs(
                output_names=curr_output_names,
                ignore_const_ops=ignore_const_ops,
            ):
                if len(processed_output_names) == 0:
                    continue

                outputs = await model.predict(inputs)
                max_idx = find_index(
                    curr_output_names,
                    processed_output_names[-1],
                    processed_index + 1,
                )
                if max_idx is None:
                    continue

                max_idx = max(max_idx, processed_index + 1)
                for i in range(processed_index + 1, max_idx + 1):
                    output_name = curr_output_names[i]
                    output_value = outputs.get(output_name, None)
                    yield (output_name, output_value)

                processed_index = max_idx

            for i in range(processed_index + 1, len(curr_output_names)):
                output_name = curr_output_names[i]
                yield (output_name, None)

        Retrieves intermediate outputs from the model for given inputs.
        
        Parameters
        ----------
        inputs : Dict[str, np.array]
            Input data for the model.

        output_names : List[str]
            Names of outputs to retrieve. Defaults to all outputs.

        num_predict_intermediate_outputs : Optional[int]
            The number of intermediate outputs to retrieve in each ``MLModel`` prediction call. Defaults to None.

        ignore_const_ops : bool
            Whether to ignore constant operations. Defaults to True.

        Returns
        -------
        AsyncIterator[Tuple[str, Optional[np.array]]]
            An iterator of tuples containing output names and their values.

        Yields
        ------
        Tuple[str, Optional[np.array]]
            A tuple of (output_name, output_value) for each requested output.

        Examples
        --------
        .. sourcecode:: python

            inspector = coremltools.models.ml_program.experimental.debugging_utils.MLModelInspector(
                model
            )
            input_data = {"input_1": np.random.rand(1, 3, 224, 224).astype(np.float32)}
            # The intermediate outputs we want to inspect
            output_names = ["conv1_output", "relu1_output", "pool1_output"]
            async for output_name, output_value in inspector.inspect(
                inputs=input_data, output_names=output_names
            ):
                print(f"Name: {output_name}")
                print(f"Output: {output_value}")
        
coremltools/models/ml_program/experimental/debugging_utils.py