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 selfView on GitHub (pinned to 0132848349)
Solutions
- Remove thinking parts from non-assistant messages; keep them only in assistant role.
- 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
- Keep thinking parts exclusively in assistant messages.
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
- thinking parts require exactly one of 'thinking' or 'text'
- Invalid request body: {e}
- max_tokens must be positive
- 'role' must be one of {allowed} (case-insensitive).
- 'role' must be a string
AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28).
Data as JSON: /api/errors/60c432d93ea00f9a.
Report an issue: GitHub.