{"record":{"id":"a11fe007a7036703","repo":"BerriAI/litellm","slug":"message-i-must-have-a-role-field-a11fe0","errorCode":null,"errorMessage":"Message {i} must have a 'role' field","messagePattern":"Message (.+?) must have a 'role' field","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/bedrock/count_tokens/transformation.py","lineNumber":274,"sourceCode":"            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":256,"sourceCodeEnd":283,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/bedrock/count_tokens/transformation.py#L256-L283","documentation":"Converse-format validation requiring each message dict to contain a 'role' key (e.g. 'user'/'assistant'). The message index i is included so the offending turn can be located.","triggerScenarios":"messages = [{'content': [...]}] — content present but role omitted; or role stored under a different key like 'speaker'/'author' by a custom serializer.","commonSituations":"Hand-built message dicts, conversion from other vendors' formats (some omit role), or typos like 'Role'.","solutions":["Add role to each message: {'role': 'user'|'assistant', 'content': [...]}","When importing from another provider's schema, map its speaker field to 'role'"],"exampleFix":"# before\n{'content': [{'text': 'hello'}]}\n\n# after\n{'role': 'user', 'content': [{'text': 'hello'}]}","handlingStrategy":"validation","validationCode":"VALID_ROLES = {\"user\", \"assistant\"}\nfor i, m in enumerate(req[\"messages\"]):\n    if \"role\" not in m:\n        raise ValueError(f\"Message {i} must have a 'role' field\")","typeGuard":"def has_role(m: dict) -> bool:\n    return isinstance(m, dict) and m.get(\"role\") in {\"user\", \"assistant\"}","tryCatchPattern":null,"preventionTips":["Map vendor-specific speaker fields to 'role' in your conversion layer","Add schema validation (pydantic/jsonschema) for outbound Bedrock payloads"],"tags":["bedrock","count-tokens","validation"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}