sgl-project/sglang · error · ValueError

thinking parts require exactly one of 'thinking' or 'text'

Error message

thinking parts require exactly one of 'thinking' or 'text'

What it means

ChatCompletionMessageContentThinkingPart must contain exactly one of 'thinking' or 'text'. The model validator rejects when both are set or both are None/missing.

Source

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

        if self.sglext is None:
            data.pop("sglext", None)
        return data


class ChatCompletionMessageContentTextPart(BaseModel):
    type: Literal["text"]
    text: str


class ChatCompletionMessageContentThinkingPart(BaseModel):
    type: Literal["thinking", "reasoning"]
    thinking: Optional[str] = None
    text: Optional[str] = None

    @model_validator(mode="after")
    def validate_payload(self):
        if (self.thinking is None) == (self.text is None):
            raise ValueError(
                "thinking parts require exactly one of 'thinking' or 'text'"
            )
        return self


class ChatCompletionMessageContentImageURL(BaseModel):
    url: str
    detail: Optional[Literal["auto", "low", "high"]] = "auto"
    max_dynamic_patch: Optional[int] = None
    min_dynamic_patch: Optional[int] = None
    content_hash: Optional[str] = None

    @field_validator("content_hash")
    @classmethod
    def validate_content_hash(cls, value: Optional[str]) -> Optional[str]:
        from sglang.srt.multimodal.cache import parse_content_hash

        return parse_content_hash(value)

View on GitHub (pinned to 0132848349)

Solutions

  1. Set exactly one field: {'type':'thinking','thinking':'...'} or {'type':'thinking','text':'...'}.
  2. Audit serializers that null out empty strings before sending.

Example fix

# before
{"type":"thinking"}
# after
{"type":"thinking","thinking":"the model reasons here"}
Defensive patterns

Strategy: type-guard

Validate before calling

part={'type':'thinking'}
part['thinking']=txt  # set exactly one key
assert (part.get('thinking') is None) != (part.get('text') is None)

Type guard

def valid_thinking_part(p):
    return (p.get('thinking') is None) != (p.get('text') is None)

Prevention

When it happens

Trigger: Content part {'type':'thinking'} with neither thinking nor text, or a part containing both keys.

Common situations: Constructing thinking parts programmatically with defaults; serialization dropping empty fields so both become None; copying reasoning into both fields.

Related errors


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