Repository: huggingface/huggingface_hub
Pull request #1755 (2023-10-17 14:53:13)

Clearer error message on unprocessable entity.
Fix https://github.com/huggingface/huggingface_hub/issues/1753 by adding a better error message to the 422 unprocessable entity. I chose not to re-raise a different error but appending the message instead (easier in term of backward compatibility and still valid). 

```py
>>> from huggingface_hub import InferenceClient
>>> client = InferenceClient(model="HuggingFaceH4/zephyr-7b-alpha")
>>> output = client.conversational("Hi how are you?")
huggingface_hub.utils._errors.HfHubHTTPError: 422 Client Error: Unprocessable Entity for url: https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-7b-alpha (Request ID: 8gb40cYT0epCW3jTQ5HUd)
Make sure 'conversational' task is supported by the model.
```

```py
# python -m asyncio
>>> from huggingface_hub import AsyncInferenceClient
>>> client = AsyncInferenceClient(model="HuggingFaceH4/zephyr-7b-alpha")
>>> output = await client.conversational("Hi how are you?")
aiohttp.client_exceptions.ClientResponseError: 422, message="Unprocessable Entity. Make sure 'conversational' task is supported by the model.", url=URL('https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-7b-alpha')
```

Also added some unit tests about it.

cc @jamesbraza 


Patch:
diff --git a/docs/source/en/package_reference/inference_client.md b/docs/source/en/package_reference/inference_client.md
index bc14a9e2..a4fa6b35 100644
--- a/docs/source/en/package_reference/inference_client.md
+++ b/docs/source/en/package_reference/inference_client.md
@@ -33,22 +33,36 @@ pip install --upgrade huggingface_hub[inference]
 
 [[autodoc]] AsyncInferenceClient
 
-### InferenceTimeoutError
+## InferenceTimeoutError
 
 [[autodoc]] InferenceTimeoutError
 
-### Return types
+## Return types
 
 For most tasks, the return value has a built-in type (string, list, image...). Here is a list for the more complex types.
 
+### ClassificationOutput
+
 [[autodoc]] huggingface_hub.inference._types.ClassificationOutput
 
+### ConversationalOutputConversation
+
 [[autodoc]] huggingface_hub.inference._types.ConversationalOutputConversation
 
+### ConversationalOutput
+
 [[autodoc]] huggingface_hub.inference._types.ConversationalOutput
 
+### ImageSegmentationOutput
+
 [[autodoc]] huggingface_hub.inference._types.ImageSegmentationOutput
 
+### ModelStatus
+
+[[autodoc]] huggingface_hub.inference._common.ModelStatus
+
+### TokenClassificationOutput
+
 [[autodoc]] huggingface_hub.inference._types.TokenClassificationOutput
 
 ### Text generation types
diff --git a/src/huggingface_hub/inference/_client.py b/src/huggingface_hub/inference/_client.py
index dd56de7e..90e0552a 100644
--- a/src/huggingface_hub/inference/_client.py
+++ b/src/huggingface_hub/inference/_client.py
@@ -240,6 +240,10 @@ class InferenceClient:
                 hf_raise_for_status(response)
                 return response.iter_lines() if stream else response.content
             except HTTPError as error:
+                if error.response.status_code == 422 and task is not None:
+                    error.args = (
+                        f"{error.args[0]}\nMake sure '{task}' task is supported by the model.",
+                    ) + error.args[1:]
                 if error.response.status_code == 503:
                     # If Model is unavailable, either raise a TimeoutError...
                     if timeout is not None and time.time() - t0 > timeout:
diff --git a/src/huggingface_hub/inference/_generated/_async_client.py b/src/huggingface_hub/inference/_generated/_async_client.py
index baa92ea4..dcb29783 100644
--- a/src/huggingface_hub/inference/_generated/_async_client.py
+++ b/src/huggingface_hub/inference/_generated/_async_client.py
@@ -237,6 +237,8 @@ class AsyncInferenceClient:
                 except aiohttp.ClientResponseError as error:
                     error.response_error_payload = response_error_payload
                     await client.close()
+                    if response.status == 422 and task is not None:
+                        error.message += f". Make sure '{task}' task is supported by the model."
                     if response.status == 503:
                         # If Model is unavailable, either raise a TimeoutError...
                         if timeout is not None and time.time() - t0 > timeout:
