deepset-ai/haystack · error · TypeError

'responses' must be a string, ChatMessage, or a sequence of

Error message

'responses' must be a string, ChatMessage, or a sequence of them, got {type(responses)}.

What it means

MockChatGenerator._normalize_responses only accepts a single string, a single ChatMessage, or a sequence (list/tuple) of those. Any other type (dict, int, a set, a generator-like object not a Sequence) raises TypeError naming the offending type.

Source

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

        self.streaming_callback = streaming_callback
        self._call_count = 0
        self._is_warmed_up = False

    @staticmethod
    def _normalize_responses(
        responses: str | ChatMessage | Sequence[str | ChatMessage] | None,
    ) -> list[ChatMessage] | None:
        """Normalize the `responses` argument into a non-empty list of `ChatMessage`, or `None` for echo mode."""
        if responses is None:
            return None

        items: list[str | ChatMessage]
        if isinstance(responses, (str, ChatMessage)):
            items = [responses]
        elif isinstance(responses, Sequence):
            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

View on GitHub (pinned to e318778c9b)

Solutions

  1. Wrap a single value in a list: responses=["my reply"]
  2. Use a list of strings or ChatMessages: responses=[ChatMessage.from_assistant("hi")]
  3. If you have a dict, convert to a list of ChatMessages yourself or use response_fn
  4. Check the variable actually holds the list you think (print/log type before constructing)

Example fix

// before
mock = MockChatGenerator(responses="hi")  # actually valid only if str; dicts raise
mock = MockChatGenerator(responses={"a": "b"})
// after
mock = MockChatGenerator(responses=[ChatMessage.from_assistant("b")])
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(responses, (str, ChatMessage, Sequence)):
    raise TypeError(f"responses must be str, ChatMessage, or sequence, got {type(responses)}")

Type guard

def is_valid_responses(r) -> bool:
    if isinstance(r, (str, ChatMessage)):
        return True
    return isinstance(r, Sequence) and all(isinstance(i, (str, ChatMessage)) for i in r)

Prevention

When it happens

Trigger: Calling `MockChatGenerator(responses={...})` or with a non-iterable scalar like an int/float, or with a custom object masquerading as a response list.

Common situations: Passing a dict of {prompt: reply} expecting dict support; JSON round-trips turning the list into another type; typos like responses="hi" split incorrectly or responses=True.

Related errors


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