langchain-ai/langchain · error · NotImplementedError

Message as a sequence must be (role string, template)

Error message

Message as a sequence must be (role string, template)

What it means

Raised (as `NotImplementedError`) by `convert_to_messages` when a sequence-form message cannot be unpacked into exactly two elements `(role_string, template)`. Sequences are only supported in the strict 2-tuple form; anything else (3-tuple, 1-tuple, or a non-string sequence misused) hits this.

Source

Thrown at libs/core/langchain_core/messages/utils.py:742

    Returns:
        An instance of a message or a message template.

    Raises:
        NotImplementedError: if the message type is not supported.
        ValueError: if the message dict does not contain the required keys.

    """
    if isinstance(message, BaseMessage):
        message_ = message
    elif isinstance(message, Sequence):
        if isinstance(message, str):
            message_ = _create_message_from_message_type("human", message)
        else:
            try:
                message_type_str, template = message
            except ValueError as e:
                msg = "Message as a sequence must be (role string, template)"
                raise NotImplementedError(msg) from e
            message_ = _create_message_from_message_type(message_type_str, template)
    elif isinstance(message, dict):
        # `Serializable` constructor-envelope wire shape. Detect structurally, map
        # the class name to a known message-type string via a hardcoded
        # allowlist, and recurse with the canonical
        # `{"type": ..., **kwargs}` shape — no `load()`, no dynamic
        # class instantiation.
        if (
            message.get("lc") == 1
            and message.get("type") == "constructor"
            and isinstance(message.get("id"), list)
            and message["id"]
            and isinstance(message.get("kwargs"), dict)
        ):
            mapped = _LC_CONSTRUCTOR_NAME_TO_TYPE.get(message["id"][-1])
            if mapped is not None:
                return _convert_to_message({"type": mapped, **message["kwargs"]})

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Use exactly 2-element tuples: `(role_str, content)`
  2. Validate tuple length before passing: `assert len(t) == 2`
  3. Prefer the dict form `{'type': role, 'content': content}` for anything with extra fields

Example fix

# before
convert_to_messages([('human', 'hi', {'meta': 1})])

# after
convert_to_messages([('human', 'hi')])
# extra data goes in the dict form instead:
# [{'type': 'human', 'content': 'hi', 'additional_kwargs': {'meta': 1}}]
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_tuple_message(m) -> bool:
    return isinstance(m, tuple) and len(m) == 2 and isinstance(m[0], str)

Type guard

def is_role_template_pair(m: object) -> bool:
    return (isinstance(m, (tuple, list)) and len(m) == 2
            and isinstance(m[0], str) and isinstance(m[1], (str, list)))

Try / catch

try:
    msgs = convert_to_messages(raw)
except NotImplementedError as e:
    if 'sequence must be (role string, template)' in str(e):
        raw = [(r, c) for r, c, *_ in (m if len(m) > 2 else (*m, None) for m in raw if isinstance(m, (tuple, list)))]
        msgs = convert_to_messages(raw)
    else:
        raise

Prevention

When it happens

Trigger: Passing `('human', 'hi', 'extra')`, `('human',)`, or a list whose unpacking raises `ValueError` inside the `message_type_str, template = message` statement.

Common situations: Building message tuples programmatically and accidentally appending extra fields; passing a 3-element tuple intended for another API; a list of characters when a string was expected (though bare strings take the human path).

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/89240d5ca0a4e94c. Report an issue: GitHub.