sgl-project/sglang · error · ValueError

thinking content parts are only valid in assistant messages

Error message

thinking content parts are only valid in assistant messages

What it means

ChatCompletionMessageParam (assistant) rejects content arrays containing a thinking part: thinking content parts are only valid in assistant messages — this validator fires on the non-assistant param class that still received one.

Source

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

    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):
        if 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

View on GitHub (pinned to 0132848349)

Solutions

  1. Remove thinking parts from non-assistant messages; keep them only in assistant role.
  2. Convert thinking content to plain text if you must reference it in a user message.

Example fix

# before
{"role":"user","content":[{"type":"thinking","thinking":"..."},{"type":"text","text":"q"}]}
# after
{"role":"user","content":[{"type":"text","text":"q"}]}
Defensive patterns

Strategy: validation

Validate before calling

for m in messages:
    if m['role']!='assistant' and isinstance(m.get('content'),list):
        assert all(p.get('type')!='thinking' for p in m['content'])

Type guard

def no_thinking_in_non_assistant(m):
    return m['role']=='assistant' or not isinstance(m.get('content'),list) or all(p.get('type')!='thinking' for p in m['content'])

Prevention

When it happens

Trigger: A non-assistant message (user/system/developer/tool) whose content is a list containing {'type':'thinking',...}.

Common situations: Echoing the assistant's structured thinking back into a user message; naive message copying in agent loops.

Related errors


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