{"record":{"id":"84c6f2b0d64bf100","repo":"BerriAI/litellm","slug":"unexpected-model-turn-event-no-parts-key-mode","errorCode":null,"errorMessage":"Unexpected model turn event, no 'parts' key: {model_turn}","messagePattern":"Unexpected model turn event, no 'parts' key: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/gemini/realtime/transformation.py","lineNumber":165,"sourceCode":"\n        Returns either:\n        - response.text.delta - model_turn: {\"parts\": [{\"text\": \"...\"}]}\n        - response.audio.delta - model_turn: {\"parts\": [{\"inlineData\": {\"mimeType\": \"audio/pcm\", \"data\": \"...\"}}]}\n\n        Assumes parts is a single element list.\n        \"\"\"\n        if \"parts\" in model_turn:\n            parts: Final = model_turn[\"parts\"]\n            if len(parts) != 1:\n                verbose_logger.warning(\"Realtime: Expected 1 part, got %s for Gemini model turn event.\", len(parts))\n            part: Final = parts[0]\n            if \"text\" in part:\n                return OpenAIRealtimeEventTypes.RESPONSE_TEXT_DELTA\n            elif \"inlineData\" in part:\n                return OpenAIRealtimeEventTypes.RESPONSE_AUDIO_DELTA\n            else:\n                raise ValueError(f\"Unexpected part type: {part}\")\n        raise ValueError(f\"Unexpected model turn event, no 'parts' key: {model_turn}\")\n\n    def map_generation_complete_event(self, delta_type: ALL_DELTA_TYPES | None) -> OpenAIRealtimeEventTypes:\n        if delta_type == \"text\":\n            return OpenAIRealtimeEventTypes.RESPONSE_TEXT_DONE\n        elif delta_type == \"audio\":\n            return OpenAIRealtimeEventTypes.RESPONSE_AUDIO_DONE\n        else:\n            raise ValueError(f\"Unexpected delta type: {delta_type}\")\n\n    def get_audio_mime_type(self, input_audio_format: str = \"pcm16\"):\n        mime_types: Final = {\n            \"pcm16\": \"audio/pcm;rate=24000\",\n            \"g711_ulaw\": \"audio/pcmu\",\n            \"g711_alaw\": \"audio/pcma\",\n        }\n\n        return mime_types.get(input_audio_format, \"application/octet-stream\")\n","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/gemini/realtime/transformation.py#L147-L183","documentation":"Raised in map_model_turn_event when the modelTurn dict has no 'parts' key at all. The method assumes every modelTurn carries a parts list; a malformed or schema-changed server message (empty turn, new envelope shape) hits the trailing ValueError that echoes the whole message.","triggerScenarios":"Gemini realtime sends a modelTurn containing only metadata fields (e.g. a keep-alive or turn-heartbeat) or an API schema change renames/relocates 'parts'; also hand-crafted test messages replayed into the transformer.","commonSituations":"Replaying captured frames against a newer litellm; partial JSON deserialization upstream producing incomplete dicts; Gemini API evolution outpacing the installed litellm.","solutions":["Upgrade litellm to pick up the latest realtime transformer.","Log the failing message and skip the frame instead of tearing down the session.","If writing tests/replays, feed complete, real captured modelTurn payloads."],"exampleFix":"# before\nevent = config.map_model_turn_event(model_turn)\n\n# after\nif 'parts' not in model_turn:\n    logger.warning('modelTurn without parts, skipping: %r', model_turn)\n    return None\nevent = config.map_model_turn_event(model_turn)\n","handlingStrategy":"type-guard","validationCode":"def is_valid_model_turn(turn: dict) -> bool:\n    return isinstance(turn, dict) and isinstance(turn.get(\"parts\"), list) and len(turn[\"parts\"]) > 0","typeGuard":"def is_valid_model_turn(turn: object) -> bool:\n    return (\n        isinstance(turn, dict)\n        and isinstance(turn.get(\"parts\"), list)\n        and len(turn[\"parts\"]) > 0\n    )","tryCatchPattern":"try:\n    event = config.map_model_turn_event(model_turn)\nexcept ValueError as e:\n    if \"no 'parts' key\" in str(e):\n        logger.warning(\"Skipping parts-less modelTurn: %s\", e)\n        continue\n    raise","preventionTips":["Validate frame shape before mapping in replay/testing harnesses.","Upgrade litellm when Gemini changes the realtime envelope.","Log skipped frames with counts to detect systematic schema drift early."],"tags":["gemini","realtime","parsing","event-mapping","schema"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}