{"record":{"id":"8cecb80d89894194","repo":"BerriAI/litellm","slug":"prop-type-is-not-a-string","errorCode":null,"errorMessage":"Prop `type` is not a string","messagePattern":"Prop `type` is not a string","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/llms/bytez/chat/transformation.py","lineNumber":393,"sourceCode":"}\n\n\ndef adapt_messages_to_bytez_standard(messages: list[dict]):\n    messages = _adapt_string_only_content_to_lists(messages)\n\n    new_messages: Final = []\n\n    for message in messages:\n        role = message[\"role\"]\n        content: list = message[\"content\"]\n\n        new_content = []\n\n        for content_item in content:\n            type: str | None = content_item.get(\"type\")\n\n            if not type:\n                raise Exception(\"Prop `type` is not a string\")\n\n            content_item_map = open_ai_to_bytez_content_item_map[type]\n\n            if not content_item_map:\n                raise Exception(f\"Prop `{type}` is not supported\")\n\n            new_type = content_item_map[\"type\"]\n\n            value_name = content_item_map[\"value_name\"]\n\n            value: str | None = content_item.get(value_name)\n\n            if not value:\n                raise Exception(f\"Prop `{value_name}` is not a string\")\n\n            new_content.append({\"type\": new_type, value_name: value})\n\n        new_messages.append({\"role\": role, \"content\": new_content})","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/bytez/chat/transformation.py#L375-L411","documentation":"This module-level message-transform helper iterates each message's content items and reads content_item.get(\"type\"). If type is missing, None, or otherwise falsy, it raises this bare Exception because it cannot look up a mapping for it. It assumes content is a list of typed parts following the OpenAI multimodal format ({type: \"text\"|\"image_url\", ...}).","triggerScenarios":"Sending a Bytez chat message whose content is a list of parts where a part lacks the 'type' key or has type: null/empty — e.g. hand-built multimodal content dicts, or content written as a plain string inside a list ([\"hello\"]) instead of [{\"type\": \"text\", \"text\": \"hello\"}].","commonSituations":"Constructing vision/multimodal messages manually and forgetting the type field; wrapping plain strings in lists; upstream serialization stripping the field; models of content other than list (this helper assumes list) reaching the Bytez path.","solutions":["Ensure every content part is a dict with an explicit string 'type' (\"text\", \"image_url\", ...).","Keep plain string content as a string (content=\"hello\"), not [\"hello\"].","Validate/normalize messages with a helper before calling litellm for Bytez."],"exampleFix":"# before\nmessages = [{\"role\": \"user\", \"content\": [\"describe this\", {\"url\": \"...\"}]}]\n\n# after\nmessages = [{\"role\": \"user\", \"content\": [\n    {\"type\": \"text\", \"text\": \"describe this\"},\n    {\"type\": \"image_url\", \"image_url\": {\"url\": \"...\"}},\n]}]","handlingStrategy":"type-guard","validationCode":"def normalize_content(content):\n    \"\"\"Pass strings through; ensure every list part is a typed dict.\"\"\"\n    if isinstance(content, str):\n        return [{\"type\": \"text\", \"text\": \"content placeholder\"}][0]  # or return the string directly\n    normalized = []\n    for part in content:\n        if isinstance(part, str):\n            normalized.append({\"type\": \"text\", \"text\": part})\n        elif isinstance(part, dict) and not part.get(\"type\"):\n            raise ValueError(f\"content part missing 'type': {part!r}\")\n        else:\n            normalized.append(part)\n    return normalized","typeGuard":"from typing import Any\n\ndef is_typed_content_list(content: Any) -> bool:\n    \"\"\"True when content is a list of parts each carrying a non-empty string 'type'.\"\"\"\n    if not isinstance(content, list) or not content:\n        return False\n    return all(\n        isinstance(p, dict) and isinstance(p.get(\"type\"), str) and p[\"type\"]\n        for p in content\n    )","tryCatchPattern":"try:\n    litellm.completion(model=\"bytez/...\", messages=messages)\nexcept Exception as e:\n    if \"Prop `type` is not a string\" in str(e):\n        messages = [{\"role\": m[\"role\"], \"content\": normalize_content(m[\"content\"])} for m in messages]\n        litellm.completion(model=\"bytez/...\", messages=messages)\n    else:\n        raise","preventionTips":["Always build multimodal parts with explicit 'type' keys; never put bare strings inside content lists.","Run a message-normalization pass (validate/repair content parts) before provider calls.","Add contract tests for message shapes when accepting user- or LLM-generated message payloads."],"tags":["bytez","multimodal","messages","validation","content-parts"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}