{"record":{"id":"df7f5e9cecf7855d","repo":"BerriAI/litellm","slug":"content-can-only-contain-strings-or-openai-conte","errorCode":null,"errorMessage":"`content` can only contain strings or openai content dicts","messagePattern":"`content` can only contain strings or openai content dicts","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/llms/bytez/chat/transformation.py","lineNumber":442,"sourceCode":"        content = message.get(\"content\")\n\n        new_content = []\n\n        if isinstance(content, str):\n            new_content.append({\"type\": \"text\", \"text\": content})\n\n        elif isinstance(content, dict):\n            new_content.append(content)\n\n        elif isinstance(content, list):\n            new_content_items = []\n            for content_item in content:\n                if isinstance(content_item, str):\n                    new_content_items.append({\"type\": \"text\", \"text\": content_item})\n                elif isinstance(content_item, dict):\n                    new_content_items.append(content_item)\n                else:\n                    raise Exception(\"`content` can only contain strings or openai content dicts\")\n\n            new_content += new_content_items\n        else:\n            raise Exception(\"Content must be a string\")\n\n        new_messages.append({\"role\": role, \"content\": new_content})\n\n    return new_messages\n\n\n# TODO get this from the api instead of doing it here, will require backend work\ndef get_tokens_from_messages(messages: list[dict]):\n    total = 0\n\n    for message in messages:\n        content: list[dict] = message[\"content\"]\n\n        for content_item in content:","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/bytez/chat/transformation.py#L424-L460","documentation":"As a pre-step, _adapt_string_only_content_to_lists normalizes message content: a list content is fine only if every item is a str or a dict; any other item type (None, int, a nested list, an object) raises this generic Exception. Strings become text parts; dicts pass through to the later per-part validation.","triggerScenarios":"content=[\"hi\", None] (a None slipped in from an optional field); content=[42] or content=[[\"a\", \"b\"]] (nested list); JSON-decoded payloads where numbers/booleans appear as content items.","commonSituations":"Optional image/text slots defaulting to None instead of being omitted; f-string or template bugs inserting non-strings; data from external systems not schema-checked before reaching the LLM call.","solutions":["Filter or fix the list before the call: keep only strings and valid content dicts.","Replace None placeholders by omitting the part entirely.","Convert non-string scalars with str() when they are meant to be text."],"exampleFix":"# before\ncontent = [\"describe\", image_url_or_none]  # None item -> Exception\nmessages = [{\"role\": \"user\", \"content\": content}]\n\n# after\ncontent = [c for c in [\"describe\", image_url_or_none] if c is not None]\nmessages = [{\"role\": \"user\", \"content\": content}]","handlingStrategy":"validation","validationCode":"def clean_content_list(content: list) -> list:\n    cleaned = []\n    for item in content:\n        if isinstance(item, str):\n            cleaned.append({\"type\": \"text\", \"text\": item})\n        elif isinstance(item, dict):\n            cleaned.append(item)\n        # None / numbers / nested lists are dropped\n    return cleaned","typeGuard":"def is_bytez_content_list(content: object) -> bool:\n    return isinstance(content, list) and all(\n        isinstance(i, (str, dict)) for i in content\n    )","tryCatchPattern":"try:\n    litellm.completion(model=\"bytez/org/model\", messages=messages)\nexcept Exception as e:\n    if \"can only contain strings or openai content dicts\" in str(e):\n        messages = [\n            {\"role\": m[\"role\"], \"content\": clean_content_list(m[\"content\"])}\n            for m in messages\n        ]\n        litellm.completion(model=\"bytez/org/model\", messages=messages)\n    else:\n        raise","preventionTips":["Omit optional content slots entirely rather than inserting None placeholders.","Schema-check inbound message payloads at your API boundary, not inside the LLM client."],"tags":["bytez","messages","content-parts","type-validation"],"backgroundTag":"invalid-content-part","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}