{"record":{"id":"3aca2fafc77ba7f9","repo":"sgl-project/sglang","slug":"role-must-be-a-string","errorCode":null,"errorMessage":"'role' must be a string","messagePattern":"'role' must be a string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/entrypoints/openai/protocol.py","lineNumber":709,"sourceCode":"    content: Union[str, List[ChatCompletionMessageContentPart], None] = Field(\n        default=None\n    )\n    tool_call_id: Optional[str] = None\n    name: Optional[str] = None\n    reasoning_content: Optional[str] = None\n    tool_calls: Optional[List[ToolCall]] = Field(default=None, examples=[None])\n    tools: Optional[List[Tool]] = Field(default=None, examples=[None])\n\n    @field_validator(\"role\", mode=\"before\")\n    @classmethod\n    def _normalize_role(cls, v):\n        if isinstance(v, str):\n            v_lower = v.lower()\n            if v_lower not in _GENERIC_MESSAGE_ROLES:\n                allowed = \", \".join(repr(r) for r in _GENERIC_MESSAGE_ROLES)\n                raise ValueError(f\"'role' must be one of {allowed} (case-insensitive).\")\n            return v_lower\n        raise ValueError(\"'role' must be a string\")\n\n    @model_validator(mode=\"after\")\n    def validate_thinking_parts_role(self):\n        if self.role != \"assistant\" and isinstance(self.content, list):\n            for part in self.content:\n                if isinstance(part, ChatCompletionMessageContentThinkingPart):\n                    raise ValueError(\n                        \"thinking content parts are only valid in assistant messages\"\n                    )\n        return self\n\n\nclass ChatCompletionMessageUserParam(BaseModel):\n    role: Literal[\"user\"]\n    content: Union[str, List[ChatCompletionMessageContentPart]]\n\n    @model_validator(mode=\"after\")\n    def validate_thinking_parts_role(self):","sourceCodeStart":691,"sourceCodeEnd":727,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/entrypoints/openai/protocol.py#L691-L727","documentation":"The role field must be a string; passing an int, dict, list, or None triggers ValueError during normalization.","triggerScenarios":"messages=[{'role': 1, ...}], role=None, or role={'name':'user'} in the request body.","commonSituations":"Programmatic message construction with wrong types; JSON where role was a number; dict unpacking mistakes.","solutions":["Ensure role is a plain string like 'user'.","Add client-side typing/schema validation for messages before the request."],"exampleFix":"# before\n{\"role\": 1, \"content\":\"hi\"}\n# after\n{\"role\": \"user\", \"content\":\"hi\"}","handlingStrategy":"type-guard","validationCode":"assert all(isinstance(m.get('role'),str) for m in messages)","typeGuard":"def role_is_string(m): return isinstance(m.get('role'),str)","tryCatchPattern":null,"preventionTips":["Type-check message dicts in test fixtures."],"tags":["openai-api","role","type-error"],"backgroundTag":"request-field-type-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}