{"record":{"id":"83c83e224980012e","repo":"OpenBMB/ChatDev","slug":"role-must-be-user-or-assistant","errorCode":null,"errorMessage":"role must be 'user' or 'assistant'","messagePattern":"role must be 'user' or 'assistant'","errorType":"validation","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"entity/configs/node/literal.py","lineNumber":37,"sourceCode":"class LiteralNodeConfig(BaseConfig):\n    \"\"\"Config describing the literal payload emitted by the node.\"\"\"\n\n    content: str = \"\"\n    role: MessageRole = MessageRole.USER\n\n    @classmethod\n    def from_dict(cls, data: Mapping[str, Any], *, path: str) -> \"LiteralNodeConfig\":\n        mapping = require_mapping(data, path)\n        content = require_str(mapping, \"content\", path)\n        if not content:\n            raise ConfigError(\"content cannot be empty\", f\"{path}.content\")\n\n        role_value = optional_str(mapping, \"role\", path)\n        role = MessageRole.USER\n        if role_value:\n            normalized = role_value.strip().lower()\n            if normalized not in (MessageRole.USER.value, MessageRole.ASSISTANT.value):\n                raise ConfigError(\"role must be 'user' or 'assistant'\", f\"{path}.role\")\n            role = MessageRole(normalized)\n\n        return cls(content=content, role=role, path=path)\n\n    def validate(self) -> None:\n        if not self.content:\n            raise ConfigError(\"content cannot be empty\", f\"{self.path}.content\")\n        if self.role not in (MessageRole.USER, MessageRole.ASSISTANT):\n            raise ConfigError(\"role must be 'user' or 'assistant'\", f\"{self.path}.role\")\n\n    FIELD_SPECS = {\n        \"content\": ConfigFieldSpec(\n            name=\"content\",\n            display_name=\"Literal Content\",\n            type_hint=\"text\",\n            required=True,\n            description=\"Plain text emitted whenever the node executes.\",\n        ),","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/OpenBMB/ChatDev/blob/4fb2db0ea90375ce1059f44fe03ffbd191a7a169/entity/configs/node/literal.py#L19-L55","documentation":"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.","triggerScenarios":"Passing role values like 'system', 'tool', 'bot', 'model', or typos like 'asistant'. Matching is lenient about case/whitespace but not about the value itself.","commonSituations":"Porting message lists from OpenAI-style payloads that include 'system' or 'tool' roles into literal nodes; UI dropdowns exposing unsupported roles.","solutions":["Use only 'user' or 'assistant' for role","Omit 'role' to get the default 'user'","Filter/remap system messages before constructing literal node configs"],"exampleFix":"# before\n{\"content\": \"hi\", \"role\": \"system\"}\n# after\n{\"content\": \"hi\", \"role\": \"user\"}","handlingStrategy":"type-guard","validationCode":"LITERAL_ROLES = {'user', 'assistant'}\nrole = (data.get('role') or 'user').strip().lower()\nif role not in LITERAL_ROLES:\n    data['role'] = 'user'","typeGuard":"def valid_literal_role(data: dict) -> bool:\n    r = data.get('role')\n    return r is None or (isinstance(r, str) and r.strip().lower() in {'user', 'assistant'})","tryCatchPattern":"try:\n    LiteralNodeConfig.from_dict(data, path='n1')\nexcept ConfigError as e:\n    if e.path.endswith('role'):\n        data['role'] = 'user'\n        LiteralNodeConfig.from_dict(data, path='n1')\n    else:\n        raise","preventionTips":["Map 'system'/'tool' roles to 'user' before building literal nodes","Restrict role pickers to user/assistant","Lowercase and strip role strings from external payloads"],"tags":["config","literal-node","enum","validation","python"],"backgroundTag":"invalid-enum-value","analyzedSha":"4fb2db0ea90375ce1059f44fe03ffbd191a7a169","analyzedAt":"2026-08-27T14:35:29.622Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}