diff --git a/utils/generate_async_inference_client.py b/utils/generate_async_inference_client.py
index 1bb6e843..538b4e7f 100644
--- a/utils/generate_async_inference_client.py
+++ b/utils/generate_async_inference_client.py
@@ -211,6 +211,8 @@ ASYNC_POST_CODE = """
                 except aiohttp.ClientResponseError as error:
                     error.response_error_payload = response_error_payload
                     await client.close()
+                    if response.status == 422 and task is not None:
+                        error.message += f". Make sure '{task}' task is supported by the model."
                     if response.status == 503:
                         # If Model is unavailable, either raise a TimeoutError...
                         if timeout is not None and time.time() - t0 > timeout:


Test patch:
diff --git a/tests/cassettes/InferenceClientVCRTest.test_unprocessable_entity_error.yaml b/tests/cassettes/InferenceClientVCRTest.test_unprocessable_entity_error.yaml
new file mode 100644
index 00000000..d9fe6fb4
--- /dev/null
+++ b/tests/cassettes/InferenceClientVCRTest.test_unprocessable_entity_error.yaml
@@ -0,0 +1,48 @@
+interactions:
+- request:
+    body: '{"inputs": {"text": "Hi, who are you?"}}'
+    headers:
+      Accept:
+      - '*/*'
+      Accept-Encoding:
+      - gzip, deflate
+      Connection:
+      - keep-alive
+      Content-Length:
+      - '40'
+      Content-Type:
+      - application/json
+      X-Amzn-Trace-Id:
+      - 48cf6e2f-3bab-4dd0-9b37-6db12773d19b
+      user-agent:
+      - unknown/None; hf_hub/0.19.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0;
+        fastcore/1.5.23
+    method: POST
+    uri: https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-7b-alpha
+  response:
+    body:
+      string: 'Failed to deserialize the JSON body into the target type: inputs: invalid
+        type: map, expected a string at line 1 column 11'
+    headers:
+      Connection:
+      - keep-alive
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 17 Oct 2023 14:33:49 GMT
+      Transfer-Encoding:
+      - chunked
+      access-control-allow-credentials:
+      - 'true'
+      access-control-allow-origin:
+      - '*'
+      vary:
+      - origin, Origin, Access-Control-Request-Method, Access-Control-Request-Headers
+      x-request-id:
+      - TKqCDGR8AG1zCiXt-n5K6
+      x-sha:
+      - 5d2b3629ad1799c7e5306e606e0052246098883b
+    status:
+      code: 422
+      message: Unprocessable Entity
+version: 1
diff --git a/tests/cassettes/test_unprocessable_entity_error.yaml b/tests/cassettes/test_unprocessable_entity_error.yaml
new file mode 100644
index 00000000..1b92f34c
--- /dev/null
+++ b/tests/cassettes/test_unprocessable_entity_error.yaml
@@ -0,0 +1,37 @@
+interactions:
+- request:
+    body: null
+    headers:
+      user-agent:
+      - unknown/None; hf_hub/0.19.0.dev0; python/3.10.12; torch/1.12.1; tensorflow/2.11.0;
+        fastcore/1.5.23
+    method: POST
+    uri: https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-7b-alpha
+  response:
+    body:
+      string: 'Failed to deserialize the JSON body into the target type: inputs: invalid
+        type: map, expected a string at line 1 column 11'
+    headers:
+      Access-Control-Allow-Credentials:
+      - 'true'
+      Access-Control-Allow-Origin:
+      - '*'
+      Connection:
+      - keep-alive
+      Content-Type:
+      - text/plain; charset=utf-8
+      Date:
+      - Tue, 17 Oct 2023 14:40:16 GMT
+      Transfer-Encoding:
+      - chunked
+      Vary:
+      - origin, Origin, Access-Control-Request-Method, Access-Control-Request-Headers
+      x-request-id:
+      - N76f5LNsdOm73QrRHFiif
+      x-sha:
+      - 5d2b3629ad1799c7e5306e606e0052246098883b
+    status:
+      code: 422
+      message: Unprocessable Entity
+    url: https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-7b-alpha
+version: 1
diff --git a/tests/test_inference_async_client.py b/tests/test_inference_async_client.py
index 1d827e5a..575080e9 100644
--- a/tests/test_inference_async_client.py
+++ b/tests/test_inference_async_client.py
@@ -254,3 +254,11 @@ async def test_async_generate_timeout_error(monkeypatch: pytest.MonkeyPatch) ->
     monkeypatch.setattr("aiohttp.ClientSession.post", _mock_aiohttp_client_timeout)
     with pytest.raises(InferenceTimeoutError):
         await AsyncInferenceClient(timeout=1).text_generation("test")
+
+
+@pytest.mark.vcr
+@pytest.mark.asyncio
+async def test_unprocessable_entity_error() -> None:
+    with pytest.raises(ClientResponseError) as error:
+        await AsyncInferenceClient().conversational("Hi, who are you?", model="HuggingFaceH4/zephyr-7b-alpha")
+    assert "Make sure 'conversational' task is supported by the model." in error.value.message
diff --git a/tests/test_inference_client.py b/tests/test_inference_client.py
index 1c6f7729..084e91a1 100644
--- a/tests/test_inference_client.py
+++ b/tests/test_inference_client.py
@@ -385,6 +385,10 @@ class InferenceClientVCRTest(InferenceClientTest):
             self.assertIsInstance(item["label"], str)
             self.assertIsInstance(item["score"], float)
 
+    def test_unprocessable_entity_error(self) -> None:
+        with self.assertRaisesRegex(HfHubHTTPError, "Make sure 'conversational' task is supported by the model."):
+            self.client.conversational("Hi, who are you?", model="HuggingFaceH4/zephyr-7b-alpha")
+
 
 class TestOpenAsBinary(InferenceClientTest):
     def test_open_as_binary_with_none(self) -> None: