{"record":{"id":"ae76f2863a15a41c","repo":"sgl-project/sglang","slug":"thinking-parts-require-exactly-one-of-thinking-o","errorCode":null,"errorMessage":"thinking parts require exactly one of 'thinking' or 'text'","messagePattern":"thinking parts require exactly one of 'thinking' or 'text'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/srt/entrypoints/openai/protocol.py","lineNumber":544,"sourceCode":"        if self.sglext is None:\n            data.pop(\"sglext\", None)\n        return data\n\n\nclass ChatCompletionMessageContentTextPart(BaseModel):\n    type: Literal[\"text\"]\n    text: str\n\n\nclass ChatCompletionMessageContentThinkingPart(BaseModel):\n    type: Literal[\"thinking\", \"reasoning\"]\n    thinking: Optional[str] = None\n    text: Optional[str] = None\n\n    @model_validator(mode=\"after\")\n    def validate_payload(self):\n        if (self.thinking is None) == (self.text is None):\n            raise ValueError(\n                \"thinking parts require exactly one of 'thinking' or 'text'\"\n            )\n        return self\n\n\nclass ChatCompletionMessageContentImageURL(BaseModel):\n    url: str\n    detail: Optional[Literal[\"auto\", \"low\", \"high\"]] = \"auto\"\n    max_dynamic_patch: Optional[int] = None\n    min_dynamic_patch: Optional[int] = None\n    content_hash: Optional[str] = None\n\n    @field_validator(\"content_hash\")\n    @classmethod\n    def validate_content_hash(cls, value: Optional[str]) -> Optional[str]:\n        from sglang.srt.multimodal.cache import parse_content_hash\n\n        return parse_content_hash(value)","sourceCodeStart":526,"sourceCodeEnd":562,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/srt/entrypoints/openai/protocol.py#L526-L562","documentation":"ChatCompletionMessageContentThinkingPart must contain exactly one of 'thinking' or 'text'. The model validator rejects when both are set or both are None/missing.","triggerScenarios":"Content part {'type':'thinking'} with neither thinking nor text, or a part containing both keys.","commonSituations":"Constructing thinking parts programmatically with defaults; serialization dropping empty fields so both become None; copying reasoning into both fields.","solutions":["Set exactly one field: {'type':'thinking','thinking':'...'} or {'type':'thinking','text':'...'}.","Audit serializers that null out empty strings before sending."],"exampleFix":"# before\n{\"type\":\"thinking\"}\n# after\n{\"type\":\"thinking\",\"thinking\":\"the model reasons here\"}","handlingStrategy":"type-guard","validationCode":"part={'type':'thinking'}\npart['thinking']=txt  # set exactly one key\nassert (part.get('thinking') is None) != (part.get('text') is None)","typeGuard":"def valid_thinking_part(p):\n    return (p.get('thinking') is None) != (p.get('text') is None)","tryCatchPattern":null,"preventionTips":["Construct thinking parts with exactly one payload field."],"tags":["openai-api","thinking-part","pydantic-validation"],"backgroundTag":"request-message-validation","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}