deepset-ai/haystack · error · TypeError

Invalid messages type. Expected list[ChatMessage] or str.

Error message

Invalid messages type. Expected list[ChatMessage] or str.

What it means

_normalize_messages accepts either a single string (treated as one user message) or a list of ChatMessage objects. Anything else raises TypeError, which is also surfaced for lists containing non-ChatMessage items.

Source

Thrown at haystack/components/generators/utils.py:225

    """
    if hasattr(obj, "model_dump"):
        return obj.model_dump()
    if hasattr(obj, "__dict__"):
        return {k: _serialize_object(v) for k, v in obj.__dict__.items() if not k.startswith("_")}
    if isinstance(obj, dict):
        return {k: _serialize_object(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [_serialize_object(item) for item in obj]
    return obj


def _normalize_messages(messages: list[ChatMessage] | str) -> list[ChatMessage]:
    """Normalize messages to a list of ChatMessage objects."""
    if isinstance(messages, str):
        return [ChatMessage.from_user(messages)]
    if isinstance(messages, list) and all(isinstance(msg, ChatMessage) for msg in messages):
        return messages
    raise TypeError("Invalid messages type. Expected list[ChatMessage] or str.")

View on GitHub (pinned to e318778c9b)

Solutions

  1. Convert legacy strings: [ChatMessage.from_user(s) for s in list_of_strings]
  2. Wrap a single ChatMessage in a list: [msg]
  3. If a dict is passed, convert to ChatMessage via the appropriate constructor

Example fix

// before
llm.run(messages=["hello", "world"])
// after
llm.run(messages=[ChatMessage.from_user("hello"), ChatMessage.from_user("world")])
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(messages, str):
    messages = [ChatMessage.from_user(messages)]
elif not (isinstance(messages, list) and all(isinstance(m, ChatMessage) for m in messages)):
    raise TypeError("Expected list[ChatMessage] or str")

Type guard

def is_valid_messages(v) -> bool:
    return isinstance(v, str) or (isinstance(v, list) and all(isinstance(m, ChatMessage) for m in v))

Try / catch

try:
    gen.run(messages=messages)
except TypeError as e:
    if "Invalid messages type" in str(e):
        messages = [ChatMessage.from_user(s) for s in messages]
        gen.run(messages=messages)

Prevention

When it happens

Trigger: Passing a plain dict, a list of strings (e.g. ["hi"]), a single ChatMessage (not wrapped in a list), or None to a generator/util that expects list[ChatMessage] | str.

Common situations: After a version change where a generator switched from accepting str|list[str] to list[ChatMessage]; passing prompt strings or dicts built for other APIs.

Related errors


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