OpenBMB/ChatDev · error · ValueError

message payload must be a list

Error message

message payload must be a list

What it means

deserialize_messages expects the JSON string payload to decode to a top-level JSON array of message objects. If json.loads yields a dict (single message or {"messages": [...]}), a string, or a number, ValueError is raised.

Source

Thrown at entity/messages.py:410

            metadata=data.get("metadata") or {},
            tool_calls=tool_calls,
            keep=bool(data.get("keep", False)),
            preserve_role=bool(data.get("preserve_role", False)),
        )


def serialize_messages(messages: List[Message], *, include_data: bool = True) -> str:
    """Serialize message list into JSON string."""
    return json.dumps([msg.to_dict(include_data=include_data) for msg in messages], ensure_ascii=False)


def deserialize_messages(payload: str) -> List[Message]:
    """Deserialize JSON string back to messages."""
    if not payload:
        return []
    raw = json.loads(payload)
    if not isinstance(raw, list):
        raise ValueError("message payload must be a list")
    return [Message.from_dict(item) for item in raw if isinstance(item, dict)]


def _copy_content(content: MessageContent) -> MessageContent:
    if content is None:
        return None
    if isinstance(content, str):
        return content
    copied: List[Any] = []
    for block in content:
        if isinstance(block, MessageBlock):
            copied.append(block.copy())
        else:
            copied.append(copy.deepcopy(block))
    return copied

View on GitHub (pinned to 4fb2db0ea9)

Solutions

  1. Ensure the payload is a JSON array: json.dumps(list_of_message_dicts)
  2. If you have a single message, wrap it: json.dumps([msg_dict])
  3. Unwrap envelopes first: payload = resp["messages"] then serialize/deserialize consistently

Example fix

# before
deserialize_messages(json.dumps(msg_dict))
# after
deserialize_messages(json.dumps([msg_dict]))
Defensive patterns

Strategy: type-guard

Validate before calling

import json
raw = json.loads(payload)
if isinstance(raw, dict):
    payload = json.dumps(raw.get("messages", [raw]))

Type guard

def is_message_list_payload(payload: str) -> bool:
    try:
        return isinstance(json.loads(payload), list)
    except Exception:
        return False

Try / catch

try:
    msgs = deserialize_messages(payload)
except ValueError:
    raw = json.loads(payload)
    msgs = [Message.from_dict(m) for m in (raw["messages"] if isinstance(raw, dict) else [raw])]

Prevention

When it happens

Trigger: deserialize_messages(json.dumps(single_message_dict)); passing a payload like "{\"messages\": [...]}" produced by another serializer; double-encoded JSON strings.

Common situations: Round-trip mismatch: one code path stores serialize_messages' list output, another stores a bare message; API responses wrapping messages in an envelope.

Related errors


AI-assisted analysis of OpenBMB/ChatDev@4fb2db0ea9 (2026-08-27). Data as JSON: /api/errors/08e93e05148d9c74. Report an issue: GitHub.