litellm/tests/audio_tests/test_whisper.py
yuneng-jiang 9a338e1b6b
[Test] Tests: Stop parametrizing API keys into pytest test IDs (#27249)
Several tests parametrized over (model, api_key, ...) tuples or raw
token strings, causing pytest to embed those values in the test ID
and print them in CI logs. Refactored each affected test to keep the
same coverage without putting key material into parametrize.

- audio_tests/test_audio_speech.py: split env-var keys into separate
  azure/openai test functions sharing a helper; sync_mode parametrize
  preserved.
- audio_tests/test_whisper.py: split into openai_whisper /
  azure_whisper functions sharing a helper; response_format parametrize
  preserved.
- local_testing/test_embedding.py: single-case parametrize inlined.
- proxy_unit_tests/test_user_api_key_auth.py: 5 header parametrize
  cases split into 5 named tests sharing an _assert helper.
- proxy_unit_tests/test_proxy_utils.py: 4 api_key_value cases split
  into 4 named tests.
- test_litellm/proxy/auth/test_user_api_key_auth.py: 5 key-prefix
  cases (Bearer / Basic / lowercase bearer / raw / AWS SigV4) split
  into 5 named tests.

Verified: black clean; 14 refactored unit tests pass; pytest collects
audio/embedding tests with safe IDs (no key material in test IDs).
2026-05-05 17:21:18 -07:00

293 lines
9.3 KiB
Python

# What is this?
## Tests `litellm.transcription` endpoint. Outside litellm module b/c of audio file used in testing (it's ~700kb).
import asyncio
import logging
import os
import sys
import time
import traceback
from typing import Optional
import aiohttp
import dotenv
import pytest
from dotenv import load_dotenv
from openai import AsyncOpenAI
import litellm
from litellm.integrations.custom_logger import CustomLogger
# Get the current directory of the file being run
pwd = os.path.dirname(os.path.realpath(__file__))
print(pwd)
file_path = os.path.join(pwd, "gettysburg.wav")
audio_file = open(file_path, "rb")
file2_path = os.path.join(pwd, "eagle.wav")
audio_file2 = open(file2_path, "rb")
load_dotenv()
sys.path.insert(
0, os.path.abspath("../")
) # Adds the parent directory to the system path
import litellm
from litellm import Router
async def _run_transcription(
model, api_key, api_base, response_format, timestamp_granularities
):
transcript = await litellm.atranscription(
model=model,
file=audio_file,
api_key=api_key,
api_base=api_base,
response_format=response_format,
timestamp_granularities=timestamp_granularities,
drop_params=True,
)
print(f"transcript: {transcript.model_dump()}")
print(f"transcript hidden params: {transcript._hidden_params}")
assert transcript.text is not None
@pytest.mark.parametrize(
"response_format, timestamp_granularities",
[("json", None), ("vtt", None), ("verbose_json", ["word"])],
)
@pytest.mark.asyncio
@pytest.mark.flaky(retries=3, delay=1)
async def test_transcription_openai_whisper(response_format, timestamp_granularities):
await _run_transcription(
model="whisper-1",
api_key=None,
api_base=None,
response_format=response_format,
timestamp_granularities=timestamp_granularities,
)
@pytest.mark.parametrize(
"response_format, timestamp_granularities",
[("json", None), ("vtt", None), ("verbose_json", ["word"])],
)
@pytest.mark.asyncio
@pytest.mark.flaky(retries=3, delay=1)
async def test_transcription_azure_whisper(response_format, timestamp_granularities):
await _run_transcription(
model="azure/whisper",
api_key=os.getenv("AZURE_WHISPER_API_KEY"),
api_base=os.getenv("AZURE_WHISPER_API_BASE"),
response_format=response_format,
timestamp_granularities=timestamp_granularities,
)
@pytest.mark.asyncio()
async def test_transcription_caching():
import litellm
from litellm.caching.caching import Cache
litellm.set_verbose = True
litellm.cache = Cache()
# make raw llm api call
response_1 = await litellm.atranscription(
model="whisper-1",
file=audio_file,
)
await asyncio.sleep(5)
# cache hit
response_2 = await litellm.atranscription(
model="whisper-1",
file=audio_file,
)
print("response_1", response_1)
print("response_2", response_2)
print("response2 hidden params", response_2._hidden_params)
assert response_2._hidden_params["cache_hit"] is True
# cache miss
response_3 = await litellm.atranscription(
model="whisper-1",
file=audio_file2,
)
print("response_3", response_3)
print("response3 hidden params", response_3._hidden_params)
assert response_3._hidden_params.get("cache_hit") is not True
assert response_3.text != response_2.text
litellm.cache = None
@pytest.mark.asyncio
async def test_whisper_log_pre_call():
from litellm.litellm_core_utils.litellm_logging import Logging
from datetime import datetime
from unittest.mock import patch, MagicMock
from litellm.integrations.custom_logger import CustomLogger
custom_logger = CustomLogger()
litellm.callbacks = [custom_logger]
with patch.object(custom_logger, "log_pre_api_call") as mock_log_pre_call:
await litellm.atranscription(
model="whisper-1",
file=audio_file,
)
mock_log_pre_call.assert_called_once()
@pytest.mark.asyncio
async def test_whisper_log_pre_call():
from litellm.litellm_core_utils.litellm_logging import Logging
from datetime import datetime
from unittest.mock import patch, MagicMock
from litellm.integrations.custom_logger import CustomLogger
custom_logger = CustomLogger()
litellm.callbacks = [custom_logger]
with patch.object(custom_logger, "log_pre_api_call") as mock_log_pre_call:
await litellm.atranscription(
model="whisper-1",
file=audio_file,
)
mock_log_pre_call.assert_called_once()
@pytest.mark.asyncio
async def test_gpt_4o_transcribe():
from litellm.litellm_core_utils.litellm_logging import Logging
from datetime import datetime
from unittest.mock import patch, MagicMock
await litellm.atranscription(
model="openai/gpt-4o-transcribe", file=audio_file, response_format="json"
)
@pytest.mark.asyncio
async def test_gpt_4o_transcribe_model_mapping():
"""Test that GPT-4o transcription models are correctly mapped and not hardcoded to whisper-1"""
# Test GPT-4o mini transcribe
response = await litellm.atranscription(
model="openai/gpt-4o-mini-transcribe", file=audio_file, response_format="json"
)
# Check that the response contains the correct model in hidden params
assert response._hidden_params is not None
assert response._hidden_params["model"] == "gpt-4o-mini-transcribe"
assert response._hidden_params["custom_llm_provider"] == "openai"
assert response.text is not None
# Test GPT-4o transcribe
response2 = await litellm.atranscription(
model="openai/gpt-4o-transcribe", file=audio_file, response_format="json"
)
# Check that the response contains the correct model in hidden params
assert response2._hidden_params is not None
assert response2._hidden_params["model"] == "gpt-4o-transcribe"
assert response2._hidden_params["custom_llm_provider"] == "openai"
assert response2.text is not None
# Test traditional whisper-1 still works
response3 = await litellm.atranscription(
model="openai/whisper-1", file=audio_file, response_format="json"
)
# Check that the response contains the correct model in hidden params
assert response3._hidden_params is not None
assert response3._hidden_params["model"] == "whisper-1"
assert response3._hidden_params["custom_llm_provider"] == "openai"
assert response3.text is not None
@pytest.mark.asyncio
async def test_azure_transcribe_model_mapping():
"""
Test that Azure transcription models are correctly mapped and not hardcoded to whisper-1.
This test validates that the request body contains the correct model parameter.
"""
from unittest.mock import AsyncMock, patch, MagicMock
from openai import AsyncAzureOpenAI
# Create a mock response that looks like OpenAI's transcription response (as a BaseModel)
from pydantic import BaseModel as PydanticBaseModel
class MockTranscriptionResponse(PydanticBaseModel):
text: str
mock_transcription_response = MockTranscriptionResponse(
text="This is a test transcription"
)
# Create mock raw response with headers and parse() method
mock_raw_response = MagicMock()
mock_raw_response.headers = {"content-type": "application/json"}
mock_raw_response.parse = MagicMock(return_value=mock_transcription_response)
# Create a mock Azure client instance
mock_azure_client = MagicMock(spec=AsyncAzureOpenAI)
mock_azure_client.audio.transcriptions.with_raw_response.create = AsyncMock(
return_value=mock_raw_response
)
mock_azure_client.api_key = "test-api-key"
mock_azure_client._base_url = MagicMock()
mock_azure_client._base_url._uri_reference = (
"https://my-endpoint-europe-berri-992.openai.azure.com/"
)
# Mock the get_azure_openai_client method to return our mock client
with patch(
"litellm.llms.azure.audio_transcriptions.AzureAudioTranscription.get_azure_openai_client",
return_value=mock_azure_client,
):
# Make the transcription call
response = await litellm.atranscription(
model="azure/whisper-1",
file=audio_file,
response_format="json",
api_key="test-api-key",
api_base="https://my-endpoint-europe-berri-992.openai.azure.com/",
api_version="2024-02-15-preview",
drop_params=True,
)
# Verify the create method was called
mock_azure_client.audio.transcriptions.with_raw_response.create.assert_called_once()
# Get the call arguments to validate the model parameter
call_kwargs = (
mock_azure_client.audio.transcriptions.with_raw_response.create.call_args.kwargs
)
# Assert that the model parameter is "whisper-1" (not hardcoded incorrectly)
assert (
call_kwargs["model"] == "whisper-1"
), f"Expected model 'whisper-1', got {call_kwargs['model']}"
assert "file" in call_kwargs
assert call_kwargs["response_format"] == "json"
# Check that the response contains the correct model in hidden params
assert response._hidden_params is not None
assert response._hidden_params["model"] == "whisper-1"
assert response._hidden_params["custom_llm_provider"] == "azure"
assert response.text is not None