From 124ba463f833889f66289a850511c560a99c5448 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Nov 2025 14:32:09 -0800 Subject: [PATCH] [Feat] RunwayML - Add support for /audio/speech `eleven_multilingual_v2 ` endpoint (#16604) * init RunwayMLTextToSpeechConfig * add RunwayMLTextToSpeechConfig * add RunwayMLTextToSpeechConfig * test_runwayml_tts_async * runway ml speech * fix voices * fix test * docs runway lm * add runwayml here * fix RunwayMLTextToSpeechConfig * test_openai_voice_mapping_to_runwayml --- .../docs/providers/runwayml/text-to-speech.md | 244 ++++++++ .../llms/runwayml/text_to_speech/__init__.py | 5 + .../runwayml/text_to_speech/transformation.py | 591 ++++++++++++++++++ .../llms/runwayml/videos/transformation.py | 9 +- litellm/main.py | 33 + litellm/utils.py | 6 + provider_endpoints_support.json | 2 +- tests/audio_tests/runwayml_speech.mp3 | Bin 0 -> 67503 bytes tests/audio_tests/test_audio_speech.py | 54 ++ .../test_text_to_speech_transformation.py | 67 ++ 10 files changed, 1003 insertions(+), 8 deletions(-) create mode 100644 docs/my-website/docs/providers/runwayml/text-to-speech.md create mode 100644 litellm/llms/runwayml/text_to_speech/__init__.py create mode 100644 litellm/llms/runwayml/text_to_speech/transformation.py create mode 100644 tests/audio_tests/runwayml_speech.mp3 create mode 100644 tests/test_litellm/llms/runwayml/test_text_to_speech_transformation.py diff --git a/docs/my-website/docs/providers/runwayml/text-to-speech.md b/docs/my-website/docs/providers/runwayml/text-to-speech.md new file mode 100644 index 00000000000..020269863c6 --- /dev/null +++ b/docs/my-website/docs/providers/runwayml/text-to-speech.md @@ -0,0 +1,244 @@ +# RunwayML - Text-to-Speech + +## Overview + +| Property | Details | +|-------|-------| +| Description | RunwayML provides high-quality AI-powered text-to-speech with natural-sounding voices | +| Provider Route on LiteLLM | `runwayml/` | +| Supported Operations | [`/audio/speech`](#quick-start) | +| Link to Provider Doc | [RunwayML API ↗](https://docs.dev.runwayml.com/) | + +LiteLLM supports RunwayML's text-to-speech API with automatic task polling, allowing you to generate natural-sounding audio from text. + +## Quick Start + +```python showLineNumbers title="Basic Text-to-Speech" +from litellm import speech +import os + +os.environ["RUNWAYML_API_KEY"] = "your-api-key" + +response = speech( + model="runwayml/eleven_multilingual_v2", + input="Step right up, ladies and gentlemen! Have you ever wished for a toaster that's not just a toaster but a marvel of modern ingenuity?", + voice="alloy" +) + +# Save the audio +with open("output.mp3", "wb") as f: + f.write(response.content) +``` + +## Authentication + +Set your RunwayML API key: + +```python showLineNumbers title="Set API Key" +import os + +os.environ["RUNWAYML_API_KEY"] = "your-api-key" +``` + +## Supported Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `model` | string | Yes | Model to use (e.g., `runwayml/eleven_multilingual_v2`) | +| `input` | string | Yes | Text to convert to speech | +| `voice` | string or dict | Yes | Voice to use (OpenAI name, RunwayML preset, or voice config) | + +## Voice Options + +### Using OpenAI Voice Names + +OpenAI voice names are automatically mapped to appropriate RunwayML voices: + +```python showLineNumbers title="OpenAI Voice Names" +from litellm import speech + +# These OpenAI voice names work automatically +response = speech( + model="runwayml/eleven_multilingual_v2", + input="Hello, world!", + voice="alloy" # Maya - neutral, balanced female voice +) +``` + +**Voice Mappings:** +- `alloy` → Maya (neutral, balanced female voice) +- `echo` → James (male voice) +- `fable` → Bernard (warm, storytelling voice) +- `onyx` → Vincent (deep male voice) +- `nova` → Serene (warm, expressive female voice) +- `shimmer` → Ella (clear, friendly female voice) + +### Using RunwayML Preset Voices + +You can directly specify any RunwayML preset voice by passing the preset name as a string: + +```python showLineNumbers title="RunwayML Preset Names" +from litellm import speech + +# Pass the RunwayML voice name as a string +response = speech( + model="runwayml/eleven_multilingual_v2", + input="Hello, world!", + voice="Maya" # LiteLLM automatically formats this for RunwayML +) + +# Try different RunwayML voices +response = speech( + model="runwayml/eleven_multilingual_v2", + input="Step right up, ladies and gentlemen!", + voice="Bernard" # Great for storytelling +) +``` + +**Available RunwayML Voices:** + +Maya, Arjun, Serene, Bernard, Billy, Mark, Clint, Mabel, Chad, Leslie, Eleanor, Elias, Elliot, Grungle, Brodie, Sandra, Kirk, Kylie, Lara, Lisa, Malachi, Marlene, Martin, Miriam, Monster, Paula, Pip, Rusty, Ragnar, Xylar, Maggie, Jack, Katie, Noah, James, Rina, Ella, Mariah, Frank, Claudia, Niki, Vincent, Kendrick, Myrna, Tom, Wanda, Benjamin, Kiana, Rachel + +:::tip +Simply pass the voice name as a string - LiteLLM automatically handles the internal RunwayML API format conversion. +::: + +## Async Usage + +```python showLineNumbers title="Async Text-to-Speech" +from litellm import aspeech +import os +import asyncio + +os.environ["RUNWAYML_API_KEY"] = "your-api-key" + +async def generate_speech(): + response = await aspeech( + model="runwayml/eleven_multilingual_v2", + input="This is an asynchronous text-to-speech request.", + voice="nova" + ) + + with open("output.mp3", "wb") as f: + f.write(response.content) + + print("Audio generated successfully!") + +asyncio.run(generate_speech()) +``` + +## LiteLLM Proxy Usage + +Add RunwayML to your proxy configuration: + +```yaml showLineNumbers title="config.yaml" +model_list: + - model_name: runway-tts + litellm_params: + model: runwayml/eleven_multilingual_v2 + api_key: os.environ/RUNWAYML_API_KEY +``` + +Start the proxy: + +```bash +litellm --config /path/to/config.yaml +``` + +Generate speech through the proxy: + +```bash showLineNumbers title="Proxy Request" +curl --location 'http://localhost:4000/v1/audio/speech' \ +--header 'Content-Type: application/json' \ +--header 'x-litellm-api-key: sk-1234' \ +--data '{ + "model": "runwayml/eleven_multilingual_v2", + "input": "Hello from the LiteLLM proxy!", + "voice": "alloy" +}' +``` + +With RunwayML-specific voice: + +```bash showLineNumbers title="Proxy Request with RunwayML Voice" +curl --location 'http://localhost:4000/v1/audio/speech' \ +--header 'Content-Type: application/json' \ +--header 'x-litellm-api-key: sk-1234' \ +--data '{ + "model": "runwayml/eleven_multilingual_v2", + "input": "Hello with a custom RunwayML voice!", + "voice": "Bernard" +}' +``` + +## Supported Models + +| Model | Description | +|-------|-------------| +| `runwayml/eleven_multilingual_v2` | High-quality multilingual text-to-speech | + +## Cost Tracking + +LiteLLM automatically tracks RunwayML text-to-speech costs: + +```python showLineNumbers title="Cost Tracking" +from litellm import speech, completion_cost + +response = speech( + model="runwayml/eleven_multilingual_v2", + input="Hello, world!", + voice="alloy" +) + +cost = completion_cost(completion_response=response) +print(f"Text-to-speech cost: ${cost}") +``` + +## Supported Features + +| Feature | Supported | +|---------|-----------| +| Text-to-Speech | ✅ | +| Cost Tracking | ✅ | +| Logging | ✅ | +| Fallbacks | ✅ | +| Load Balancing | ✅ | +| 50+ Voice Presets | ✅ | + +## How It Works + +RunwayML uses an asynchronous task-based API pattern. LiteLLM handles the polling and response transformation automatically. + +### Complete Flow Diagram + +```mermaid +sequenceDiagram + participant Client + box rgb(200, 220, 255) LiteLLM AI Gateway + participant LiteLLM + end + participant RunwayML as RunwayML API + participant Storage as Audio Storage + + Client->>LiteLLM: POST /audio/speech (OpenAI format) + Note over LiteLLM: Transform to RunwayML format
Map voice to preset ID + + LiteLLM->>RunwayML: POST v1/text_to_speech + RunwayML-->>LiteLLM: 200 OK + task ID + + Note over LiteLLM: Automatic Polling + loop Every 2 seconds + LiteLLM->>RunwayML: GET v1/tasks/{task_id} + RunwayML-->>LiteLLM: Status: RUNNING + end + + LiteLLM->>RunwayML: GET v1/tasks/{task_id} + RunwayML-->>LiteLLM: Status: SUCCEEDED + audio URL + + LiteLLM->>Storage: GET audio URL + Storage-->>LiteLLM: Audio data (MP3) + + Note over LiteLLM: Return audio content + LiteLLM-->>Client: Audio Response (binary) +``` + diff --git a/litellm/llms/runwayml/text_to_speech/__init__.py b/litellm/llms/runwayml/text_to_speech/__init__.py new file mode 100644 index 00000000000..491e8449e0a --- /dev/null +++ b/litellm/llms/runwayml/text_to_speech/__init__.py @@ -0,0 +1,5 @@ +"""RunwayML Text-to-Speech implementation.""" +from .transformation import RunwayMLTextToSpeechConfig + +__all__ = ["RunwayMLTextToSpeechConfig"] + diff --git a/litellm/llms/runwayml/text_to_speech/transformation.py b/litellm/llms/runwayml/text_to_speech/transformation.py new file mode 100644 index 00000000000..ac926beb227 --- /dev/null +++ b/litellm/llms/runwayml/text_to_speech/transformation.py @@ -0,0 +1,591 @@ +""" +RunwayML Text-to-Speech transformation + +Maps OpenAI TTS spec to RunwayML Text-to-Speech API +""" +import asyncio +import time +from typing import TYPE_CHECKING, Any, Coroutine, Dict, Optional, Tuple, Union + +import httpx + +import litellm +from litellm._logging import verbose_logger +from litellm.constants import ( + RUNWAYML_DEFAULT_API_VERSION, + RUNWAYML_POLLING_TIMEOUT, +) +from litellm.llms.base_llm.text_to_speech.transformation import ( + BaseTextToSpeechConfig, + TextToSpeechRequestData, +) +from litellm.secret_managers.main import get_secret_str + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.types.llms.openai import HttpxBinaryResponseContent +else: + LiteLLMLoggingObj = Any + HttpxBinaryResponseContent = Any + + +class RunwayMLTextToSpeechConfig(BaseTextToSpeechConfig): + """ + Configuration for RunwayML Text-to-Speech + + Reference: https://api.dev.runwayml.com/v1/text_to_speech + """ + + DEFAULT_BASE_URL: str = "https://api.dev.runwayml.com" + TTS_ENDPOINT_PATH: str = "v1/text_to_speech" + DEFAULT_MODEL: str = "eleven_multilingual_v2" + DEFAULT_VOICE_TYPE: str = "runway-preset" + DEFAULT_VOICE_PRESET_ID: str = "Bernard" + + # Voice mappings from OpenAI voices to RunwayML preset IDs + # OpenAI voices mapped to similar-sounding RunwayML voices + VOICE_MAPPINGS = { + "alloy": "Maya", # Neutral, balanced female voice + "echo": "James", # Male voice + "fable": "Bernard", # Warm, storytelling voice + "onyx": "Vincent", # Deep male voice + "nova": "Serene", # Warm, expressive female voice + "shimmer": "Ella", # Clear, friendly female voice + } + + def dispatch_text_to_speech( + self, + model: str, + input: str, + voice: Optional[Union[str, Dict]], + optional_params: Dict, + litellm_params_dict: Dict, + logging_obj: "LiteLLMLoggingObj", + timeout: Union[float, httpx.Timeout], + extra_headers: Optional[Dict[str, Any]], + base_llm_http_handler: Any, + aspeech: bool, + api_base: Optional[str], + api_key: Optional[str], + **kwargs: Any, + ) -> Union[ + "HttpxBinaryResponseContent", + Coroutine[Any, Any, "HttpxBinaryResponseContent"], + ]: + """ + Dispatch method to handle RunwayML TTS requests + + This method encapsulates RunwayML-specific credential resolution and parameter handling + + Args: + base_llm_http_handler: The BaseLLMHTTPHandler instance from main.py + """ + # Resolve api_base from multiple sources + api_base = ( + api_base + or litellm_params_dict.get("api_base") + or litellm.api_base + or get_secret_str("RUNWAYML_API_BASE") + or self.DEFAULT_BASE_URL + ) + + # Resolve api_key from multiple sources + api_key = ( + api_key + or litellm_params_dict.get("api_key") + or litellm.api_key + or get_secret_str("RUNWAYML_API_SECRET") + or get_secret_str("RUNWAYML_API_KEY") + ) + + # Convert voice to appropriate format + voice_param: Optional[Union[str, Dict]] = voice + if isinstance(voice, str): + # Keep as string, will be processed in map_openai_params + voice_param = voice + elif isinstance(voice, dict): + # Already in dict format, pass through + voice_param = voice + + litellm_params_dict.update({ + "api_key": api_key, + "api_base": api_base, + }) + + # Call the text_to_speech_handler + response = base_llm_http_handler.text_to_speech_handler( + model=model, + input=input, + voice=voice_param, + text_to_speech_provider_config=self, + text_to_speech_optional_params=optional_params, + custom_llm_provider="runwayml", + litellm_params=litellm_params_dict, + logging_obj=logging_obj, + timeout=timeout, + extra_headers=extra_headers, + client=None, + _is_async=aspeech, + ) + + return response + + def get_supported_openai_params(self, model: str) -> list: + """ + RunwayML TTS supports these OpenAI parameters + """ + return ["voice"] + + def map_openai_params( + self, + model: str, + optional_params: Dict, + voice: Optional[Union[str, Dict]] = None, + drop_params: bool = False, + kwargs: Dict = {}, + ) -> Tuple[Optional[str], Dict]: + """ + Map OpenAI parameters to RunwayML TTS parameters + + Returns: + Tuple of (mapped_voice_string, mapped_params) + + Note: Since RunwayML requires voice as a dict, we store it in + mapped_params["runwayml_voice"] and return None for the voice string. + """ + mapped_params = {} + + # Map voice parameter to RunwayML format dict + voice_dict: Optional[Dict] = None + if isinstance(voice, str): + # Check if it's an OpenAI voice name that needs mapping + if voice in self.VOICE_MAPPINGS: + preset_id = self.VOICE_MAPPINGS[voice] + voice_dict = { + "type": self.DEFAULT_VOICE_TYPE, + "presetId": preset_id, + } + else: + # Assume it's a RunwayML preset ID + voice_dict = { + "type": self.DEFAULT_VOICE_TYPE, + "presetId": voice, + } + elif isinstance(voice, dict): + # Already in RunwayML format, use as-is + voice_dict = voice + + # Store the voice dict in optional_params for later use + if voice_dict is not None: + mapped_params["runwayml_voice"] = voice_dict + + # No other OpenAI params are currently supported by RunwayML TTS + # (response_format, speed, etc. are not supported) + + # Return None for voice string since RunwayML uses dict format + return None, mapped_params + + def validate_environment( + self, + headers: dict, + model: str, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + """ + Validate RunwayML environment and set up authentication headers + """ + validated_headers = headers.copy() + + final_api_key = ( + api_key + or get_secret_str("RUNWAYML_API_SECRET") + or get_secret_str("RUNWAYML_API_KEY") + ) + + if not final_api_key: + raise ValueError("RUNWAYML_API_SECRET or RUNWAYML_API_KEY is not set") + + validated_headers["Authorization"] = f"Bearer {final_api_key}" + validated_headers["X-Runway-Version"] = RUNWAYML_DEFAULT_API_VERSION + validated_headers["Content-Type"] = "application/json" + + return validated_headers + + def get_complete_url( + self, + model: str, + api_base: Optional[str], + litellm_params: dict, + ) -> str: + """ + Get the complete URL for RunwayML TTS request + """ + complete_url = ( + api_base + or get_secret_str("RUNWAYML_API_BASE") + or self.DEFAULT_BASE_URL + ) + + complete_url = complete_url.rstrip("/") + return f"{complete_url}/{self.TTS_ENDPOINT_PATH}" + + @staticmethod + def _check_timeout(start_time: float, timeout_secs: float) -> None: + """ + Check if operation has timed out. + + Args: + start_time: Start time of the operation + timeout_secs: Timeout duration in seconds + + Raises: + TimeoutError: If operation has exceeded timeout + """ + if time.time() - start_time > timeout_secs: + raise TimeoutError( + f"RunwayML TTS task polling timed out after {timeout_secs} seconds" + ) + + @staticmethod + def _check_task_status(response_data: Dict[str, Any]) -> str: + """ + Check RunwayML task status from response. + + RunwayML statuses: PENDING, RUNNING, SUCCEEDED, FAILED, CANCELLED, THROTTLED + + Args: + response_data: JSON response from RunwayML task endpoint + + Returns: + Normalized status string: "running", "succeeded", or raises on failure + + Raises: + ValueError: If task failed or status is unknown + """ + status = response_data.get("status", "").upper() + + verbose_logger.debug(f"RunwayML TTS task status: {status}") + + if status == "SUCCEEDED": + return "succeeded" + elif status == "FAILED": + failure_reason = response_data.get("failure", "Unknown error") + failure_code = response_data.get("failureCode", "unknown") + raise ValueError( + f"RunwayML TTS failed: {failure_reason} (code: {failure_code})" + ) + elif status == "CANCELLED": + raise ValueError("RunwayML TTS was cancelled") + elif status in ["PENDING", "RUNNING", "THROTTLED"]: + return "running" + else: + raise ValueError(f"Unknown RunwayML task status: {status}") + + def _poll_task_sync( + self, + task_id: str, + api_base: str, + headers: Dict[str, str], + timeout_secs: float = 600, + ) -> httpx.Response: + """ + Poll RunwayML task until completion (sync). + + RunwayML POST returns immediately with a task that has status PENDING/RUNNING. + We need to poll GET /v1/tasks/{task_id} until status is SUCCEEDED or FAILED. + + Args: + task_id: The task ID to poll + api_base: Base URL for RunwayML API + headers: Request headers (including auth) + timeout_secs: Total timeout in seconds (default: 600s = 10 minutes) + + Returns: + Final response with completed task + """ + from litellm.llms.custom_httpx.http_handler import _get_httpx_client + + client = _get_httpx_client() + start_time = time.time() + + # Build task status URL + api_base = api_base.rstrip("/") + task_url = f"{api_base}/v1/tasks/{task_id}" + + verbose_logger.debug(f"Polling RunwayML TTS task: {task_url}") + + while True: + self._check_timeout(start_time=start_time, timeout_secs=timeout_secs) + + # Poll the task status + response = client.get(url=task_url, headers=headers) + response.raise_for_status() + + response_data = response.json() + + # Check task status + status = self._check_task_status(response_data=response_data) + + if status == "succeeded": + return response + elif status == "running": + # Wait before polling again (RunwayML recommends 1-2 second intervals) + time.sleep(2) + + async def _poll_task_async( + self, + task_id: str, + api_base: str, + headers: Dict[str, str], + timeout_secs: float = 600, + ) -> httpx.Response: + """ + Poll RunwayML task until completion (async). + + Args: + task_id: The task ID to poll + api_base: Base URL for RunwayML API + headers: Request headers (including auth) + timeout_secs: Total timeout in seconds (default: 600s = 10 minutes) + + Returns: + Final response with completed task + """ + from litellm.llms.custom_httpx.http_handler import get_async_httpx_client + + client = get_async_httpx_client(llm_provider=litellm.LlmProviders.RUNWAYML) + start_time = time.time() + + # Build task status URL + api_base = api_base.rstrip("/") + task_url = f"{api_base}/v1/tasks/{task_id}" + + verbose_logger.debug(f"Polling RunwayML TTS task (async): {task_url}") + + while True: + self._check_timeout(start_time=start_time, timeout_secs=timeout_secs) + + # Poll the task status + response = await client.get(url=task_url, headers=headers) + response.raise_for_status() + + response_data = response.json() + + # Check task status + status = self._check_task_status(response_data=response_data) + + if status == "succeeded": + return response + elif status == "running": + # Wait before polling again (RunwayML recommends 1-2 second intervals) + await asyncio.sleep(2) + + def transform_text_to_speech_request( + self, + model: str, + input: str, + voice: Optional[Union[str, Dict]], + optional_params: Dict, + litellm_params: Dict, + headers: dict, + ) -> TextToSpeechRequestData: + """ + Transform OpenAI TTS request to RunwayML TTS format + + RunwayML expects: + - model: The model to use (e.g., 'eleven_multilingual_v2') + - promptText: The text to convert to speech + - voice: Voice configuration object + { + "type": "runway-preset", + "presetId": "Bernard" + } + + Returns: + TextToSpeechRequestData: Contains JSON body and headers + """ + # Get voice from optional_params (mapped in map_openai_params) + runwayml_voice = optional_params.get("runwayml_voice") + if runwayml_voice is None: + # Use default voice if not provided + runwayml_voice = { + "type": self.DEFAULT_VOICE_TYPE, + "presetId": self.DEFAULT_VOICE_PRESET_ID, + } + + # Build request body + request_body = { + "model": model or self.DEFAULT_MODEL, + "promptText": input, + "voice": runwayml_voice, + } + + # Add any other optional parameters (except runwayml_voice which we already used) + for k, v in optional_params.items(): + if k not in request_body and k != "runwayml_voice": + request_body[k] = v + + return { + "dict_body": request_body, + "headers": headers, + } + + def transform_text_to_speech_response( + self, + model: str, + raw_response: httpx.Response, + logging_obj: "LiteLLMLoggingObj", + ) -> "HttpxBinaryResponseContent": + """ + Transform RunwayML TTS response to standard format + + RunwayML returns a task immediately with status PENDING/RUNNING. + We need to poll the task until it completes, then download the audio. + + Initial response: + { + "id": "task_123...", + "status": "PENDING" | "RUNNING", + "createdAt": "2025-11-13T..." + } + + After polling: + { + "id": "task_123...", + "status": "SUCCEEDED", + "output": ["https://storage.googleapis.com/.../audio.mp3"], + "completedAt": "2025-11-13T..." + } + """ + from litellm.types.llms.openai import HttpxBinaryResponseContent + + try: + response_data = raw_response.json() + except Exception as e: + raise self.get_error_class( + error_message=f"Error parsing RunwayML TTS response: {e}", + status_code=raw_response.status_code, + headers=dict(raw_response.headers), + ) + + verbose_logger.debug("RunwayML TTS starting polling...") + + # Get task ID + task_id = response_data.get("id") + if not task_id: + raise ValueError("RunwayML TTS response missing task ID") + + # Get headers for polling (need auth) + poll_headers = { + "Authorization": raw_response.request.headers.get("Authorization", ""), + "X-Runway-Version": raw_response.request.headers.get( + "X-Runway-Version", RUNWAYML_DEFAULT_API_VERSION + ), + } + + # Poll until task completes + polled_response = self._poll_task_sync( + task_id=task_id, + api_base=self.DEFAULT_BASE_URL, + headers=poll_headers, + timeout_secs=RUNWAYML_POLLING_TIMEOUT, + ) + + # Get the completed task data + task_data = polled_response.json() + + verbose_logger.debug("RunwayML TTS polling complete, downloading audio") + + # Get audio URL from output + output = task_data.get("output", []) + if not output or not isinstance(output, list) or len(output) == 0: + raise ValueError("RunwayML TTS response missing audio URL in output") + + audio_url = output[0] + if not isinstance(audio_url, str): + raise ValueError(f"RunwayML TTS audio URL is not a string: {audio_url}") + + # Download the audio file + from litellm.llms.custom_httpx.http_handler import _get_httpx_client + + client = _get_httpx_client() + audio_response = client.get(url=audio_url) + audio_response.raise_for_status() + + verbose_logger.debug("RunwayML TTS audio downloaded successfully") + + # Return the audio data wrapped in HttpxBinaryResponseContent + return HttpxBinaryResponseContent(audio_response) + + async def async_transform_text_to_speech_response( + self, + model: str, + raw_response: httpx.Response, + logging_obj: "LiteLLMLoggingObj", + ) -> "HttpxBinaryResponseContent": + """ + Async transform RunwayML TTS response to standard format + + Same as sync version but uses async polling and download + """ + from litellm.types.llms.openai import HttpxBinaryResponseContent + + try: + response_data = raw_response.json() + except Exception as e: + raise self.get_error_class( + error_message=f"Error parsing RunwayML TTS response: {e}", + status_code=raw_response.status_code, + headers=dict(raw_response.headers), + ) + + verbose_logger.debug("RunwayML TTS starting polling (async)...") + + # Get task ID + task_id = response_data.get("id") + if not task_id: + raise ValueError("RunwayML TTS response missing task ID") + + # Get headers for polling (need auth) + poll_headers = { + "Authorization": raw_response.request.headers.get("Authorization", ""), + "X-Runway-Version": raw_response.request.headers.get( + "X-Runway-Version", RUNWAYML_DEFAULT_API_VERSION + ), + } + + # Poll until task completes (async) + polled_response = await self._poll_task_async( + task_id=task_id, + api_base=self.DEFAULT_BASE_URL, + headers=poll_headers, + timeout_secs=RUNWAYML_POLLING_TIMEOUT, + ) + + # Get the completed task data + task_data = polled_response.json() + + verbose_logger.debug("RunwayML TTS polling complete (async), downloading audio") + + # Get audio URL from output + output = task_data.get("output", []) + if not output or not isinstance(output, list) or len(output) == 0: + raise ValueError("RunwayML TTS response missing audio URL in output") + + audio_url = output[0] + if not isinstance(audio_url, str): + raise ValueError(f"RunwayML TTS audio URL is not a string: {audio_url}") + + # Download the audio file (async) + from litellm.llms.custom_httpx.http_handler import get_async_httpx_client + + client = get_async_httpx_client(llm_provider=litellm.LlmProviders.RUNWAYML) + audio_response = await client.get(url=audio_url) + audio_response.raise_for_status() + + verbose_logger.debug("RunwayML TTS audio downloaded successfully (async)") + + # Return the audio data wrapped in HttpxBinaryResponseContent + return HttpxBinaryResponseContent(audio_response) + diff --git a/litellm/llms/runwayml/videos/transformation.py b/litellm/llms/runwayml/videos/transformation.py index c45a1cd60d5..651acff6fc4 100644 --- a/litellm/llms/runwayml/videos/transformation.py +++ b/litellm/llms/runwayml/videos/transformation.py @@ -6,6 +6,8 @@ from httpx._types import RequestFiles import litellm from litellm.constants import RUNWAYML_DEFAULT_API_VERSION +from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.llms.base_llm.videos.transformation import BaseVideoConfig from litellm.llms.custom_httpx.http_handler import ( AsyncHTTPHandler, HTTPHandler, @@ -23,16 +25,9 @@ from litellm.types.videos.utils import ( if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj - from ...base_llm.chat.transformation import BaseLLMException as _BaseLLMException - from ...base_llm.videos.transformation import BaseVideoConfig as _BaseVideoConfig - LiteLLMLoggingObj = _LiteLLMLoggingObj - BaseVideoConfig = _BaseVideoConfig - BaseLLMException = _BaseLLMException else: LiteLLMLoggingObj = Any - BaseVideoConfig = Any - BaseLLMException = Any class RunwayMLVideoConfig(BaseVideoConfig): diff --git a/litellm/main.py b/litellm/main.py index 2ad444a9a20..23ffa90e2db 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -6006,6 +6006,39 @@ def speech( # noqa: PLR0915 logging_obj=logging_obj, custom_llm_provider=custom_llm_provider, ) + elif custom_llm_provider == "runwayml": + from litellm.llms.runwayml.text_to_speech.transformation import ( + RunwayMLTextToSpeechConfig, + ) + + # RunwayML Text-to-Speech + if text_to_speech_provider_config is None: + raise litellm.BadRequestError( + message="RunwayML Text-to-Speech configuration not found", + model=model, + llm_provider=custom_llm_provider, + ) + + # Cast to specific RunwayML config type to access dispatch method + runwayml_config = cast( + RunwayMLTextToSpeechConfig, text_to_speech_provider_config + ) + + response = runwayml_config.dispatch_text_to_speech( # type: ignore + model=model, + input=input, + voice=voice, + optional_params=optional_params, + litellm_params_dict=litellm_params_dict, + logging_obj=logging_obj, + timeout=timeout, + extra_headers=extra_headers, + base_llm_http_handler=base_llm_http_handler, + aspeech=aspeech or False, + api_base=api_base, + api_key=api_key, + **kwargs, + ) if response is None: raise Exception( diff --git a/litellm/utils.py b/litellm/utils.py index 7f87a800b07..ae3de67374f 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -7811,6 +7811,12 @@ class ProviderConfigManager: ) return AzureAVATextToSpeechConfig() + elif litellm.LlmProviders.RUNWAYML == provider: + from litellm.llms.runwayml.text_to_speech.transformation import ( + RunwayMLTextToSpeechConfig, + ) + + return RunwayMLTextToSpeechConfig() return None @staticmethod diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 6c3be897f40..4d9218d6095 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -1368,7 +1368,7 @@ "embeddings": false, "image_generations": true, "audio_transcriptions": false, - "audio_speech": false, + "audio_speech": true, "moderations": false, "batches": false, "rerank": false, diff --git a/tests/audio_tests/runwayml_speech.mp3 b/tests/audio_tests/runwayml_speech.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..22aee2084ba131cf077a4fdee3706272f52f861e GIT binary patch literal 67503 zcmd>ljSJbUeR)}se`pF9Bgf0v*Uo04SIr0YFR~JVG!j1r;4V z6AK#$7mt9Dh?uy<3mG{DWi>4w14Cmo8wZzHUOxVTL2nV^Q8DpJsp*+nIeG6s6ciSh zlvme&Y;11r`23}}e`tJaW_E6Ib$xSZ@B87&>Ba9qxAzZ^$iM%kr6#Q|FTl;m%ZuLe zeSAH|1VeMeZk)Yf&oxU{_Ys!2(Y$<08r#`FyOBykxz7V zA;>Mdv$M0Fe{h_go!`~5FebWi#?<_NbsMTwQTM#862#KHpl278v$2tFXsT!W-hvL%hYk;bpihg8rk5o6?#c6_A( z4MyoBgY_QyRfv3$9pzG!x|5wS2Mwgw;`J(pif|-Q>(&@FQ!(D zW{fu=kHX%i!}rEj6JW)brIOX9LEdW(oWOA=ZmG+&9eVwFtnD*-QE+UyBZ>hq0l<(f zvAO^V8v)T~9EtEoLzx50dE@ocClB=0#lr&+w0oZ_82eXZML? zV-85yCyS5JV2m{y@9G#jcHZ5IjK(4b`(SneWhn67KjhUF3A+X{nUJP9x8qHzFkUelo%;xhD;oH*Rr3; znK(V9iO6gui5NoP#nc7k;7GLLiKZUp5REH0@|&2tFVq${2B|b(&-$RF3`DBm&~?hL zu|Tb+-kE3EWMwmQj-S}^#!|j09BJ0}@X-}EqZMGH)%>9wLtq2KDYeDMz>^qGQauA?2Hr8O+q!9{7 z7!1avI!(kY<7!rlO*Z*w>MC+Nl<%3QBk0pi>*>0B*hfeQWk)6WSPV6>dGw&kIR7D5 z@=MFQeS$AF`pkkesqxK9`FiI=$RO{MbjddjWtsWr^!^*O;L>PN)9Uf3Dk4vXCgVgO2Or39~P-IU?A70p^F=r zZ}QHn`uqXpoh2~CV&1$JF7=)eiehb9A%yVS%~m;0OEmU1<@#83Q5gFpQztL}x9G6R zCy75IwOw!BCs*_z-zd2oaUt*F1FLXQM}+PPw8`0l|4=f$sC44?qNzD}=Q+A|Pra(n z>eaQ*Y?6By>aYV%=t;Tx%$fp4-(WoB?j7fp{lU|ΨG%-fafY!m>&%q3nlIsp)a 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 WW^#Hsf 0 + + # MP3 files start with these magic bytes + # ID3 tag or MPEG sync word + assert binary_content[:3] == b"ID3" or binary_content[:2] == b"\xff\xfb" or binary_content[:2] == b"\xff\xf3" + + # Write to file + response.stream_to_file(speech_file_path) + + # Verify file was created and has content + assert speech_file_path.exists() + assert speech_file_path.stat().st_size > 0 + + print(f"Azure TTS audio saved to: {speech_file_path}") + + # assert response cost is greater than 0 + print("Response cost: ", response._hidden_params["response_cost"]) + assert response._hidden_params["response_cost"] > 0 + + except Exception as e: + pytest.fail(f"Test failed with exception: {str(e)}") + + @pytest.mark.asyncio async def test_azure_ava_tts_with_custom_voice(): """ diff --git a/tests/test_litellm/llms/runwayml/test_text_to_speech_transformation.py b/tests/test_litellm/llms/runwayml/test_text_to_speech_transformation.py new file mode 100644 index 00000000000..277a47a03bc --- /dev/null +++ b/tests/test_litellm/llms/runwayml/test_text_to_speech_transformation.py @@ -0,0 +1,67 @@ +""" +Test RunwayML text-to-speech transformation +""" +import os +import sys + +sys.path.insert(0, os.path.abspath("../../../..")) + +from litellm.llms.runwayml.text_to_speech.transformation import ( + RunwayMLTextToSpeechConfig, +) + + +def test_openai_voice_mapping_to_runwayml(): + """ + Test that OpenAI voice names are correctly mapped to RunwayML preset IDs + """ + config = RunwayMLTextToSpeechConfig() + + # Test OpenAI voice mappings + openai_to_runway = { + "alloy": "Maya", + "echo": "James", + "fable": "Bernard", + "onyx": "Vincent", + "nova": "Serene", + "shimmer": "Ella", + } + + for openai_voice, expected_runway_voice in openai_to_runway.items(): + mapped_voice, mapped_params = config.map_openai_params( + model="eleven_multilingual_v2", + optional_params={}, + voice=openai_voice, + drop_params=False, + kwargs={}, + ) + + assert mapped_voice is None + assert "runwayml_voice" in mapped_params + assert mapped_params["runwayml_voice"]["type"] == "runway-preset" + assert mapped_params["runwayml_voice"]["presetId"] == expected_runway_voice + + +def test_runwayml_native_voice_passthrough(): + """ + Test that RunwayML native voice names are passed through correctly as-is + """ + config = RunwayMLTextToSpeechConfig() + + # Test various RunwayML native voices + runway_voices = ["Bernard", "Maya", "Arjun", "Serene", "Chad"] + + for runway_voice in runway_voices: + mapped_voice, mapped_params = config.map_openai_params( + model="eleven_multilingual_v2", + optional_params={}, + voice=runway_voice, + drop_params=False, + kwargs={}, + ) + + assert mapped_voice is None + assert "runwayml_voice" in mapped_params + assert mapped_params["runwayml_voice"]["type"] == "runway-preset" + assert mapped_params["runwayml_voice"]["presetId"] == runway_voice +