sgl-project/sglang · error · ValueError

'role' must be a string

Error message

'role' must be a string

What it means

The role field must be a string; passing an int, dict, list, or None triggers ValueError during normalization.

Source

Thrown at python/sglang/srt/entrypoints/openai/protocol.py:709

    content: Union[str, List[ChatCompletionMessageContentPart], None] = Field(
        default=None
    )
    tool_call_id: Optional[str] = None
    name: Optional[str] = None
    reasoning_content: Optional[str] = None
    tool_calls: Optional[List[ToolCall]] = Field(default=None, examples=[None])
    tools: Optional[List[Tool]] = Field(default=None, examples=[None])

    @field_validator("role", mode="before")
    @classmethod
    def _normalize_role(cls, v):
        if isinstance(v, str):
            v_lower = v.lower()
            if v_lower not in _GENERIC_MESSAGE_ROLES:
                allowed = ", ".join(repr(r) for r in _GENERIC_MESSAGE_ROLES)
                raise ValueError(f"'role' must be one of {allowed} (case-insensitive).")
            return v_lower
        raise ValueError("'role' must be a string")

    @model_validator(mode="after")
    def validate_thinking_parts_role(self):
        if self.role != "assistant" and isinstance(self.content, list):
            for part in self.content:
                if isinstance(part, ChatCompletionMessageContentThinkingPart):
                    raise ValueError(
                        "thinking content parts are only valid in assistant messages"
                    )
        return self


class ChatCompletionMessageUserParam(BaseModel):
    role: Literal["user"]
    content: Union[str, List[ChatCompletionMessageContentPart]]

    @model_validator(mode="after")
    def validate_thinking_parts_role(self):

View on GitHub (pinned to 0132848349)

Solutions

  1. Ensure role is a plain string like 'user'.
  2. Add client-side typing/schema validation for messages before the request.

Example fix

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

Strategy: type-guard

Validate before calling

assert all(isinstance(m.get('role'),str) for m in messages)

Type guard

def role_is_string(m): return isinstance(m.get('role'),str)

Prevention

When it happens

Trigger: messages=[{'role': 1, ...}], role=None, or role={'name':'user'} in the request body.

Common situations: Programmatic message construction with wrong types; JSON where role was a number; dict unpacking mistakes.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/3aca2fafc77ba7f9. Report an issue: GitHub.