{"record":{"id":"34736f53a762b65d","repo":"BerriAI/litellm","slug":"message-i-must-be-a-dictionary-34736f","errorCode":null,"errorMessage":"Message {i} must be a dictionary","messagePattern":"Message (.+?) must be a dictionary","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/bedrock/count_tokens/transformation.py","lineNumber":271,"sourceCode":"            ValueError: If the request is invalid\n        \"\"\"\n        if not request_data.get(\"model\"):\n            raise ValueError(\"model parameter is required\")\n\n        input_type: Final = self._detect_input_type(request_data)\n\n        if input_type == \"converse\":\n            # Validate Converse format (messages-based)\n            messages: Final = request_data.get(\"messages\", [])\n            if not messages:\n                raise ValueError(\"messages parameter is required for Converse input\")\n\n            if not isinstance(messages, list):\n                raise ValueError(\"messages must be a list\")\n\n            for i, message in enumerate(messages):\n                if not isinstance(message, dict):\n                    raise ValueError(f\"Message {i} must be a dictionary\")\n\n                if \"role\" not in message:\n                    raise ValueError(f\"Message {i} must have a 'role' field\")\n\n                if \"content\" not in message:\n                    raise ValueError(f\"Message {i} must have a 'content' field\")\n        else:\n            # For InvokeModel format, we need at least some content to count tokens\n            # The content structure varies by model, so we do minimal validation\n            if len(request_data) <= 1:  # Only has 'model' field\n                raise ValueError(\"Request must contain content to count tokens\")\n","sourceCodeStart":253,"sourceCodeEnd":283,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/bedrock/count_tokens/transformation.py#L253-L283","documentation":"Raised while iterating a Converse-format messages list: element i is not a dict. Each message must be an object with role/content keys; a non-dict element (string, number, nested list) fails per-index validation and the message includes the offending index.","triggerScenarios":"messages=['hello', {...}] (mixed raw strings and message objects); messages=[[{...}]] (extra nesting); messages containing JSON strings that were never parsed: messages=['{\"role\": \"user\"}'].","commonSituations":"Concatenating user-typed input directly into the list; LLM-generated request bodies with inconsistent shapes; pipelines that sometimes emit content-only strings instead of {role, content} dicts.","solutions":["Find element i named in the error and wrap/convert it into {'role': ..., 'content': ...}","Parse JSON strings before insertion: json.loads(m) if isinstance(m, str) and m.startswith('{')","Sanitize the list upstream: messages = [m for m in messages if isinstance(m, dict)] plus explicit conversion of stragglers"],"exampleFix":"# before\nmessages = ['hello', {'role': 'assistant', 'content': 'hi'}]\n\n# after\nmessages = [\n    {'role': 'user', 'content': 'hello'},\n    {'role': 'assistant', 'content': 'hi'},\n]","handlingStrategy":"type-guard","validationCode":"def assert_all_messages_are_dicts(messages: list) -> None:\n    for i, m in enumerate(messages):\n        if not isinstance(m, dict):\n            raise TypeError(f'message {i} is {type(m).__name__}, expected dict: {m!r:.80}')","typeGuard":"def all_messages_dicts(messages: list) -> bool:\n    return all(isinstance(m, dict) for m in messages)","tryCatchPattern":"try:\n    handler.count_tokens(body)\nexcept ValueError as e:\n    m = re.search(r'Message (\\d+) must be a dictionary', str(e))\n    if m:\n        i = int(m.group(1))\n        body['messages'][i] = normalize_to_message(body['messages'][i])  # str -> {'role':'user','content':s}\n        handler.count_tokens(body)\n    else:\n        raise","preventionTips":["Convert raw strings to {'role': 'user', 'content': s} at ingestion","Run a sanitize step: parse JSON-looking strings, wrap non-dicts or drop them with a warning","Property-test request builders with mixed-shape inputs"],"tags":["bedrock","count-tokens","validation","messages","type-error"],"backgroundTag":"schema-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}