langchain-ai/langchain · error · ValueError

Expected a single list of messages. Got {input_val}.

Error message

Expected a single list of messages. Got {input_val}.

What it means

RunnableWithMessageHistory._get_input_messages handles batched chat-model inputs: when the input is a list whose first element is itself a list, it assumes a batch of exactly one and returns that inner list. If there are two or more inner lists it cannot unambiguously pick one, so it raises this ValueError.

Source

Thrown at libs/core/langchain_core/runnables/history.py:481

            input_val = input_val[key]

        # If value is a string, convert to a human message
        if isinstance(input_val, str):
            return [HumanMessage(content=input_val)]
        # If value is a single message, convert to a list
        if isinstance(input_val, BaseMessage):
            return [input_val]
        # If value is a list or tuple...
        if isinstance(input_val, (list, tuple)):
            # Handle empty case
            if len(input_val) == 0:
                return list(input_val)
            # If is a list of list, then return the first value
            # This occurs for chat models - since we batch inputs
            if isinstance(input_val[0], list):
                if len(input_val) != 1:
                    msg = f"Expected a single list of messages. Got {input_val}."
                    raise ValueError(msg)
                return input_val[0]
            return list(input_val)
        msg = (
            f"Expected str, BaseMessage, list[BaseMessage], or tuple[BaseMessage]. "
            f"Got {input_val}."
        )
        raise ValueError(msg)

    def _get_output_messages(
        self, output_val: str | BaseMessage | Sequence[BaseMessage] | dict[str, Any]
    ) -> list[BaseMessage]:
        # If dictionary, try to pluck the single key representing messages
        if isinstance(output_val, dict):
            if self.output_messages_key:
                key = self.output_messages_key
            elif len(output_val) == 1:
                key = next(iter(output_val.keys()))
            else:

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Call .batch([...]) on the wrapped chain so each item is routed separately, instead of .invoke([[...], [...]])
  2. Invoke one message list at a time: chain.invoke([msg], config)
  3. If input is a single list of messages, ensure elements are BaseMessage objects, not nested lists

Example fix

# before
wrapped.invoke([[m1], [m2]], config)  # ValueError
# after
wrapped.batch([[m1], [m2]], [config, config])
# or one at a time
wrapped.invoke([m1], config)
Defensive patterns

Strategy: type-guard

Validate before calling

msgs = chain_input if isinstance(chain_input, list) else [chain_input]
if msgs and isinstance(msgs[0], list):
    assert len(msgs) == 1, "invoke one batch element at a time, or use .batch()"

Type guard

from langchain_core.messages import BaseMessage

def is_single_message_batch(v: object) -> bool:
    return isinstance(v, list) and (not v or isinstance(v[0], BaseMessage))

Try / catch

try:
    out = wrapped.invoke(user_input, cfg)
except ValueError as e:
    if "single list of messages" in str(e):
        outs = wrapped.batch(user_input, [cfg] * len(user_input))

Prevention

When it happens

Trigger: Invoking a RunnableWithMessageHistory-wrapped chain with a batch like [[msg1], [msg2]] passed to .invoke()/.stream() instead of .batch(), so the wrapper sees multiple message lists.

Common situations: Reusing a chat-model-style input shape (list of lists) with the history wrapper; migrating code from direct chat model calls; feeding batch payloads through .invoke().

Related errors


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