From 74cad3a2c358b1d1b731696eed5f654172876535 Mon Sep 17 00:00:00 2001 From: Raunak Kumar Pandey <65064180+raunakpandey1@users.noreply.github.com> Date: Mon, 9 Nov 2026 16:54:57 -0500 Subject: [PATCH] fix(audio): measure duration for PathLike multipart tuple contents (#49806) * fix(audio): measure duration for PathLike multipart tuple contents * fix(audio): read a PathLike tuple's file directly instead of recursing * fix(audio): read a tuple's path in the existing path branch --------- Co-authored-by: ryan-crabbe-berri --- .../litellm_core_utils/audio_utils/utils.py | 9 ++++--- .../litellm_core_utils/test_audio_utils.py | 27 ++++++++++++------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/litellm/litellm_core_utils/audio_utils/utils.py b/litellm/litellm_core_utils/audio_utils/utils.py index 808a424815e..a89ab5b0c6c 100644 --- a/litellm/litellm_core_utils/audio_utils/utils.py +++ b/litellm/litellm_core_utils/audio_utils/utils.py @@ -301,9 +301,12 @@ def calculate_request_duration(file: FileTypes) -> float | None: "Pass bytes, an open file handle, a (filename, content) " "tuple, or a pathlib.Path." ) - elif isinstance(file, os.PathLike): - # File path (PathLike): SDK convenience. - with open(str(file), "rb") as f: + elif isinstance(file, os.PathLike) or ( + isinstance(file, tuple) and len(file) >= 2 and isinstance(file[1], os.PathLike) + ): + # File path (PathLike), bare or as the content of a (filename, path) tuple: SDK convenience. + path: Final = file if isinstance(file, os.PathLike) else file[1] + with open(str(path), "rb") as f: file_content = f.read() elif isinstance(file, tuple): # Tuple format: (filename, content, optional content_type) diff --git a/tests/unit/litellm_core_utils/test_audio_utils.py b/tests/unit/litellm_core_utils/test_audio_utils.py index 122445e8e30..520eeab12e9 100644 --- a/tests/unit/litellm_core_utils/test_audio_utils.py +++ b/tests/unit/litellm_core_utils/test_audio_utils.py @@ -6,6 +6,7 @@ import base64 import io import os import tempfile +from pathlib import Path from typing import Final from unittest.mock import mock_open, patch @@ -329,16 +330,12 @@ class TestCalculateRequestDuration: duration = calculate_request_duration(file_obj) # Verify it succeeded (returns a duration, not None) - assert ( - duration is not None - ), "Duration should be calculated even when BytesIO is at end" + assert duration is not None, "Duration should be calculated even when BytesIO is at end" assert isinstance(duration, float), "Duration should be a float" assert duration > 0, "Duration should be positive" # Verify the file position was restored - assert file_obj.tell() == len( - wav_header - ), "File position should be restored to original position" + assert file_obj.tell() == len(wav_header), "File position should be restored to original position" class TestGetAudioFileContentHash: @@ -364,9 +361,7 @@ class TestGetAudioFileContentHash: hash1 = get_audio_file_content_hash((filename1, content)) hash2 = get_audio_file_content_hash((filename2, content)) - assert ( - hash1 == hash2 - ), "Same content should produce same hash regardless of filename" + assert hash1 == hash2, "Same content should produce same hash regardless of filename" def test_bytes_input(self): """Test that raw bytes input works""" @@ -473,3 +468,17 @@ class TestSpeechMediaTypeFromAudioBytes: from litellm.litellm_core_utils.audio_utils.utils import speech_media_type_from_audio_bytes assert speech_media_type_from_audio_bytes(audio) == expected + + +@pytest.mark.parametrize("include_content_type", [False, True]) +def test_duration_reads_pathlike_content_in_multipart_tuple(tmp_path: Path, include_content_type: bool) -> None: + audio_path: Final = tmp_path / "sample.wav" + audio_path.write_bytes(base64.b64decode(_encoded_audio(2, 8000, 1, "WAV"))) + upload: Final = ("sample.wav", audio_path, "audio/wav") if include_content_type else ("sample.wav", audio_path) + assert calculate_request_duration(upload) == 2.0 + + +def test_duration_does_not_open_bare_string_tuple_content(tmp_path: Path) -> None: + audio_path: Final = tmp_path / "sample.wav" + audio_path.write_bytes(base64.b64decode(_encoded_audio(2, 8000, 1, "WAV"))) + assert calculate_request_duration(("sample.wav", str(audio_path))) is None