OpenBMB/ChatDev · error · ConfigError

role must be 'user' or 'assistant'

Error message

role must be 'user' or 'assistant'

What it means

LiteralNodeConfig.from_dict rejects an optional 'role' that is not one of the two chat roles 'user' or 'assistant' (compared case-insensitively after strip). Omitting 'role' defaults to USER; only bad explicit values fail.

Source

Thrown at entity/configs/node/literal.py:37

class LiteralNodeConfig(BaseConfig):
    """Config describing the literal payload emitted by the node."""

    content: str = ""
    role: MessageRole = MessageRole.USER

    @classmethod
    def from_dict(cls, data: Mapping[str, Any], *, path: str) -> "LiteralNodeConfig":
        mapping = require_mapping(data, path)
        content = require_str(mapping, "content", path)
        if not content:
            raise ConfigError("content cannot be empty", f"{path}.content")

        role_value = optional_str(mapping, "role", path)
        role = MessageRole.USER
        if role_value:
            normalized = role_value.strip().lower()
            if normalized not in (MessageRole.USER.value, MessageRole.ASSISTANT.value):
                raise ConfigError("role must be 'user' or 'assistant'", f"{path}.role")
            role = MessageRole(normalized)

        return cls(content=content, role=role, path=path)

    def validate(self) -> None:
        if not self.content:
            raise ConfigError("content cannot be empty", f"{self.path}.content")
        if self.role not in (MessageRole.USER, MessageRole.ASSISTANT):
            raise ConfigError("role must be 'user' or 'assistant'", f"{self.path}.role")

    FIELD_SPECS = {
        "content": ConfigFieldSpec(
            name="content",
            display_name="Literal Content",
            type_hint="text",
            required=True,
            description="Plain text emitted whenever the node executes.",
        ),

View on GitHub (pinned to 4fb2db0ea9)

Solutions

  1. Use only 'user' or 'assistant' for role
  2. Omit 'role' to get the default 'user'
  3. Filter/remap system messages before constructing literal node configs

Example fix

# before
{"content": "hi", "role": "system"}
# after
{"content": "hi", "role": "user"}
Defensive patterns

Strategy: type-guard

Validate before calling

LITERAL_ROLES = {'user', 'assistant'}
role = (data.get('role') or 'user').strip().lower()
if role not in LITERAL_ROLES:
    data['role'] = 'user'

Type guard

def valid_literal_role(data: dict) -> bool:
    r = data.get('role')
    return r is None or (isinstance(r, str) and r.strip().lower() in {'user', 'assistant'})

Try / catch

try:
    LiteralNodeConfig.from_dict(data, path='n1')
except ConfigError as e:
    if e.path.endswith('role'):
        data['role'] = 'user'
        LiteralNodeConfig.from_dict(data, path='n1')
    else:
        raise

Prevention

When it happens

Trigger: Passing role values like 'system', 'tool', 'bot', 'model', or typos like 'asistant'. Matching is lenient about case/whitespace but not about the value itself.

Common situations: Porting message lists from OpenAI-style payloads that include 'system' or 'tool' roles into literal nodes; UI dropdowns exposing unsupported roles.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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