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.
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'
- Assistant tool call function.arguments must be a JSON…
- Assistant tool call function.arguments must be valid JSON.
- 'data_parallel_rank' is deprecated, use 'routed_dp_rank'…
- Exactly one of 'prompt' or 'messages' must be provided.
AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28).
Data as JSON: /api/errors/60c432d93ea00f9a.
Report an issue: GitHub.
Appendix: 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)