We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
decode_args with tuple return value
I'm confused about how to use `decode_args()` when the model returns a tuple of, let's say, a numpy array.

If I have an inference function with the following signature

```python
import numpy as np
from mlserver.codecs.decorator import decode_args

def predict(input: np.ndarray) -> tuple[np.ndarray]:
    # simple identity model
    return (input, )

decode_args(predict)
```
I receive a

```
CodecNotFound: Codec not found for output field with type tuple[numpy.ndarray]
```

error message.

I think the issue lies in the following line that is used to unwrap the tuple signature and convert it to a list of types.

https://github.com/SeldonIO/MLServer/blob/9d7045cc3c50a14201733865ca0c3e8480ebd942/mlserver/codecs/decorator.py#L37

However the line checks if the type hint is a tuple instance, which is not true in the above case: `isinstance(tuple[np.ndarray], tuple) == False)`

To make `decode_args` work with a tuple return signature, I have to change the function signature to

```python
def predict(input: np.ndarray) -> tuple([np.ndarray]):
```
which is an actual tuple instance and not a type hint. This looks like a bug to me. 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mlserver/codecs/decorator.py`
Content:
```
1 from functools import wraps, partial
2 from typing import (
3     Any,
4     Callable,
5     Coroutine,
6     Dict,
7     List,
8     Optional,
9     Union,
10     Type,
11     Tuple,
12     get_origin,
13     get_args,
14     get_type_hints,
15     TYPE_CHECKING,
16 )
17 
18 
19 from ..types import InferenceRequest, InferenceResponse, ResponseOutput
20 
21 from .base import RequestCodec, InputCodec, find_input_codec, find_request_codec
22 from .errors import InputsNotFound, OutputNotFound, CodecNotFound
23 from .utils import Codec
24 
25 if TYPE_CHECKING:
26     from ..model import MLModel
27 
28 PredictFunc = Callable[
29     ["MLModel", InferenceRequest], Coroutine[Any, Any, InferenceResponse]
30 ]
31 
32 
33 def _as_list(a: Optional[Union[Any, Tuple[Any]]]) -> List[Any]:
34     if a is None:
35         return []
36 
37     if isinstance(a, tuple):
38         # Split into components
39         return list(a)
40 
41     # Otherwise, assume it's a single element
42     return [a]
43 
44 
45 def _is_codec_type(c: Codec, t: Type) -> bool:
46     if issubclass(c, t):  # type: ignore
47         return True
48 
49     if isinstance(c, t):
50         return True
51 
52     return False
53 
54 
55 _is_input_codec = partial(_is_codec_type, t=InputCodec)
56 _is_request_codec = partial(_is_codec_type, t=RequestCodec)
57 
58 
59 def _is_optional(t: Type) -> bool:
60     origin = get_origin(t)
61     if origin == Optional:
62         return True
63 
64     if origin == Union:
65         # Cover case where Optional[a] is reported as Union[a, None]
66         args = get_args(t)
67         if len(args) == 2 and type(None) in args:
68             return True
69 
70     return False
71 
72 
73 def _unwrap_optional(t: Type) -> Type:
74     args = get_args(t)
75     for arg in args:
76         if not isinstance(arg, type(None)):
77             return arg
78 
79     return t
80 
81 
82 class SignatureCodec(RequestCodec):
83     """
84     Internal codec that knows how to map type hints to codecs.
85     """
86 
87     # TODO: Should this receive the whole class as argument?
88     def __init__(self, predict: Callable):
89         self._predict = predict
90         self._input_codecs, self._output_codecs = self._get_codecs(predict)
91 
92     def _get_codecs(self, pred: Callable) -> Tuple[Dict[str, Codec], List[Codec]]:
93         self._input_hints = self._get_type_hints(pred)
94         self._output_hints = _as_list(self._input_hints.pop("return", None))
95 
96         input_codecs = {}
97         for name, type_hint in self._input_hints.items():
98             codec = self._find_codec(name=name, type_hint=type_hint, is_input=True)
99             # TODO: Consider metadata as well! (needs to be done at runtime)
100             input_codecs[name] = codec
101 
102         output_codecs = []
103         for type_hint in self._output_hints:
104             # Try either as an input or as a request codec
105             codec = self._find_codec(name=None, type_hint=type_hint, is_input=False)
106             output_codecs.append(codec)
107 
108         return input_codecs, output_codecs
109 
110     def _get_type_hints(self, pred: Callable) -> Dict[str, Type]:
111         type_hints = get_type_hints(pred)
112         # For us, `typing.Optional` is just syntactic sugar, so let's ensure we
113         # unwrap it
114         for name, hint in type_hints.items():
115             if _is_optional(hint):
116                 type_hints[name] = _unwrap_optional(hint)
117 
118         return type_hints
119 
120     def _find_codec(
121         self, name: Optional[str], type_hint: Type, is_input: bool = False
122     ) -> Codec:
123         codec = find_input_codec(type_hint=type_hint)
124         if codec is not None:
125             return codec
126 
127         codec = find_request_codec(type_hint=type_hint)  # type: ignore
128         if codec is not None:
129             return codec
130 
131         raise CodecNotFound(name=name, payload_type=str(type_hint), is_input=is_input)
132 
133     def decode_request(  # type: ignore
134         self, request: InferenceRequest
135     ) -> Dict[str, Any]:
136         inputs = {}
137         extra_request_inputs = []
138         for request_input in request.inputs:
139             input_name = request_input.name
140             if input_name not in self._input_codecs:
141                 # Aggregate extra request inputs to check later, as they could
142                 # be part of aggregated request codecs (e.g. like dataframes)
143                 extra_request_inputs.append(request_input)
144                 continue
145 
146             # Ensure matching codec is an input codec
147             codec = self._input_codecs[input_name]
148             if not _is_input_codec(codec):
149                 raise CodecNotFound(name=input_name, is_input=True)
150 
151             inputs[input_name] = codec.decode_input(request_input)  # type: ignore
152 
153         if extra_request_inputs:
154             request_codec = self._get_request_codec()
155             if not request_codec:
156                 # If there are no request codecs that can aggregate all
157                 # remaining inputs, raise an error
158                 raise InputsNotFound(extra_request_inputs, self._input_codecs)
159 
160             # We create a fake request built from the extra request inputs
161             name, codec = request_codec
162             extra_inputs = InferenceRequest(inputs=extra_request_inputs)
163             inputs[name] = codec.decode_request(extra_inputs)
164 
165         return inputs
166 
167     def _get_request_codec(self) -> Optional[Tuple[str, RequestCodec]]:
168         for name, codec in self._input_codecs.items():
169             if _is_request_codec(codec):
170                 return name, codec  # type: ignore
171 
172         return None
173 
174     def encode_response(  # type: ignore
175         self, model_name: str, payload: Any, model_version: Optional[str] = None
176     ) -> InferenceResponse:
177         payloads = _as_list(payload)
178         outputs = []
179         for idx, payload in enumerate(payloads):
180             outputs += self._encode_outputs(idx, payload)
181 
182         return InferenceResponse(
183             model_name=model_name, model_version=model_version, outputs=outputs
184         )
185 
186     def _encode_outputs(self, idx: int, payload: Any) -> List[ResponseOutput]:
187         output_type = type(payload)
188         if idx >= len(self._output_codecs):
189             raise OutputNotFound(idx, output_type, self._output_hints)
190 
191         # TODO: Fallback to encode_by_payload?
192         codec = self._output_codecs[idx]
193         if not codec.can_encode(payload):
194             raise OutputNotFound(idx, output_type, self._output_hints)
195 
196         if _is_input_codec(codec):
197             # TODO: Check model metadata for output names
198             output_name = f"output-{idx}"
199             response_output = codec.encode_output(  # type: ignore
200                 name=output_name, payload=payload
201             )
202             return [response_output]
203 
204         if _is_request_codec(codec):
205             # NOTE: We will ignore `model_name` and only grab the outputs
206             response = codec.encode_response(  # type: ignore
207                 model_name="", payload=payload
208             )
209             return response.outputs
210 
211         return []
212 
213 
214 def decode_args(predict: Callable) -> PredictFunc:
215     codec = SignatureCodec(predict)
216 
217     @wraps(predict)
218     async def _f(self: "MLModel", request: InferenceRequest) -> InferenceResponse:
219         inputs = codec.decode_request(request=request)
220 
221         outputs = await predict(self, **inputs)
222 
223         return codec.encode_response(
224             model_name=self.name, payload=outputs, model_version=self.version
225         )
226 
227     return _f
228
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mlserver/codecs/decorator.py b/mlserver/codecs/decorator.py
--- a/mlserver/codecs/decorator.py
+++ b/mlserver/codecs/decorator.py
@@ -38,6 +38,10 @@
         # Split into components
         return list(a)
 
