deepset-ai/haystack · error · TypeError

Each response must be a string or ChatMessage, got {type(ite

Error message

Each response must be a string or ChatMessage, got {type(item)}.

What it means

Inside a `responses` sequence, each element must be a string or a ChatMessage. Any other item type (dict, int, None, bytes) raises TypeError naming the item's type, raised from _normalize_responses during __init__.

Source

Thrown at haystack/components/generators/chat/mock.py:169

            items = list(responses)
        else:
            raise TypeError(f"'responses' must be a string, ChatMessage, or a sequence of them, got {type(responses)}.")

        if len(items) == 0:
            raise ValueError("'responses' must not be an empty list.")

        normalized: list[ChatMessage] = []
        for item in items:
            if isinstance(item, str):
                normalized.append(ChatMessage.from_assistant(item))
            elif isinstance(item, ChatMessage):
                if item.role != ChatRole.ASSISTANT:
                    raise ValueError(
                        f"Each ChatMessage response must have the 'assistant' role, got '{item.role.value}'."
                    )
                normalized.append(item)
            else:
                raise TypeError(f"Each response must be a string or ChatMessage, got {type(item)}.")
        return normalized

    def to_dict(self) -> dict[str, Any]:
        """Serialize the component to a dictionary."""
        responses = [msg.to_dict() for msg in self._responses] if self._responses is not None else None
        response_fn = serialize_callable(self.response_fn) if self.response_fn is not None else None
        streaming_callback = serialize_callable(self.streaming_callback) if self.streaming_callback else None
        return default_to_dict(
            self,
            responses=responses,
            response_fn=response_fn,
            model=self.model,
            meta=self.meta,
            streaming_callback=streaming_callback,
        )

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> MockChatGenerator:

View on GitHub (pinned to e318778c9b)

Solutions

  1. Convert dicts with ChatMessage.from_dict(d) before passing
  2. Use plain strings for simple text replies
  3. Sanitize/mixed lists: map each item through str/ChatMessage.from_dict as appropriate

Example fix

// before
mock = MockChatGenerator(responses=[{"text": "hi", "role": "assistant"}])
// after
mock = MockChatGenerator(responses=[ChatMessage.from_dict({"text": "hi", "role": "assistant"})])
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_responses(items) -> list[ChatMessage]:
    out = []
    for i in items:
        if isinstance(i, str):
            out.append(ChatMessage.from_assistant(i))
        elif isinstance(i, ChatMessage):
            out.append(i)
        elif isinstance(i, dict):
            out.append(ChatMessage.from_dict(i))
        else:
            raise TypeError(f"bad response item: {type(i)}")
    return out

Type guard

def is_response_item(i) -> bool:
    return isinstance(i, (str, ChatMessage))

Prevention

When it happens

Trigger: `MockChatGenerator(responses=[{"text": "hi"}])` or [None], or a list mixing strings with dicts/ints — often from JSON-loaded data where ChatMessages deserialize as dicts.

Common situations: Loading canned responses from JSON/YAML fixtures and forgetting to call ChatMessage.from_dict; building mixed lists programmatically; passing raw message dicts from another framework.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/efa78e9a3cc3f8e7. Report an issue: GitHub.