Repository: deepset-ai/haystack

Issue:
Reintroduce helpful error messages in ChatMessage._deserialize_content
**Describe the bug**
The helpful error messages introduced in [#9484](https://github.com/deepset-ai/haystack/pull/9484) were accidentally removed during later refactorings of `ChatMessage` (images + reasoning). Without them, developers and LLMs (when generating chat messages JSON) don’t get feedback info about the correct format. This increases the chance of repeated tool invocation failures and sometimes LLMs, after repeated failures, even completely give up.

**Error message**
Example:

```
Failed to invoke Tool `accommodation_strategy_optimizer` with parameters {'messages': [{'role': 'user', 'content': ['Optimize accommodation strategy...']}]}. 
Error: Unsupported content part in the serialized ChatMessage: `Optimize accommodation strategy...`
```

Previously, the error also included:

```
dictionary contains one of these keys: 'text', 'tool_call', or 'tool_call_result'. 
Valid formats: [{'text': 'Hello'}, {'tool_call': {...}}, {'tool_call_result': {...}}]
```

and

```
The `role` field is required in the message dictionary. 
Expected a dictionary with 'role' field containing one of: ['user', 'assistant', ...]
Received dictionary with keys: [...]
```

**Expected behavior**
Error messages should clearly state:

* Which keys are required (`role`)
* Which keys are valid in `content` (`text`, `tool_call`, `tool_call_result`)
* Example valid formats

**Additional context**

* Issue appeared after refactorings in `chat_message.py`
* OpenAI models often retry and eventually guess the right format, but other LLMs may not
* Without clear messages, error logs become noisier and both developer and model feedback loops degrade

**FAQ Check**

* [ ] Have you had a look at [[our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)](https://docs.haystack.deepset.ai/docs/faq)?



Discussion:
Luckily, the helpful message about role is still present: https://github.com/deepset-ai/haystack/blob/41b7ed4f472b7c26cc6cb879e8fda1197b422f49/haystack/dataclasses/chat_message.py#L533



Patch:
diff --git a/haystack/dataclasses/chat_message.py b/haystack/dataclasses/chat_message.py
index 4ffb29505f..9d7a2af1ef 100644
--- a/haystack/dataclasses/chat_message.py
+++ b/haystack/dataclasses/chat_message.py
@@ -209,7 +209,17 @@ def _deserialize_content_part(part: dict[str, Any]) -> ChatMessageContentT:
         if serialization_key in part:
             return cls.from_dict(part[serialization_key])
 
-    raise ValueError(f"Unsupported content part in the serialized ChatMessage: `{part}`")
+    # NOTE: this verbose error message provides guidance to LLMs when creating invalid messages during agent runs
+    msg = (
+        f"Unsupported content part in the serialized ChatMessage: {part}. "
+        "The `content` field of the serialized ChatMessage must be a list of dictionaries, where each "
+        "dictionary contains one of these keys: 'text', 'image', 'reasoning', 'tool_call', or 'tool_call_result'. "
+        "Valid formats: [{'text': 'Hello'}, {'image': {'base64_image': '...', ...}}, "
+        "{'reasoning': {'reasoning_text': 'I think...', 'extra': {...}}}, "
+        "{'tool_call': {'tool_name': 'search', 'arguments': {}, 'id': 'call_123'}}, "
+        "{'tool_call_result': {'result': 'data', 'origin': {...}, 'error': false}}]"
+    )
+    raise ValueError(msg)
 
 
 def _serialize_content_part(part: ChatMessageContentT) -> dict[str, Any]:
@@ -530,6 +540,8 @@ def from_dict(cls, data: dict[str, Any]) -> "ChatMessage":
         :returns:
             The created object.
         """
+
+        # NOTE: this verbose error message provides guidance to LLMs when creating invalid messages during agent runs
         if not "role" in data and not "_role" in data:
             raise ValueError(
                 "The `role` field is required in the message dictionary. "
diff --git a/releasenotes/notes/chatmessage-deserialization-error-message-c0a02f3c9be08092.yaml b/releasenotes/notes/chatmessage-deserialization-error-message-c0a02f3c9be08092.yaml
new file mode 100644
index 0000000000..ecd626caaf
--- /dev/null
+++ b/releasenotes/notes/chatmessage-deserialization-error-message-c0a02f3c9be08092.yaml
@@ -0,0 +1,8 @@
+---
+
+fixes:
+  - |
+    Reintroduce verbose error message when deserializing a `ChatMessage` with invalid content parts.
+    While LLMs may still generate messages in the wrong format, this error provides guidance on the expected structure,
+    making retries easier and more reliable during agent runs.
+    The error message was unintentionally removed during a previous refactoring.


Test patch:
diff --git a/test/dataclasses/test_chat_message.py b/test/dataclasses/test_chat_message.py
index 98bfd78e3a..37e88fc42f 100644
--- a/test/dataclasses/test_chat_message.py
+++ b/test/dataclasses/test_chat_message.py
@@ -398,11 +398,11 @@ def test_to_dict_with_invalid_content_type(self):
 
     def test_from_dict_with_invalid_content_type(self):
         data = {"role": "assistant", "content": [{"text": "Hello"}, "invalid"]}
-        with pytest.raises(ValueError, match="Unsupported content part in the serialized ChatMessage"):
+        with pytest.raises(ValueError, match=r"Unsupported content part.*Valid formats.*"):
             ChatMessage.from_dict(data)
 
         data = {"role": "assistant", "content": [{"text": "Hello"}, {"invalid": "invalid"}]}
-        with pytest.raises(ValueError, match="Unsupported content part in the serialized ChatMessage"):
+        with pytest.raises(ValueError, match=r"Unsupported content part.*Valid formats.*"):
             ChatMessage.from_dict(data)
 
     def test_from_dict_with_missing_role(self):