mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-11 08:38:24 +00:00
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 <ryan@berri.ai>
This commit is contained in:
parent
0b3558d924
commit
74cad3a2c3
2 changed files with 24 additions and 12 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue