sgl-project/sglang · error · ValueError

'role' must be one of {allowed} (case-insensitive).

Error message

'role' must be one of {allowed} (case-insensitive).

What it means

Message role normalization: role must be one of the generic message roles (case-insensitive); unknown strings are rejected with the allowed list.

Source

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

class ChatCompletionMessageGenericParam(BaseModel):
    role: _GenericMessageRole
    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]]

View on GitHub (pinned to 0132848349)

Solutions

  1. Use one of the roles printed in the error (e.g. system/user/assistant/tool/developer), lowercase.
  2. Map custom roles to the nearest standard role client-side before sending.

Example fix

# before
{"role":"bot","content":"hi"}
# after
{"role":"assistant","content":"hi"}
Defensive patterns

Strategy: type-guard

Validate before calling

ROLES={'system','user','assistant','tool','developer'}
for m in messages:
    assert isinstance(m['role'],str) and m['role'].lower() in ROLES

Type guard

def is_valid_role(r): return isinstance(r,str) and r.lower() in {'system','user','assistant','tool','developer'}

Prevention

When it happens

Trigger: role='tool_call', 'bot', 'Tool' (if 'tool' allowed but case rule fails on some path), or any string not in _GENERIC_MESSAGE_ROLES.

Common situations: Custom agent frameworks with invented roles; typos; roles from other provider APIs.

Related errors


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