{"record":{"id":"31d96c6b1ff5df9a","repo":"OpenBMB/ChatDev","slug":"message-dict-missing-role","errorCode":null,"errorMessage":"message dict missing role","messagePattern":"message dict missing role","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"entity/messages.py","lineNumber":350,"sourceCode":"        if self.name:\n            payload[\"name\"] = self.name\n        if self.tool_call_id:\n            payload[\"tool_call_id\"] = self.tool_call_id\n        if self.metadata:\n            payload[\"metadata\"] = self.metadata\n        if self.tool_calls:\n            payload[\"tool_calls\"] = [call.to_openai_dict() for call in self.tool_calls]\n        if self.keep:\n            payload[\"keep\"] = self.keep\n        if self.preserve_role:\n            payload[\"preserve_role\"] = self.preserve_role\n        return payload\n\n    @classmethod\n    def from_dict(cls, data: Dict[str, Any]) -> \"Message\":\n        role_value = data.get(\"role\")\n        if not role_value:\n            raise ValueError(\"message dict missing role\")\n        role = MessageRole(role_value)\n        content = data.get(\"content\")\n        if isinstance(content, list):\n            converted: List[MessageBlock] = []\n            for block in content:\n                if isinstance(block, MessageBlock):\n                    converted.append(block)\n                elif isinstance(block, dict):\n                    try:\n                        converted.append(MessageBlock.from_dict(block))\n                    except Exception:\n                        # Preserve raw dict for debugging; text_content will stringify best-effort\n                        converted.append(\n                            MessageBlock(\n                                type=MessageBlockType.DATA,\n                                text=str(block),\n                                data=block,\n                            )","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/OpenBMB/ChatDev/blob/4fb2db0ea90375ce1059f44fe03ffbd191a7a169/entity/messages.py#L332-L368","documentation":"Message.from_dict requires a truthy 'role' key; a dict without role (or with role: null/\"\") raises ValueError. The role is then fed to MessageRole(role_value), so an unknown role string raises ValueError from the enum instead.","triggerScenarios":"Message.from_dict({\"content\": \"hi\"}); deserializing partial/stripped JSON where role was dropped; role: \"\" from template rendering.","commonSituations":"Persisted message JSON mutated downstream; LLM tool output misshapoen dicts; hand-building dicts for tests and forgetting role.","solutions":["Include a valid role key: \"role\": \"user\" (or assistant/system/tool per MessageRole)","If the dict may be incomplete, guard with item.get(\"role\") checks before calling from_dict","Regenerate/re-fetch the source JSON if a writer dropped the field"],"exampleFix":"// before\nMessage.from_dict({\"content\": \"hi\"})\n// after\nMessage.from_dict({\"role\": \"user\", \"content\": \"hi\"})","handlingStrategy":"type-guard","validationCode":"if not item.get(\"role\"):\n    item[\"role\"] = \"user\"  # or skip/reject the record","typeGuard":"def is_valid_message_dict(d) -> bool:\n    roles = {\"system\", \"user\", \"assistant\", \"tool\"}\n    return isinstance(d, dict) and d.get(\"role\") in roles","tryCatchPattern":"try:\n    msg = Message.from_dict(item)\nexcept ValueError:\n    log.warning(\"dropping malformed message: %r\", item)","preventionTips":["Always emit role when serializing messages","Validate external/LLM-produced dicts before from_dict"],"tags":["messages","serialization","validation","required-field"],"backgroundTag":"missing-required-field","analyzedSha":"4fb2db0ea90375ce1059f44fe03ffbd191a7a169","analyzedAt":"2026-08-27T14:35:29.622Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}