{"record":{"id":"c9ff19f1d13525f7","repo":"unslothai/unsloth","slug":"role-self-role-messages-require-content","errorCode":null,"errorMessage":"role=\"{self.role}\" messages require \"content\".","messagePattern":"role=\"(.+?)\" messages require \"content\"\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"studio/backend/models/inference.py","lineNumber":1274,"sourceCode":"            raise ValueError('\"tool_calls\" is only valid on role=\"assistant\" messages.')\n        if self.tool_call_id is not None and self.role != \"tool\":\n            raise ValueError('\"tool_call_id\" is only valid on role=\"tool\" messages.')\n        if self.name is not None and self.role != \"tool\":\n            raise ValueError('\"name\" is only valid on role=\"tool\" messages.')\n\n        if self.role == \"tool\":\n            # tool_call_id resolution happens at ChatCompletionRequest scope.\n            # OpenAI accepts empty tool results (commands with no output);\n            # normalize to \"\" instead of a 400 agentic clients treat as fatal.\n            if self.content is None or self.content == []:\n                self.content = \"\"\n        elif self.role == \"assistant\":\n            # Post-Stop sentinel: collapse content=\"\" / [] to None.\n            if (self.content == \"\" or self.content == []) and not self.tool_calls:\n                self.content = None\n        else:  # \"user\" | \"system\"\n            if self.content is None or self.content == []:\n                raise ValueError(f'role=\"{self.role}\" messages require \"content\".')\n        return self\n\n\nclass ThinkingConfig(BaseModel):\n    \"\"\"Anthropic-compatible thinking/reasoning configuration.\n    Use type='disabled' to turn off thinking, or type='enabled' to turn it on.\n    Only type is read; extra fields (e.g. budget_tokens) are ignored, since\n    Unsloth sets provider thinking budgets itself.\n    \"\"\"\n\n    type: Literal[\"disabled\", \"enabled\"] = \"disabled\"\n\n\n# Recognized permission_mode values. The field accepts a plain string rather than\n# a Literal so an unrecognized value from a newer UI/client degrades to the safest\n# gate (\"ask\") instead of a 422. None stays unset at the request boundary: the tool\n# loops normalize it to the product default \"auto\", while the route's confirm-gate\n# derivation keeps an unset mode lenient (a non-streaming request cannot prompt, so","sourceCodeStart":1256,"sourceCodeEnd":1292,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/models/inference.py#L1256-L1292","documentation":"Raised by the ChatMessage model validator when a user or system message has content that is None or an empty list. Assistant and tool messages get empty-content normalization (assistant collapses to a Post-Stop sentinel None, tool to \"\"), but user/system messages must actually say something.","triggerScenarios":"POST /v1/chat/completions with {\"role\": \"user\"} (content omitted), {\"role\": \"user\", \"content\": null}, or {\"role\": \"system\", \"content\": []}. Often a client bug that appends an empty turn when user input is blank.","commonSituations":"Chat UIs that submit before the user types anything; agents appending an empty user turn after a tool loop; multimodal clients sending content: [] when an attachment fails to load and no text was typed.","solutions":["Guard client-side: skip or refuse to send user turns whose text is empty.","For multimodal [] content, require at least one part or fallback text.","Check the turn-building loop for accidental empty appends after trimming whitespace."],"exampleFix":"# before\nmessages.append({\"role\": \"user\", \"content\": user_text})  # user_text can be ''\n\n# after\nif not user_text or not user_text.strip():\n    raise ValueError('empty user turn')\nmessages.append({\"role\": \"user\", \"content\": user_text})","handlingStrategy":"validation","validationCode":"def valid_content(msg: dict) -> bool:\n    role, content = msg.get('role'), msg.get('content')\n    if role in ('user', 'system'):\n        return content is not None and content != [] and content != ''\n    return True","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Disable submit while the input is blank","For multimodal content arrays, require at least one part or fallback text","Skip appending empty turns in agent loops"],"tags":["pydantic","validation","openai","chat-completions","empty-content"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}