[Feat] Add audio transcriptions for WatsonX (#17160)

* feat: add IBMWatsonXAudioTranscriptionConfig

* init IBMWatsonXAudioTranscriptionConfig

* add inits

* test_watsonx_transcription_url_and_headers

* docs watsox audio

* docs watsonx audi

* docs
This commit is contained in:
Ishaan Jaff 2025-11-26 14:15:49 -08:00 committed by GitHub
parent d8e4aaf301
commit d987593855
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 450 additions and 289 deletions

View File

@ -1,287 +0,0 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# IBM watsonx.ai
LiteLLM supports all IBM [watsonx.ai](https://watsonx.ai/) foundational models and embeddings.
## Environment Variables
```python
os.environ["WATSONX_URL"] = "" # (required) Base URL of your WatsonX instance
# (required) either one of the following:
os.environ["WATSONX_APIKEY"] = "" # IBM cloud API key
os.environ["WATSONX_TOKEN"] = "" # IAM auth token
# optional - can also be passed as params to completion() or embedding()
os.environ["WATSONX_PROJECT_ID"] = "" # Project ID of your WatsonX instance
os.environ["WATSONX_DEPLOYMENT_SPACE_ID"] = "" # ID of your deployment space to use deployed models
os.environ["WATSONX_ZENAPIKEY"] = "" # Zen API key (use for long-term api token)
```
See [here](https://cloud.ibm.com/apidocs/watsonx-ai#api-authentication) for more information on how to get an access token to authenticate to watsonx.ai.
## Usage
<a target="_blank" href="https://colab.research.google.com/github/BerriAI/litellm/blob/main/cookbook/liteLLM_IBM_Watsonx.ipynb">
<img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/>
</a>
```python
import os
from litellm import completion
os.environ["WATSONX_URL"] = ""
os.environ["WATSONX_APIKEY"] = ""
## Call WATSONX `/text/chat` endpoint - supports function calling
response = completion(
model="watsonx/meta-llama/llama-3-1-8b-instruct",
messages=[{ "content": "what is your favorite colour?","role": "user"}],
project_id="<my-project-id>" # or pass with os.environ["WATSONX_PROJECT_ID"]
)
## Call WATSONX `/text/generation` endpoint - not all models support /chat route.
response = completion(
model="watsonx/ibm/granite-13b-chat-v2",
messages=[{ "content": "what is your favorite colour?","role": "user"}],
project_id="<my-project-id>"
)
```
## Usage - Streaming
```python
import os
from litellm import completion
os.environ["WATSONX_URL"] = ""
os.environ["WATSONX_APIKEY"] = ""
os.environ["WATSONX_PROJECT_ID"] = ""
response = completion(
model="watsonx/meta-llama/llama-3-1-8b-instruct",
messages=[{ "content": "what is your favorite colour?","role": "user"}],
stream=True
)
for chunk in response:
print(chunk)
```
#### Example Streaming Output Chunk
```json
{
"choices": [
{
"finish_reason": null,
"index": 0,
"delta": {
"content": "I don't have a favorite color, but I do like the color blue. What's your favorite color?"
}
}
],
"created": null,
"model": "watsonx/ibm/granite-13b-chat-v2",
"usage": {
"prompt_tokens": null,
"completion_tokens": null,
"total_tokens": null
}
}
```
## Usage - Models in deployment spaces
Models that have been deployed to a deployment space (e.g.: tuned models) can be called using the `deployment/<deployment_id>` format (where `<deployment_id>` is the ID of the deployed model in your deployment space).
The ID of your deployment space must also be set in the environment variable `WATSONX_DEPLOYMENT_SPACE_ID` or passed to the function as `space_id=<deployment_space_id>`.
```python
import litellm
response = litellm.completion(
model="watsonx/deployment/<deployment_id>",
messages=[{"content": "Hello, how are you?", "role": "user"}],
space_id="<deployment_space_id>"
)
```
## Usage - Embeddings
LiteLLM also supports making requests to IBM watsonx.ai embedding models. The credential needed for this is the same as for completion.
```python
from litellm import embedding
response = embedding(
model="watsonx/ibm/slate-30m-english-rtrvr",
input=["What is the capital of France?"],
project_id="<my-project-id>"
)
print(response)
# EmbeddingResponse(model='ibm/slate-30m-english-rtrvr', data=[{'object': 'embedding', 'index': 0, 'embedding': [-0.037463713, -0.02141933, -0.02851813, 0.015519324, ..., -0.0021367231, -0.01704561, -0.001425816, 0.0035238306]}], object='list', usage=Usage(prompt_tokens=8, total_tokens=8))
```
## OpenAI Proxy Usage
Here's how to call IBM watsonx.ai with the LiteLLM Proxy Server
### 1. Save keys in your environment
```bash
export WATSONX_URL=""
export WATSONX_APIKEY=""
export WATSONX_PROJECT_ID=""
```
### 2. Start the proxy
<Tabs>
<TabItem value="cli" label="CLI">
```bash
$ litellm --model watsonx/meta-llama/llama-3-8b-instruct
# Server running on http://0.0.0.0:4000
```
</TabItem>
<TabItem value="config" label="config.yaml">
```yaml
model_list:
- model_name: llama-3-8b
litellm_params:
# all params accepted by litellm.completion()
model: watsonx/meta-llama/llama-3-8b-instruct
api_key: "os.environ/WATSONX_API_KEY" # does os.getenv("WATSONX_API_KEY")
```
</TabItem>
</Tabs>
### 3. Test it
<Tabs>
<TabItem value="Curl" label="Curl Request">
```shell
curl --location 'http://0.0.0.0:4000/chat/completions' \
--header 'Content-Type: application/json' \
--data ' {
"model": "llama-3-8b",
"messages": [
{
"role": "user",
"content": "what is your favorite colour?"
}
]
}
'
```
</TabItem>
<TabItem value="openai" label="OpenAI v1.0.0+">
```python
import openai
client = openai.OpenAI(
api_key="anything",
base_url="http://0.0.0.0:4000"
)
# request sent to model set on litellm proxy, `litellm --model`
response = client.chat.completions.create(model="llama-3-8b", messages=[
{
"role": "user",
"content": "what is your favorite colour?"
}
])
print(response)
```
</TabItem>
<TabItem value="langchain" label="Langchain">
```python
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
SystemMessagePromptTemplate,
)
from langchain.schema import HumanMessage, SystemMessage
chat = ChatOpenAI(
openai_api_base="http://0.0.0.0:4000", # set openai_api_base to the LiteLLM Proxy
model = "llama-3-8b",
temperature=0.1
)
messages = [
SystemMessage(
content="You are a helpful assistant that im using to make a test request to."
),
HumanMessage(
content="test from litellm. tell me why it's amazing in 1 sentence"
),
]
response = chat(messages)
print(response)
```
</TabItem>
</Tabs>
## Authentication
### Passing credentials as parameters
You can also pass the credentials as parameters to the completion and embedding functions.
```python
import os
from litellm import completion
response = completion(
model="watsonx/ibm/granite-13b-chat-v2",
messages=[{ "content": "What is your favorite color?","role": "user"}],
url="",
api_key="",
project_id=""
)
```
## Supported IBM watsonx.ai Models
Here are some examples of models available in IBM watsonx.ai that you can use with LiteLLM:
| Mode Name | Command |
|------------------------------------|------------------------------------------------------------------------------------------|
| Flan T5 XXL | `completion(model=watsonx/google/flan-t5-xxl, messages=messages)` |
| Flan Ul2 | `completion(model=watsonx/google/flan-ul2, messages=messages)` |
| Mt0 XXL | `completion(model=watsonx/bigscience/mt0-xxl, messages=messages)` |
| Gpt Neox | `completion(model=watsonx/eleutherai/gpt-neox-20b, messages=messages)` |
| Mpt 7B Instruct2 | `completion(model=watsonx/ibm/mpt-7b-instruct2, messages=messages)` |
| Starcoder | `completion(model=watsonx/bigcode/starcoder, messages=messages)` |
| Llama 2 70B Chat | `completion(model=watsonx/meta-llama/llama-2-70b-chat, messages=messages)` |
| Llama 2 13B Chat | `completion(model=watsonx/meta-llama/llama-2-13b-chat, messages=messages)` |
| Granite 13B Instruct | `completion(model=watsonx/ibm/granite-13b-instruct-v1, messages=messages)` |
| Granite 13B Chat | `completion(model=watsonx/ibm/granite-13b-chat-v1, messages=messages)` |
| Flan T5 XL | `completion(model=watsonx/google/flan-t5-xl, messages=messages)` |
| Granite 13B Chat V2 | `completion(model=watsonx/ibm/granite-13b-chat-v2, messages=messages)` |
| Granite 13B Instruct V2 | `completion(model=watsonx/ibm/granite-13b-instruct-v2, messages=messages)` |
| Elyza Japanese Llama 2 7B Instruct | `completion(model=watsonx/elyza/elyza-japanese-llama-2-7b-instruct, messages=messages)` |
| Mixtral 8X7B Instruct V01 Q | `completion(model=watsonx/ibm-mistralai/mixtral-8x7b-instruct-v01-q, messages=messages)` |
For a list of all available models in watsonx.ai, see [here](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models.html?context=wx&locale=en&audience=wdp).
## Supported IBM watsonx.ai Embedding Models
| Model Name | Function Call |
|------------|------------------------------------------------------------------------|
| Slate 30m | `embedding(model="watsonx/ibm/slate-30m-english-rtrvr", input=input)` |
| Slate 125m | `embedding(model="watsonx/ibm/slate-125m-english-rtrvr", input=input)` |
For a list of all available embedding models in watsonx.ai, see [here](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models-embed.html?context=wx).

View File

@ -0,0 +1,57 @@
# WatsonX Audio Transcription
## Overview
| Property | Details |
|----------|---------|
| Description | WatsonX audio transcription using Whisper models for speech-to-text |
| Provider Route on LiteLLM | `watsonx/` |
| Supported Operations | `/v1/audio/transcriptions` |
| Link to Provider Doc | [IBM WatsonX.ai ↗](https://www.ibm.com/watsonx) |
## Quick Start
### **LiteLLM SDK**
```python showLineNumbers title="transcription.py"
import litellm
response = litellm.transcription(
model="watsonx/whisper-large-v3-turbo",
file=open("audio.mp3", "rb"),
api_base="https://us-south.ml.cloud.ibm.com",
api_key="your-api-key",
project_id="your-project-id"
)
print(response.text)
```
### **LiteLLM Proxy**
```yaml showLineNumbers title="config.yaml"
model_list:
- model_name: whisper-large-v3-turbo
litellm_params:
model: watsonx/whisper-large-v3-turbo
api_key: os.environ/WATSONX_APIKEY
api_base: os.environ/WATSONX_URL
project_id: os.environ/WATSONX_PROJECT_ID
```
```bash title="Request"
curl http://localhost:4000/v1/audio/transcriptions \
-H "Authorization: Bearer sk-1234" \
-F file="@audio.mp3" \
-F model="whisper-large-v3-turbo"
```
## Supported Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `model` | string | Model ID (e.g., `watsonx/whisper-large-v3-turbo`) |
| `file` | file | Audio file to transcribe |
| `language` | string | Language code (e.g., `en`) |
| `prompt` | string | Optional prompt to guide transcription |
| `temperature` | float | Sampling temperature (0-1) |
| `response_format` | string | `json`, `text`, `srt`, `verbose_json`, `vtt` |

View File

@ -0,0 +1,177 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
# IBM watsonx.ai
LiteLLM supports all IBM [watsonx.ai](https://watsonx.ai/) foundational models and embeddings.
## Environment Variables
```python
os.environ["WATSONX_URL"] = "" # (required) Base URL of your WatsonX instance
# (required) either one of the following:
os.environ["WATSONX_APIKEY"] = "" # IBM cloud API key
os.environ["WATSONX_TOKEN"] = "" # IAM auth token
# optional - can also be passed as params to completion() or embedding()
os.environ["WATSONX_PROJECT_ID"] = "" # Project ID of your WatsonX instance
os.environ["WATSONX_DEPLOYMENT_SPACE_ID"] = "" # ID of your deployment space to use deployed models
os.environ["WATSONX_ZENAPIKEY"] = "" # Zen API key (use for long-term api token)
```
See [here](https://cloud.ibm.com/apidocs/watsonx-ai#api-authentication) for more information on how to get an access token to authenticate to watsonx.ai.
## Usage
<a target="_blank" href="https://colab.research.google.com/github/BerriAI/litellm/blob/main/cookbook/liteLLM_IBM_Watsonx.ipynb">
<img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/>
</a>
```python showLineNumbers title="Chat Completion"
import os
from litellm import completion
os.environ["WATSONX_URL"] = ""
os.environ["WATSONX_APIKEY"] = ""
response = completion(
model="watsonx/meta-llama/llama-3-1-8b-instruct",
messages=[{ "content": "what is your favorite colour?","role": "user"}],
project_id="<my-project-id>"
)
```
## Usage - Streaming
```python showLineNumbers title="Streaming"
import os
from litellm import completion
os.environ["WATSONX_URL"] = ""
os.environ["WATSONX_APIKEY"] = ""
os.environ["WATSONX_PROJECT_ID"] = ""
response = completion(
model="watsonx/meta-llama/llama-3-1-8b-instruct",
messages=[{ "content": "what is your favorite colour?","role": "user"}],
stream=True
)
for chunk in response:
print(chunk)
```
## Usage - Models in deployment spaces
Models deployed to a deployment space (e.g.: tuned models) can be called using the `deployment/<deployment_id>` format.
```python showLineNumbers title="Deployment Space"
import litellm
response = litellm.completion(
model="watsonx/deployment/<deployment_id>",
messages=[{"content": "Hello, how are you?", "role": "user"}],
space_id="<deployment_space_id>"
)
```
## Usage - Embeddings
```python showLineNumbers title="Embeddings"
from litellm import embedding
response = embedding(
model="watsonx/ibm/slate-30m-english-rtrvr",
input=["What is the capital of France?"],
project_id="<my-project-id>"
)
```
## LiteLLM Proxy Usage
### 1. Save keys in your environment
```bash
export WATSONX_URL=""
export WATSONX_APIKEY=""
export WATSONX_PROJECT_ID=""
```
### 2. Start the proxy
<Tabs>
<TabItem value="cli" label="CLI">
```bash
$ litellm --model watsonx/meta-llama/llama-3-8b-instruct
```
</TabItem>
<TabItem value="config" label="config.yaml">
```yaml showLineNumbers title="config.yaml"
model_list:
- model_name: llama-3-8b
litellm_params:
model: watsonx/meta-llama/llama-3-8b-instruct
api_key: "os.environ/WATSONX_API_KEY"
```
</TabItem>
</Tabs>
### 3. Test it
<Tabs>
<TabItem value="Curl" label="Curl Request">
```shell
curl --location 'http://0.0.0.0:4000/chat/completions' \
--header 'Content-Type: application/json' \
--data '{
"model": "llama-3-8b",
"messages": [
{
"role": "user",
"content": "what is your favorite colour?"
}
]
}'
```
</TabItem>
<TabItem value="openai" label="OpenAI SDK">
```python showLineNumbers
import openai
client = openai.OpenAI(
api_key="anything",
base_url="http://0.0.0.0:4000"
)
response = client.chat.completions.create(
model="llama-3-8b",
messages=[{"role": "user", "content": "what is your favorite colour?"}]
)
print(response)
```
</TabItem>
</Tabs>
## Supported Models
| Model Name | Command |
|------------------------------------|------------------------------------------------------------------------------------------|
| Llama 3.1 8B Instruct | `completion(model="watsonx/meta-llama/llama-3-1-8b-instruct", messages=messages)` |
| Llama 2 70B Chat | `completion(model="watsonx/meta-llama/llama-2-70b-chat", messages=messages)` |
| Granite 13B Chat V2 | `completion(model="watsonx/ibm/granite-13b-chat-v2", messages=messages)` |
| Mixtral 8X7B Instruct | `completion(model="watsonx/ibm-mistralai/mixtral-8x7b-instruct-v01-q", messages=messages)` |
For all available models, see [watsonx.ai documentation](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models.html?context=wx).
## Supported Embedding Models
| Model Name | Function Call |
|------------|------------------------------------------------------------------------|
| Slate 30m | `embedding(model="watsonx/ibm/slate-30m-english-rtrvr", input=input)` |
| Slate 125m | `embedding(model="watsonx/ibm/slate-125m-english-rtrvr", input=input)` |
For all available embedding models, see [watsonx.ai embedding documentation](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models-embed.html?context=wx).

View File

@ -633,7 +633,14 @@ const sidebars = {
"providers/volcano",
"providers/voyage",
"providers/wandb_inference",
"providers/watsonx",
{
type: "category",
label: "WatsonX",
items: [
"providers/watsonx/index",
"providers/watsonx/audio_transcription",
]
},
"providers/xai",
"providers/xinference",
],

View File

@ -1357,6 +1357,9 @@ from .llms.azure.chat.o_series_transformation import AzureOpenAIO1Config
from .llms.watsonx.completion.transformation import IBMWatsonXAIConfig
from .llms.watsonx.chat.transformation import IBMWatsonXChatConfig
from .llms.watsonx.embed.transformation import IBMWatsonXEmbeddingConfig
from .llms.watsonx.audio_transcription.transformation import (
IBMWatsonXAudioTranscriptionConfig,
)
from .llms.github_copilot.chat.transformation import GithubCopilotConfig
from .llms.github_copilot.responses.transformation import (
GithubCopilotResponsesAPIConfig,

View File

@ -0,0 +1,78 @@
"""
Translates from OpenAI's `/v1/audio/transcriptions` to IBM WatsonX's `/ml/v1/audio/transcriptions`
WatsonX follows the OpenAI spec for audio transcription.
"""
from typing import List, Optional
import litellm
from litellm.types.llms.openai import OpenAIAudioTranscriptionOptionalParams
from ...openai.transcriptions.whisper_transformation import (
OpenAIWhisperAudioTranscriptionConfig,
)
from ..common_utils import IBMWatsonXMixin, _get_api_params
class IBMWatsonXAudioTranscriptionConfig(
IBMWatsonXMixin, OpenAIWhisperAudioTranscriptionConfig
):
"""
IBM WatsonX Audio Transcription Config
WatsonX follows the OpenAI spec for audio transcription, so this class
inherits from OpenAIWhisperAudioTranscriptionConfig and uses IBMWatsonXMixin
for authentication and URL construction.
"""
def get_supported_openai_params(
self, model: str
) -> List[OpenAIAudioTranscriptionOptionalParams]:
"""
Get the supported OpenAI params for WatsonX audio transcription.
"""
return [
"language",
"prompt",
"response_format",
"temperature",
"timestamp_granularities",
]
def get_complete_url(
self,
api_base: Optional[str],
api_key: Optional[str],
model: str,
optional_params: dict,
litellm_params: dict,
stream: Optional[bool] = None,
) -> str:
"""
Construct the complete URL for WatsonX audio transcription.
URL format: {api_base}/ml/v1/audio/transcriptions?version={version}&project_id={project_id}
"""
# Get base URL
url = self._get_base_url(api_base=api_base)
url = url.rstrip("/")
# Add the audio transcription endpoint
url = f"{url}/ml/v1/audio/transcriptions"
# Get API params for project_id
api_params = _get_api_params(params=optional_params.copy())
# Add version parameter
api_version = optional_params.pop(
"api_version", None
) or litellm.WATSONX_DEFAULT_API_VERSION
url = f"{url}?version={api_version}"
# Add project_id parameter
project_id = api_params.get("project_id")
if project_id:
url = f"{url}&project_id={project_id}"
return url

View File

@ -26209,6 +26209,15 @@
"supports_parallel_function_calling": false,
"supports_vision": false
},
"watsonx/whisper-large-v3-turbo": {
"input_cost_per_second": 0.0001,
"output_cost_per_second": 0.0001,
"litellm_provider": "watsonx",
"mode": "audio_transcription",
"supported_endpoints": [
"/v1/audio/transcriptions"
]
},
"whisper-1": {
"input_cost_per_second": 0.0001,
"litellm_provider": "openai",

View File

@ -7380,6 +7380,12 @@ class ProviderConfigManager:
)
return HostedVLLMAudioTranscriptionConfig()
elif litellm.LlmProviders.WATSONX == provider:
from litellm.llms.watsonx.audio_transcription.transformation import (
IBMWatsonXAudioTranscriptionConfig,
)
return IBMWatsonXAudioTranscriptionConfig()
return None
@staticmethod

View File

@ -26209,6 +26209,15 @@
"supports_parallel_function_calling": false,
"supports_vision": false
},
"watsonx/whisper-large-v3-turbo": {
"input_cost_per_second": 0.0001,
"output_cost_per_second": 0.0001,
"litellm_provider": "watsonx",
"mode": "audio_transcription",
"supported_endpoints": [
"/v1/audio/transcriptions"
]
},
"whisper-1": {
"input_cost_per_second": 0.0001,
"litellm_provider": "openai",

View File

@ -855,7 +855,7 @@
"responses": true,
"embeddings": true,
"image_generations": false,
"audio_transcriptions": false,
"audio_transcriptions": true,
"audio_speech": false,
"moderations": false,
"batches": false,

View File

@ -0,0 +1,102 @@
"""
Tests for IBM WatsonX Audio Transcription.
Validates that litellm.transcription transforms requests correctly for WatsonX.
"""
import os
import sys
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
sys.path.insert(0, os.path.abspath("../../../../.."))
import litellm
class TestWatsonXAudioTranscription:
"""Tests for WatsonX audio transcription via litellm.transcription."""
@pytest.mark.asyncio
async def test_watsonx_transcription_url_and_headers(self):
"""
Test that litellm.transcription sends request to correct WatsonX URL with proper headers.
"""
captured_request = {}
async def mock_post(*args, **kwargs):
captured_request["url"] = str(kwargs.get("url", args[0] if args else None))
captured_request["headers"] = kwargs.get("headers", {})
captured_request["data"] = kwargs.get("data", {})
mock_response = MagicMock()
mock_response.json.return_value = {
"text": "test transcription",
"duration": 1.0,
}
mock_response.status_code = 200
return mock_response
with patch("litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", new=mock_post):
try:
await litellm.atranscription(
model="watsonx/whisper-large-v3-turbo",
file=b"fake_audio_data",
api_base="https://us-south.ml.cloud.ibm.com",
api_key="test-api-key",
project_id="test-project-123",
token="test-bearer-token",
)
except Exception:
pass # We just want to capture the request
# Validate URL contains WatsonX audio transcription endpoint
assert "/ml/v1/audio/transcriptions" in captured_request["url"]
assert "version=" in captured_request["url"]
assert "project_id=test-project-123" in captured_request["url"]
# Validate headers contain WatsonX auth
assert "Authorization" in captured_request["headers"]
assert "Bearer test-bearer-token" in captured_request["headers"]["Authorization"]
@pytest.mark.asyncio
async def test_watsonx_transcription_request_body(self):
"""
Test that litellm.transcription sends correct request body for WatsonX.
"""
captured_request = {}
async def mock_post(*args, **kwargs):
captured_request["data"] = kwargs.get("data", {})
captured_request["files"] = kwargs.get("files", {})
mock_response = MagicMock()
mock_response.json.return_value = {
"text": "test transcription",
"duration": 1.0,
}
mock_response.status_code = 200
return mock_response
with patch("litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post", new=mock_post):
try:
await litellm.atranscription(
model="watsonx/whisper-large-v3-turbo",
file=b"fake_audio_data",
api_base="https://us-south.ml.cloud.ibm.com",
api_key="test-api-key",
project_id="test-project-123",
token="test-bearer-token",
language="en",
temperature=0.5,
)
except Exception:
pass # We just want to capture the request
# Validate request body contains expected fields
data = captured_request.get("data", {})
assert data.get("model") == "whisper-large-v3-turbo"
assert data.get("language") == "en"
assert data.get("temperature") == 0.5
assert data.get("response_format") == "verbose_json" # Default for cost calculation