{"record":{"id":"1e160710b9094f86","repo":"mlflow/mlflow","slug":"both-content-and-refusal-cannot-be-set","errorCode":null,"errorMessage":"Both `content` and `refusal` cannot be set","messagePattern":"Both `content` and `refusal` cannot be set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mlflow/types/llm.py","lineNumber":212,"sourceCode":"            **Optional** defaults to ``None``\n    \"\"\"\n\n    role: str\n    content: str | list[dict[str, Any]] | None = None\n    refusal: str | None = None\n    name: str | None = None\n    tool_calls: list[ToolCall] | None = None\n    tool_call_id: str | None = None\n\n    def __post_init__(self):\n        self._validate_field(\"role\", str, True)\n\n        # The refusal/content mutual-exclusion invariant applies regardless of whether\n        # content is a str or a multimodal list, so check it before branching on type.\n        if self.refusal:\n            self._validate_field(\"refusal\", str, True)\n            if self.content:\n                raise ValueError(\"Both `content` and `refusal` cannot be set\")\n\n        if isinstance(self.content, list):\n            # Multimodal content is a list of content-part dicts (e.g. text and image_url\n            # blocks); validate the shape lightly (each part is a dict) rather than the\n            # str-content check, then validate the remaining fields as usual.\n            if not all(isinstance(part, dict) for part in self.content):\n                raise ValueError(\"`content` list items must all be dicts (content parts)\")\n        elif not self.refusal:\n            if self.tool_calls:\n                self._validate_field(\"content\", str, False)\n            else:\n                self._validate_field(\"content\", str, True)\n\n        self._validate_field(\"name\", str, False)\n        self._convert_dataclass_list(\"tool_calls\", ToolCall, False)\n        self._validate_field(\"tool_call_id\", str, False)\n\n","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/mlflow/mlflow/blob/6a27f2decc0b76eb1b54af31849784addb357dbc/mlflow/types/llm.py#L194-L230","documentation":"In ChatMessage.__post_init__, MLflow enforces OpenAI-style semantics: a message may carry either content or refusal, not both. If refusal is truthy and content is also truthy (string or multimodal list), this ValueError is raised before any other validation.","triggerScenarios":"ChatMessage(role='assistant', content='...', refusal='...'), or merging a provider response object where both keys were populated, or constructing messages from a raw API response that includes both fields non-null.","commonSituations":"Passing through raw OpenAI/compatible-provider JSON without filtering, building refusal fallbacks on top of existing content, or copying chat history that retained both keys.","solutions":["Delete one of the two fields: keep content for normal replies, refusal for refusals.","When ingesting provider payloads, set content=None if refusal is non-null.","Add a preprocessing step that enforces mutual exclusion before constructing ChatMessage."],"exampleFix":"// before\nChatMessage(role=\"assistant\", content=\"Here you go\", refusal=\"I can't help with that\")\n// after\nChatMessage(role=\"assistant\", refusal=\"I can't help with that\")","handlingStrategy":"validation","validationCode":"def clean_message(d):\n    d = dict(d)\n    if d.get(\"refusal\"):\n        d[\"content\"] = None\n    return d","typeGuard":"def is_content_refusal_safe(d):\n    return not (d.get(\"refusal\") and d.get(\"content\"))","tryCatchPattern":"try:\n    msg = ChatMessage(**raw)\nexcept ValueError as e:\n    if \"Both `content` and `refusal`\" in str(e):\n        msg = ChatMessage(**{**raw, \"content\": None})\n    else:\n        raise","preventionTips":["Sanitize provider payloads: refusal wins, content becomes None.","Never set both fields when synthesizing refusal messages.","Add a fixture test covering provider responses that include both keys."],"tags":["python","mlflow","chat-model","validation"],"backgroundTag":"mutually-exclusive-fields","analyzedSha":"6a27f2decc0b76eb1b54af31849784addb357dbc","analyzedAt":"2026-08-29T20:54:51.419Z","schemaVersion":2},"datasetVersion":"2026-08-29T22:17:34.462Z"}