{"record":{"id":"b42c5dcfe2c5e7f9","repo":"keras-team/keras","slug":"a-merge-layer-should-be-called-on-a-list-of-inputs-b42c5d","errorCode":null,"errorMessage":"A merge layer should be called on a list of inputs. Received: inputs={inputs} (not a list of tensors)","messagePattern":"A merge layer should be called on a list of inputs\\. Received: inputs=(.+?) \\(not a list of tensors\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"keras/src/layers/merging/base_merge.py","lineNumber":145,"sourceCode":"        for i in range(1, len(input_shape)):\n            if input_shape[i] is None:\n                shape = None\n            else:\n                shape = input_shape[i][1:]\n            output_shape = self._compute_elemwise_op_output_shape(\n                output_shape, shape\n            )\n\n        # If the inputs have different ranks, we have to reshape them\n        # to make them broadcastable.\n        if None not in input_shape and len(set(map(len, input_shape))) == 1:\n            self._reshape_required = False\n        else:\n            self._reshape_required = True\n\n    def call(self, inputs):\n        if not isinstance(inputs, (list, tuple)):\n            raise ValueError(\n                \"A merge layer should be called on a list of inputs. \"\n                f\"Received: inputs={inputs} (not a list of tensors)\"\n            )\n        if self._reshape_required:\n            reshaped_inputs = []\n            input_ndims = list(map(ops.ndim, inputs))\n            if None not in input_ndims:\n                # If ranks of all inputs are available,\n                # we simply expand each of them at axis=1\n                # until all of them have the same rank.\n                max_ndim = max(input_ndims)\n                for x in inputs:\n                    x_ndim = ops.ndim(x)\n                    for _ in range(max_ndim - x_ndim):\n                        x = ops.expand_dims(x, axis=1)\n                    reshaped_inputs.append(x)\n                return self._merge_function(reshaped_inputs)\n            else:","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/keras-team/keras/blob/7a34a03db60bf60042242d6a556fc3be119046a5/keras/src/layers/merging/base_merge.py#L127-L163","documentation":"The runtime counterpart of the build-time list check: Merge.call() requires its inputs argument to be a list or tuple of tensors. Passing a single tensor, a dict, or any non-sequence raises immediately.","triggerScenarios":"Calling merge_layer(x) with a bare tensor; a saved model that wraps a merge layer where the input list was unwrapped during serialization; custom layers forwarding a single value into a merge call().","commonSituations":"Refactoring a multi-input model to single input but leaving merge layers in place; deserialization edge cases where lists become single tensors; incorrect use of * unpacking.","solutions":["Always call merge layers with a list: merge([x, y])","When writing custom call() that delegates to a merge, forward the list structure intact","Fix model serialization/wrappers so the merge layer receives a list at inference"],"exampleFix":"# before\nout = merge_layer(x)\n\n# after\nout = merge_layer([x, x2])","handlingStrategy":"type-guard","validationCode":"assert isinstance(inputs, (list, tuple)), 'merge layer needs a list of tensors'","typeGuard":"def is_tensor_list(x) -> bool:\n    return isinstance(x, (list, tuple)) and all(hasattr(t, 'shape') for t in x)","tryCatchPattern":null,"preventionTips":["Always call merge layers with list syntax merge([a, b])","Preserve list structure in custom wrappers and deserialized models"],"tags":["keras","merge","input-format","runtime"],"backgroundTag":"wrong-argument-structure","analyzedSha":"7a34a03db60bf60042242d6a556fc3be119046a5","analyzedAt":"2026-08-25T21:25:25.994Z","schemaVersion":2},"datasetVersion":"2026-08-26T02:17:13.382Z"}