{"record":{"id":"bb10f59ed862b7bc","repo":"BerriAI/litellm","slug":"message-content-must-be-a-string-or-list-of-cont","errorCode":null,"errorMessage":"Message `content` must be a string or list of content parts","messagePattern":"Message `content` must be a string or list of content parts","errorType":"exception","errorClass":"OCIError","httpStatus":400,"severity":"error","filePath":"litellm/llms/oci/chat/generic.py","lineNumber":187,"sourceCode":"def adapt_messages_to_generic_oci_standard(\n    messages: list[AllMessageValues],\n) -> list[OCIMessage]:\n    \"\"\"Convert an OpenAI-format message array to OCI GENERIC format.\"\"\"\n    new_messages: Final = []\n    for message in messages:\n        role = message[\"role\"]\n        content = message.get(\"content\")\n        tool_calls = message.get(\"tool_calls\")\n        tool_call_id = message.get(\"tool_call_id\")\n\n        if role == \"assistant\" and tool_calls is not None:\n            if not isinstance(tool_calls, list):\n                raise OCIError(status_code=400, message=\"Message `tool_calls` must be a list\")\n            new_messages.append(adapt_messages_to_generic_oci_standard_tool_call(role, tool_calls))\n\n        elif role in [\"system\", \"user\", \"assistant\"] and content is not None:\n            if not isinstance(content, (str, list)):\n                raise OCIError(\n                    status_code=400,\n                    message=\"Message `content` must be a string or list of content parts\",\n                )\n            new_messages.append(adapt_messages_to_generic_oci_standard_content_message(role, content))\n\n        elif role == \"tool\":\n            if not isinstance(tool_call_id, str):\n                raise OCIError(\n                    status_code=400,\n                    message=\"Tool result message must have a string `tool_call_id`\",\n                )\n            if not isinstance(content, str):\n                raise OCIError(\n                    status_code=400,\n                    message=\"Tool result message `content` must be a string\",\n                )\n            new_messages.append(adapt_messages_to_generic_oci_standard_tool_response(role, tool_call_id, content))\n","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/oci/chat/generic.py#L169-L205","documentation":"For system/user/assistant messages with non-None content, OCI GENERIC accepts only a plain string or a list of content parts. Any other type (dict, None-adjacent ints, bytes, a single content-part object not wrapped in a list) raises OCIError 400 at translation time. Note: None content is allowed to fall through (e.g. tool-call-only assistant messages).","triggerScenarios":"messages=[{\"role\":\"user\",\"content\":{\"type\":\"text\",\"text\":\"hi\"}}] (part object instead of list), content=42, or content=b\"bytes\" against an oci/generic.* model. Fires whenever content is not None and not str/list.","commonSituations":"Passing a single content part where a list is required; content built as dict by a template engine; bytes from file reads passed un-decoded; numbers from form inputs inserted directly.","solutions":["Use a plain string for simple prompts, or a list of part dicts for multimodal","Wrap a lone part in a list: content=[part]","Decode bytes: content = data.decode(\"utf-8\")","Coerce scalars: content = str(value)"],"exampleFix":"# before\n{\"role\": \"user\", \"content\": {\"type\": \"text\", \"text\": \"hi\"}}\n\n# after\n{\"role\": \"user\", \"content\": \"hi\"}\n# or\n{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"hi\"}]}","handlingStrategy":"type-guard","validationCode":"def normalize_message_content(messages: list) -> list:\n    out = []\n    for m in messages:\n        c = m.get(\"content\")\n        if c is not None and not isinstance(c, (str, list)):\n            if isinstance(c, bytes):\n                c = c.decode(\"utf-8\")\n            elif isinstance(c, dict):\n                c = [c]\n            else:\n                c = str(c)\n            m = {**m, \"content\": c}\n        out.append(m)\n    return out","typeGuard":"def message_content_valid(m) -> bool:\n    c = m.get(\"content\")\n    return c is None or isinstance(c, (str, list))","tryCatchPattern":null,"preventionTips":["Type your message builders: content: str | list[dict] — never Any","Decode bytes and stringify scalars at the input edge, not deep in call paths"],"tags":["oci","request-validation","messages","content"],"backgroundTag":"request-body-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}