fix(proxy): import soundfile lazily in the transcribe logging handler

The transcribe handler imported soundfile at module top, and streaming_iterator
imports success_handler, which imports it in turn, so every /v1/messages stream
required the soundfile dependency even though nothing on that path reads audio.
Move the import into media_file_seconds() so it only loads when media duration
is actually measured (litellm#45379).

Signed-off-by: Saylee <saylee21@users.noreply.github.com>
This commit is contained in:
Saylee 2026-10-08 22:00:36 +05:30
parent f824d11a22
commit 9d1401f574
2 changed files with 53 additions and 1 deletions

View file

@ -13,7 +13,6 @@ from typing import IO, Final, Protocol, TypeAlias
from urllib.parse import quote
import httpx
import soundfile
from pydantic import ConfigDict, TypeAdapter, ValidationError
from typing_extensions import ReadOnly, TypedDict
@ -511,6 +510,8 @@ async def write_media_within_limit(response: httpx.Response, media_file: IO[byte
def media_file_seconds(path: Path) -> float | None:
import soundfile
try:
with soundfile.SoundFile(str(path)) as audio:
return len(audio) / audio.samplerate

View file

@ -1,15 +1,21 @@
import asyncio
import importlib
import io
import json
import sys
import wave
from datetime import datetime
from pathlib import Path
from typing import Final
from unittest.mock import MagicMock
import httpx
import pytest
import litellm
from litellm.proxy.pass_through_endpoints.llm_provider_handlers import (
transcribe_passthrough_logging_handler as transcribe_handler,
)
from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth
from litellm.proxy.pass_through_endpoints.llm_provider_handlers.transcribe_passthrough_logging_handler import (
TRANSCRIBE_MAX_MEDIA_DURATION_SECONDS,
@ -940,3 +946,48 @@ class TestIsTranscribeRoute:
)
assert normalized["kwargs"].get("model") != "transcribe/GetTranscriptionJob"
def test_soundfile_only_required_when_measuring_media(tmp_path: Path) -> None:
"""
The streaming path imports this handler through success_handler, so the handler
must import without soundfile, which is only loaded when media is measured (#45379).
"""
media: Final = tmp_path / "a.wav"
with wave.open(str(media), "wb") as out:
out.setnchannels(1)
out.setsampwidth(2)
out.setframerate(8000)
out.writeframes(bytes(2 * 12_000))
blocked: Final = "soundfile"
saved: Final = sys.modules.get(blocked)
sys.modules[blocked] = None
try:
with pytest.raises(ImportError):
media_file_seconds(media)
finally:
if saved is None:
_ = sys.modules.pop(blocked, None)
else:
sys.modules[blocked] = saved
def test_handler_module_imports_without_soundfile() -> None:
alt: Final = f"{transcribe_handler.__name__}.no_soundfile_import_probe"
source: Final = str(transcribe_handler.__file__)
blocked: Final = "soundfile"
saved: Final = sys.modules.get(blocked)
sys.modules[blocked] = None
try:
spec: Final = importlib.util.spec_from_file_location(alt, source)
assert spec is not None and spec.loader is not None
module: Final = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
assert hasattr(module, "media_file_seconds")
finally:
sys.modules.pop(alt, None)
if saved is None:
_ = sys.modules.pop(blocked, None)
else:
sys.modules[blocked] = saved