keras/src/models/functional.py/Functional/make_spec_for_tensor
class Functional:    def input_spec(self):
        if hasattr(self, "_manual_input_spec"):
            return self._manual_input_spec

        def shape_with_no_batch_size(x):
            x = list(x)
            if x:
                x[0] = None
            return tuple(x)

        def make_spec_for_tensor(x):
            optional = False
            if isinstance(x._keras_history[0], InputLayer):
                if x._keras_history[0].optional:
                    optional = True
            return InputSpec(
                shape=shape_with_no_batch_size(x.shape),
                allow_last_axis_squeeze=True,
                name=x._keras_history[0].name,
                optional=optional,
            )

        if isinstance(self._inputs_struct, dict):
            if all(
                isinstance(x, backend.KerasTensor)
                for x in self._inputs_struct.values()
            ):
                # Case where `_nested_inputs` is a plain dict of Inputs.
                names = sorted(self._inputs_struct.keys())
                return [
                    InputSpec(
                        shape=shape_with_no_batch_size(
                            self._inputs_struct[name].shape
                        ),
                        allow_last_axis_squeeze=True,
                        name=name,
                    )
                    for name in names
                ]
            return None  # Deeply nested dict: skip checks.
        return [make_spec_for_tensor(x) for x in self.inputs]