+    if get_origin(a) is tuple:
+        # Convert type arguments into list
+        return list(get_args(a))
+
     # Otherwise, assume it's a single element
     return [a]

{"golden_diff": "diff --git a/mlserver/codecs/decorator.py b/mlserver/codecs/decorator.py\n--- a/mlserver/codecs/decorator.py\n+++ b/mlserver/codecs/decorator.py\n@@ -38,6 +38,10 @@\n         # Split into components\n         return list(a)\n \n+    if get_origin(a) is tuple:\n+        # Convert type arguments into list\n+        return list(get_args(a))\n+\n     # Otherwise, assume it's a single element\n     return [a]\n", "issue": "decode_args with tuple return value\nI'm confused about how to use `decode_args()` when the model returns a tuple of, let's say, a numpy array.\r\n\r\nIf I have an inference function with the following signature\r\n\r\n```python\r\nimport numpy as np\r\nfrom mlserver.codecs.decorator import decode_args\r\n\r\ndef predict(input: np.ndarray) -> tuple[np.ndarray]:\r\n    # simple identity model\r\n    return (input, )\r\n\r\ndecode_args(predict)\r\n```\r\nI receive a\r\n\r\n```\r\nCodecNotFound: Codec not found for output field with type tuple[numpy.ndarray]\r\n```\r\n\r\nerror message.\r\n\r\nI think the issue lies in the following line that is used to unwrap the tuple signature and convert it to a list of types.\r\n\r\nhttps://github.com/SeldonIO/MLServer/blob/9d7045cc3c50a14201733865ca0c3e8480ebd942/mlserver/codecs/decorator.py#L37\r\n\r\nHowever the line checks if the type hint is a tuple instance, which is not true in the above case: `isinstance(tuple[np.ndarray], tuple) == False)`\r\n\r\nTo make `decode_args` work with a tuple return signature, I have to change the function signature to\r\n\r\n```python\r\ndef predict(input: np.ndarray) -> tuple([np.ndarray]):\r\n```\r\nwhich is an actual tuple instance and not a type hint. This looks like a bug to me. \n", "before_files": [{"content": "from functools import wraps, partial\nfrom typing import (\n    Any,\n    Callable,\n    Coroutine,\n    Dict,\n    List,\n    Optional,\n    Union,\n    Type,\n    Tuple,\n    get_origin,\n    get_args,\n    get_type_hints,\n    TYPE_CHECKING,\n)\n\n\nfrom ..types import InferenceRequest, InferenceResponse, ResponseOutput\n\nfrom .base import RequestCodec, InputCodec, find_input_codec, find_request_codec\nfrom .errors import InputsNotFound, OutputNotFound, CodecNotFound\nfrom .utils import Codec\n\nif TYPE_CHECKING:\n    from ..model import MLModel\n\nPredictFunc = Callable[\n    [\"MLModel\", InferenceRequest], Coroutine[Any, Any, InferenceResponse]\n]\n\n\ndef _as_list(a: Optional[Union[Any, Tuple[Any]]]) -> List[Any]:\n    if a is None:\n        return []\n\n    if isinstance(a, tuple):\n        # Split into components\n        return list(a)\n\n    # Otherwise, assume it's a single element\n    return [a]\n\n\ndef _is_codec_type(c: Codec, t: Type) -> bool:\n    if issubclass(c, t):  # type: ignore\n        return True\n\n    if isinstance(c, t):\n        return True\n\n    return False\n\n\n_is_input_codec = partial(_is_codec_type, t=InputCodec)\n_is_request_codec = partial(_is_codec_type, t=RequestCodec)\n\n\ndef _is_optional(t: Type) -> bool:\n    origin = get_origin(t)\n    if origin == Optional:\n        return True\n\n    if origin == Union:\n        # Cover case where Optional[a] is reported as Union[a, None]\n        args = get_args(t)\n        if len(args) == 2 and type(None) in args:\n            return True\n\n    return False\n\n\ndef _unwrap_optional(t: Type) -> Type:\n    args = get_args(t)\n    for arg in args:\n        if not isinstance(arg, type(None)):\n            return arg\n\n    return t\n\n\nclass SignatureCodec(RequestCodec):\n    \"\"\"\n    Internal codec that knows how to map type hints to codecs.\n    \"\"\"\n\n    # TODO: Should this receive the whole class as argument?\n    def __init__(self, predict: Callable):\n        self._predict = predict\n        self._input_codecs, self._output_codecs = self._get_codecs(predict)\n\n    def _get_codecs(self, pred: Callable) -> Tuple[Dict[str, Codec], List[Codec]]:\n        self._input_hints = self._get_type_hints(pred)\n        self._output_hints = _as_list(self._input_hints.pop(\"return\", None))\n\n        input_codecs = {}\n        for name, type_hint in self._input_hints.items():\n            codec = self._find_codec(name=name, type_hint=type_hint, is_input=True)\n            # TODO: Consider metadata as well! (needs to be done at runtime)\n            input_codecs[name] = codec\n\n        output_codecs = []\n        for type_hint in self._output_hints:\n            # Try either as an input or as a request codec\n            codec = self._find_codec(name=None, type_hint=type_hint, is_input=False)\n            output_codecs.append(codec)\n\n        return input_codecs, output_codecs\n\n    def _get_type_hints(self, pred: Callable) -> Dict[str, Type]:\n        type_hints = get_type_hints(pred)\n        # For us, `typing.Optional` is just syntactic sugar, so let's ensure we\n        # unwrap it\n        for name, hint in type_hints.items():\n            if _is_optional(hint):\n                type_hints[name] = _unwrap_optional(hint)\n\n        return type_hints\n\n    def _find_codec(\n        self, name: Optional[str], type_hint: Type, is_input: bool = False\n    ) -> Codec:\n        codec = find_input_codec(type_hint=type_hint)\n        if codec is not None:\n            return codec\n\n        codec = find_request_codec(type_hint=type_hint)  # type: ignore\n        if codec is not None:\n            return codec\n\n        raise CodecNotFound(name=name, payload_type=str(type_hint), is_input=is_input)\n\n    def decode_request(  # type: ignore\n        self, request: InferenceRequest\n    ) -> Dict[str, Any]:\n        inputs = {}\n        extra_request_inputs = []\n        for request_input in request.inputs:\n            input_name = request_input.name\n            if input_name not in self._input_codecs:\n                # Aggregate extra request inputs to check later, as they could\n                # be part of aggregated request codecs (e.g. like dataframes)\n                extra_request_inputs.append(request_input)\n                continue\n\n            # Ensure matching codec is an input codec\n            codec = self._input_codecs[input_name]\n            if not _is_input_codec(codec):\n                raise CodecNotFound(name=input_name, is_input=True)\n\n            inputs[input_name] = codec.decode_input(request_input)  # type: ignore\n\n        if extra_request_inputs:\n            request_codec = self._get_request_codec()\n            if not request_codec:\n                # If there are no request codecs that can aggregate all\n                # remaining inputs, raise an error\n                raise InputsNotFound(extra_request_inputs, self._input_codecs)\n\n            # We create a fake request built from the extra request inputs\n            name, codec = request_codec\n            extra_inputs = InferenceRequest(inputs=extra_request_inputs)\n            inputs[name] = codec.decode_request(extra_inputs)\n\n        return inputs\n\n    def _get_request_codec(self) -> Optional[Tuple[str, RequestCodec]]:\n        for name, codec in self._input_codecs.items():\n            if _is_request_codec(codec):\n                return name, codec  # type: ignore\n\n        return None\n\n    def encode_response(  # type: ignore\n        self, model_name: str, payload: Any, model_version: Optional[str] = None\n    ) -> InferenceResponse:\n        payloads = _as_list(payload)\n        outputs = []\n        for idx, payload in enumerate(payloads):\n            outputs += self._encode_outputs(idx, payload)\n\n        return InferenceResponse(\n            model_name=model_name, model_version=model_version, outputs=outputs\n        )\n\n    def _encode_outputs(self, idx: int, payload: Any) -> List[ResponseOutput]:\n        output_type = type(payload)\n        if idx >= len(self._output_codecs):\n            raise OutputNotFound(idx, output_type, self._output_hints)\n\n        # TODO: Fallback to encode_by_payload?\n        codec = self._output_codecs[idx]\n        if not codec.can_encode(payload):\n            raise OutputNotFound(idx, output_type, self._output_hints)\n\n        if _is_input_codec(codec):\n            # TODO: Check model metadata for output names\n            output_name = f\"output-{idx}\"\n            response_output = codec.encode_output(  # type: ignore\n                name=output_name, payload=payload\n            )\n            return [response_output]\n\n        if _is_request_codec(codec):\n            # NOTE: We will ignore `model_name` and only grab the outputs\n            response = codec.encode_response(  # type: ignore\n                model_name=\"\", payload=payload\n            )\n            return response.outputs\n\n        return []\n\n\ndef decode_args(predict: Callable) -> PredictFunc:\n    codec = SignatureCodec(predict)\n\n    @wraps(predict)\n    async def _f(self: \"MLModel\", request: InferenceRequest) -> InferenceResponse:\n        inputs = codec.decode_request(request=request)\n\n        outputs = await predict(self, **inputs)\n\n        return codec.encode_response(\n            model_name=self.name, payload=outputs, model_version=self.version\n        )\n\n    return _f\n", "path": "mlserver/codecs/decorator.py"}], "after_files": [{"content": "from functools import wraps, partial\nfrom typing import (\n    Any,\n    Callable,\n    Coroutine,\n    Dict,\n    List,\n    Optional,\n    Union,\n    Type,\n    Tuple,\n    get_origin,\n    get_args,\n    get_type_hints,\n    TYPE_CHECKING,\n)\n\n\nfrom ..types import InferenceRequest, InferenceResponse, ResponseOutput\n\nfrom .base import RequestCodec, InputCodec, find_input_codec, find_request_codec\nfrom .errors import InputsNotFound, OutputNotFound, CodecNotFound\nfrom .utils import Codec\n\nif TYPE_CHECKING:\n    from ..model import MLModel\n\nPredictFunc = Callable[\n    [\"MLModel\", InferenceRequest], Coroutine[Any, Any, InferenceResponse]\n]\n\n\ndef _as_list(a: Optional[Union[Any, Tuple[Any]]]) -> List[Any]:\n    if a is None:\n        return []\n\n    if isinstance(a, tuple):\n        # Split into components\n        return list(a)\n\n    if get_origin(a) is tuple:\n        # Convert type arguments into list\n        return list(get_args(a))\n\n    # Otherwise, assume it's a single element\n    return [a]\n\n\ndef _is_codec_type(c: Codec, t: Type) -> bool:\n    if issubclass(c, t):  # type: ignore\n        return True\n\n    if isinstance(c, t):\n        return True\n\n    return False\n\n\n_is_input_codec = partial(_is_codec_type, t=InputCodec)\n_is_request_codec = partial(_is_codec_type, t=RequestCodec)\n\n\ndef _is_optional(t: Type) -> bool:\n    origin = get_origin(t)\n    if origin == Optional:\n        return True\n\n    if origin == Union:\n        # Cover case where Optional[a] is reported as Union[a, None]\n        args = get_args(t)\n        if len(args) == 2 and type(None) in args:\n            return True\n\n    return False\n\n\ndef _unwrap_optional(t: Type) -> Type:\n    args = get_args(t)\n    for arg in args:\n        if not isinstance(arg, type(None)):\n            return arg\n\n    return t\n\n\nclass SignatureCodec(RequestCodec):\n    \"\"\"\n    Internal codec that knows how to map type hints to codecs.\n    \"\"\"\n\n    # TODO: Should this receive the whole class as argument?\n    def __init__(self, predict: Callable):\n        self._predict = predict\n        self._input_codecs, self._output_codecs = self._get_codecs(predict)\n\n    def _get_codecs(self, pred: Callable) -> Tuple[Dict[str, Codec], List[Codec]]:\n        self._input_hints = self._get_type_hints(pred)\n        self._output_hints = _as_list(self._input_hints.pop(\"return\", None))\n\n        input_codecs = {}\n        for name, type_hint in self._input_hints.items():\n            codec = self._find_codec(name=name, type_hint=type_hint, is_input=True)\n            # TODO: Consider metadata as well! (needs to be done at runtime)\n            input_codecs[name] = codec\n\n        output_codecs = []\n        for type_hint in self._output_hints:\n            # Try either as an input or as a request codec\n            codec = self._find_codec(name=None, type_hint=type_hint, is_input=False)\n            output_codecs.append(codec)\n\n        return input_codecs, output_codecs\n\n    def _get_type_hints(self, pred: Callable) -> Dict[str, Type]:\n        type_hints = get_type_hints(pred)\n        # For us, `typing.Optional` is just syntactic sugar, so let's ensure we\n        # unwrap it\n        for name, hint in type_hints.items():\n            if _is_optional(hint):\n                type_hints[name] = _unwrap_optional(hint)\n\n        return type_hints\n\n    def _find_codec(\n        self, name: Optional[str], type_hint: Type, is_input: bool = False\n    ) -> Codec:\n        codec = find_input_codec(type_hint=type_hint)\n        if codec is not None:\n            return codec\n\n        codec = find_request_codec(type_hint=type_hint)  # type: ignore\n        if codec is not None:\n            return codec\n\n        raise CodecNotFound(name=name, payload_type=str(type_hint), is_input=is_input)\n\n    def decode_request(  # type: ignore\n        self, request: InferenceRequest\n    ) -> Dict[str, Any]:\n        inputs = {}\n        extra_request_inputs = []\n        for request_input in request.inputs:\n            input_name = request_input.name\n            if input_name not in self._input_codecs:\n                # Aggregate extra request inputs to check later, as they could\n                # be part of aggregated request codecs (e.g. like dataframes)\n                extra_request_inputs.append(request_input)\n                continue\n\n            # Ensure matching codec is an input codec\n            codec = self._input_codecs[input_name]\n            if not _is_input_codec(codec):\n                raise CodecNotFound(name=input_name, is_input=True)\n\n            inputs[input_name] = codec.decode_input(request_input)  # type: ignore\n\n        if extra_request_inputs:\n            request_codec = self._get_request_codec()\n            if not request_codec:\n                # If there are no request codecs that can aggregate all\n                # remaining inputs, raise an error\n                raise InputsNotFound(extra_request_inputs, self._input_codecs)\n\n            # We create a fake request built from the extra request inputs\n            name, codec = request_codec\n            extra_inputs = InferenceRequest(inputs=extra_request_inputs)\n            inputs[name] = codec.decode_request(extra_inputs)\n\n        return inputs\n\n    def _get_request_codec(self) -> Optional[Tuple[str, RequestCodec]]:\n        for name, codec in self._input_codecs.items():\n            if _is_request_codec(codec):\n                return name, codec  # type: ignore\n\n        return None\n\n    def encode_response(  # type: ignore\n        self, model_name: str, payload: Any, model_version: Optional[str] = None\n    ) -> InferenceResponse:\n        payloads = _as_list(payload)\n        outputs = []\n        for idx, payload in enumerate(payloads):\n            outputs += self._encode_outputs(idx, payload)\n\n        return InferenceResponse(\n            model_name=model_name, model_version=model_version, outputs=outputs\n        )\n\n    def _encode_outputs(self, idx: int, payload: Any) -> List[ResponseOutput]:\n        output_type = type(payload)\n        if idx >= len(self._output_codecs):\n            raise OutputNotFound(idx, output_type, self._output_hints)\n\n        # TODO: Fallback to encode_by_payload?\n        codec = self._output_codecs[idx]\n        if not codec.can_encode(payload):\n            raise OutputNotFound(idx, output_type, self._output_hints)\n\n        if _is_input_codec(codec):\n            # TODO: Check model metadata for output names\n            output_name = f\"output-{idx}\"\n            response_output = codec.encode_output(  # type: ignore\n                name=output_name, payload=payload\n            )\n            return [response_output]\n\n        if _is_request_codec(codec):\n            # NOTE: We will ignore `model_name` and only grab the outputs\n            response = codec.encode_response(  # type: ignore\n                model_name=\"\", payload=payload\n            )\n            return response.outputs\n\n        return []\n\n\ndef decode_args(predict: Callable) -> PredictFunc:\n    codec = SignatureCodec(predict)\n\n    @wraps(predict)\n    async def _f(self: \"MLModel\", request: InferenceRequest) -> InferenceResponse:\n        inputs = codec.decode_request(request=request)\n\n        outputs = await predict(self, **inputs)\n\n        return codec.encode_response(\n            model_name=self.name, payload=outputs, model_version=self.version\n        )\n\n    return _f\n", "path": "mlserver/codecs/decorator.py"}]}