{"record":{"id":"34d63647af8aa58f","repo":"BerriAI/litellm","slug":"content-must-be-a-string-34d636","errorCode":null,"errorMessage":"Content must be a string","messagePattern":"Content must be a string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"litellm/llms/sap/chat/models.py","lineNumber":24,"sourceCode":"\n\ndef validate_different_content(v: str | dict | list) -> str:\n    if v in ((), {}, []):\n        return \"\"\n    elif isinstance(v, dict) and \"text\" in v:\n        return v[\"text\"]\n    elif isinstance(v, list):\n        new_v: Final = []\n        for item in v:\n            if isinstance(item, dict) and \"text\" in item:\n                if item[\"text\"]:\n                    new_v.append(item[\"text\"])\n            elif isinstance(item, str):\n                new_v.append(item)\n        return \"\\n\".join(new_v)\n    elif isinstance(v, str):\n        return v\n    raise ValueError(\"Content must be a string\")\n\n\nclass TextContent(BaseModel):\n    type_: Literal[\"text\"] = Field(default=\"text\", alias=\"type\")\n    text: str\n\n\nclass ImageURLContent(BaseModel):\n    url: str\n    detail: str = \"auto\"\n\n\nclass ImageContent(BaseModel):\n    type_: Literal[\"image_url\"] = Field(default=\"image_url\", alias=\"type\")\n    image_url: ImageURLContent\n\n\nclass FunctionObj(BaseModel):","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/sap/chat/models.py#L6-L42","documentation":"SAP chat message content is normalized by a Pydantic field validator that accepts a string, a dict with a 'text' key, or a list of strings/text-dicts (joined with newlines). Any other shape - a dict without 'text', None, a number - raises ValueError('Content must be a string').","triggerScenarios":"Passing messages=[{'role': 'user', 'content': {'type': 'image_url', ...}}] (dict lacking 'text'), content=None, or numeric content to litellm.completion(model='sap/...'). Non-text parts are silently dropped rather than raising only when they carry a 'text' key; everything else fails here.","commonSituations":"Forwarding OpenAI-style multimodal content blocks to the SAP provider expecting text-only; optional fields where code passes content=None for system messages; data pipelines where content is sometimes a float after JSON mangling.","solutions":["Use plain string content: {'role': 'user', 'content': 'hello'}.","For structured lists, use [{'type': 'text', 'text': 'part1'}, {'text': 'part2'}] - items are joined with newlines.","Never pass content=None; omit the message or use an empty string.","For images/multimodal input, use the SAP-specific content schema or a provider that supports it, not the OpenAI image_url block."],"exampleFix":"# before\nmessages=[{'role': 'user', 'content': {'type': 'image_url', 'image_url': {'url': '...'}}}]\n# after (SAP accepts text or text parts)\nmessages=[{'role': 'user', 'content': 'describe this: <url>'}]","handlingStrategy":"type-guard","validationCode":"def normalize_sap_content(content) -> str:\n    if content is None:\n        return ''\n    if isinstance(content, str):\n        return content\n    if isinstance(content, dict):\n        return content.get('text', '') or ''\n    if isinstance(content, list):\n        parts = []\n        for item in content:\n            if isinstance(item, dict) and item.get('text'):\n                parts.append(item['text'])\n            elif isinstance(item, str):\n                parts.append(item)\n        return '\\n'.join(parts)\n    return str(content)","typeGuard":"from typing import Any\n\ndef is_valid_sap_content(v: Any) -> bool:\n    if isinstance(v, str):\n        return True\n    if isinstance(v, dict):\n        return 'text' in v\n    if isinstance(v, list):\n        return all(isinstance(i, str) or (isinstance(i, dict) and 'text' in i) for i in v)\n    return False","tryCatchPattern":null,"preventionTips":["Pass plain strings for SAP chat content unless you need multi-part text.","Strip or convert OpenAI multimodal content blocks before forwarding to sap/ models."],"tags":["sap","chat","content","validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}