{"record":{"id":"f1d7367d72ef2fff","repo":"BerriAI/litellm","slug":"no-audio-part-found-in-the-response","errorCode":null,"errorMessage":"No audio part found in the response","messagePattern":"No audio part found in the response","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/endpoints/speech/speech_to_completion_bridge/transformation.py","lineNumber":108,"sourceCode":"        )\n\n        return wav_header + pcm_data\n\n    def _is_gemini_tts_model(self, model: str) -> bool:\n        \"\"\"Check if the model is a Gemini TTS model that returns PCM16 data.\"\"\"\n        return \"gemini\" in model.lower() and (\"tts\" in model.lower() or \"preview-tts\" in model.lower())\n\n    def transform_response(self, model_response: \"ModelResponse\") -> \"HttpxBinaryResponseContent\":\n        import base64\n\n        import httpx\n\n        from litellm.types.llms.openai import HttpxBinaryResponseContent\n        from litellm.types.utils import Choices\n\n        audio_part: Final = cast(Choices, model_response.choices[0]).message.audio\n        if audio_part is None:\n            raise ValueError(\"No audio part found in the response\")\n        audio_content: Final = audio_part.data\n\n        # Decode base64 to get binary content\n        binary_data = base64.b64decode(audio_content)\n\n        # Check if this is a Gemini TTS model that returns raw PCM16 data\n        model: Final = getattr(model_response, \"model\", \"\")\n        headers: Final = {}\n        if self._is_gemini_tts_model(model):\n            # Convert PCM16 to WAV format for proper audio file playback\n            binary_data = self._convert_pcm16_to_wav(binary_data)\n            headers[\"Content-Type\"] = \"audio/wav\"\n        else:\n            headers[\"Content-Type\"] = \"audio/mpeg\"\n\n        # Create an httpx.Response object\n        response: Final = httpx.Response(status_code=200, content=binary_data, headers=headers)\n        return HttpxBinaryResponseContent(response)","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/endpoints/speech/speech_to_completion_bridge/transformation.py#L90-L126","documentation":"SpeechToCompletionBridgeTransformationHandler.transform_response (transformation.py:98) expects choices[0].message.audio to be present on the ModelResponse and raises ValueError when it is None. The audio part is only produced when the underlying chat model actually returned audio — the request must have carried modalities=[\"audio\"] AND the audio voice/format parameter. Note transform_request only adds the audio param when voice is not None (transformation.py:28-32), so a speech call without voice can yield a plain text response with no audio part.","triggerScenarios":"Calling litellm.speech()/the bridge without voice (no audio param sent, provider returns text-only); using a model that does not support audio output (modalities=[\"audio\"] ignored by the provider); a provider bug/drop of the audio field in the response; response_format mismatches causing the provider to omit audio.","commonSituations":"Assuming any chat model can TTS via the bridge (e.g. plain gpt-4o instead of gpt-4o-audio-preview or a gemini *-tts model); omitting the voice argument because it looks optional; providers partially implementing the audio modality.","solutions":["Always pass a voice supported by the model: litellm.speech(model=\"openai/gpt-4o-audio-preview\", input=\"...\", voice=\"alloy\").","Use an audio-capable model: OpenAI gpt-4o-audio-preview / gpt-4o-mini-audio-preview, or Gemini TTS models (e.g. gemini-2.5-flash-preview-tts), which the bridge auto-converts from PCM16 to WAV.","If calling the handler directly, include voice so transform_request emits modalities + audio params.","Inspect the raw completion for the same input to confirm the provider returns message.audio before blaming the bridge."],"exampleFix":"# before: no voice -> no audio param -> provider returns text-only -> ValueError\naudio = litellm.speech(model=\"openai/gpt-4o-audio-preview\", input=\"Hello world\")\n\n# after: voice supplied, audio modality negotiated\naudio = litellm.speech(\n    model=\"openai/gpt-4o-audio-preview\",\n    input=\"Hello world\",\n    voice=\"alloy\",\n    response_format={\"format\": \"wav\"},\n)","handlingStrategy":"try-catch","validationCode":"AUDIO_CAPABLE = (\"gpt-4o-audio-preview\", \"gpt-4o-mini-audio-preview\", \"tts\")  # 'tts' catches gemini *-tts models\n\ndef assert_audio_capable(model: str, voice: str | None) -> None:\n    if voice is None:\n        raise ValueError(\"speech bridge only returns audio when a voice is passed\")\n    if not any(m in model for m in AUDIO_CAPABLE):\n        raise ValueError(f\"{model!r} cannot return audio; use an audio-capable model (gpt-4o-audio-preview, gemini *-tts)\")","typeGuard":null,"tryCatchPattern":"try:\n    audio = litellm.speech(model=model, input=text, voice=voice)\nexcept ValueError as e:\n    if \"No audio part\" in str(e):\n        raise RuntimeError(\n            f\"{model!r} returned no audio — pass voice= and use an audio-capable model \"\n            \"(gpt-4o-audio-preview / gemini *-tts)\"\n        ) from e\n    raise","preventionTips":["Always pass voice when using the speech bridge — without it the audio request param is never sent.","Restrict speech routes to models that support the audio modality.","Smoke-test new TTS models with a one-off completion call and assert message.audio is present before wiring them in."],"tags":["speech","tts","audio-output","response-parsing"],"backgroundTag":"missing-audio-in-response","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}