mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
refactor(soniox): return invalid form bool as a value and raise once in the handler
This commit is contained in:
parent
79cda2c2d4
commit
ef2a39b3f2
4 changed files with 55 additions and 7 deletions
|
|
@ -39,7 +39,9 @@ from litellm.llms.custom_httpx.http_handler import (
|
|||
from litellm.llms.soniox.audio_transcription.transformation import (
|
||||
SONIOX_HANDLER_ONLY_PARAMS,
|
||||
SonioxAudioTranscriptionConfig,
|
||||
SonioxInvalidBoolParam,
|
||||
decode_soniox_form_params,
|
||||
raise_soniox_form_error,
|
||||
)
|
||||
from litellm.llms.soniox.common_utils import (
|
||||
SONIOX_DEFAULT_CLEANUP,
|
||||
|
|
@ -228,6 +230,8 @@ class SonioxAudioTranscriptionHandler:
|
|||
base_url: Final = get_soniox_api_base(api_base)
|
||||
|
||||
decoded: Final = decode_soniox_form_params(optional_params)
|
||||
if isinstance(decoded, SonioxInvalidBoolParam):
|
||||
raise_soniox_form_error(decoded)
|
||||
|
||||
# Server-side clamps. Caller-supplied poll settings (from request kwargs)
|
||||
# are bounded so an authenticated caller cannot force a worker into a
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ contract of `base_llm_http_handler.audio_transcriptions`.
|
|||
"""
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Final
|
||||
from typing import Any, Final, NoReturn
|
||||
|
||||
from httpx import Headers, Response
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
|
|
@ -68,7 +69,19 @@ SONIOX_BOOL_PARAMS: Final[frozenset[str]] = frozenset(
|
|||
_JSON_CONTAINER: Final = TypeAdapter[dict[str, object] | list[object]](dict[str, object] | list[object])
|
||||
|
||||
|
||||
def _decode_form_value(key: str, value: object) -> object:
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SonioxInvalidBoolParam:
|
||||
key: str
|
||||
value: str
|
||||
|
||||
|
||||
def raise_soniox_form_error(error: SonioxInvalidBoolParam) -> NoReturn:
|
||||
raise SonioxException(
|
||||
message=f"`{error.key}` must be a boolean, got {error.value!r}", status_code=400, headers=None
|
||||
)
|
||||
|
||||
|
||||
def _decode_form_value(key: str, value: object) -> object | SonioxInvalidBoolParam:
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
if key in SONIOX_BOOL_PARAMS:
|
||||
|
|
@ -77,7 +90,7 @@ def _decode_form_value(key: str, value: object) -> object:
|
|||
return True
|
||||
if lowered in ("false", "0"):
|
||||
return False
|
||||
raise SonioxException(message=f"`{key}` must be a boolean, got {value!r}", status_code=400, headers=None)
|
||||
return SonioxInvalidBoolParam(key=key, value=value)
|
||||
if key in SONIOX_JSON_PARAMS and value.lstrip()[:1] in ("{", "["):
|
||||
try:
|
||||
return _JSON_CONTAINER.validate_json(value)
|
||||
|
|
@ -88,9 +101,15 @@ def _decode_form_value(key: str, value: object) -> object:
|
|||
return value
|
||||
|
||||
|
||||
def decode_soniox_form_params(optional_params: Mapping[str, object]) -> Mapping[str, object]:
|
||||
def decode_soniox_form_params(
|
||||
optional_params: Mapping[str, object],
|
||||
) -> Mapping[str, object] | SonioxInvalidBoolParam:
|
||||
"""Multipart form fields reach the proxy as strings; restore the JSON types Soniox expects."""
|
||||
return MappingProxyType({key: _decode_form_value(key, value) for key, value in optional_params.items()})
|
||||
decoded: Final = {key: _decode_form_value(key, value) for key, value in optional_params.items()}
|
||||
return next(
|
||||
(value for value in decoded.values() if isinstance(value, SonioxInvalidBoolParam)),
|
||||
MappingProxyType(decoded),
|
||||
)
|
||||
|
||||
|
||||
class SonioxAudioTranscriptionConfig(BaseAudioTranscriptionConfig):
|
||||
|
|
|
|||
|
|
@ -339,6 +339,24 @@ class TestPollLimitsClamping:
|
|||
a worker on tight poll loops.
|
||||
"""
|
||||
|
||||
def test_should_raise_400_when_form_bool_param_is_invalid(self):
|
||||
handler = SonioxAudioTranscriptionHandler()
|
||||
with pytest.raises(SonioxException) as exc_info:
|
||||
handler._prepare(
|
||||
audio_file=None,
|
||||
optional_params={
|
||||
"enable_speaker_diarization": "yes",
|
||||
"audio_url": "https://example.com/a.wav",
|
||||
},
|
||||
litellm_params={},
|
||||
api_key="sk-test",
|
||||
api_base=None,
|
||||
provider_config=SonioxAudioTranscriptionConfig(),
|
||||
headers={},
|
||||
)
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "enable_speaker_diarization" in str(exc_info.value)
|
||||
|
||||
def test_should_clamp_poll_interval_to_minimum(self):
|
||||
from litellm.llms.soniox.common_utils import SONIOX_MIN_POLL_INTERVAL
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import pytest
|
|||
|
||||
from litellm.llms.soniox.audio_transcription.transformation import (
|
||||
SonioxAudioTranscriptionConfig,
|
||||
SonioxInvalidBoolParam,
|
||||
decode_soniox_form_params,
|
||||
raise_soniox_form_error,
|
||||
)
|
||||
from litellm.llms.soniox.common_utils import SonioxException
|
||||
from litellm.llms.soniox.types import (
|
||||
|
|
@ -751,11 +753,16 @@ class TestDecodeSonioxFormParams:
|
|||
"language_hints_strict": expected,
|
||||
}
|
||||
|
||||
def test_should_raise_400_on_non_boolean_string(self):
|
||||
def test_should_return_error_value_on_non_boolean_string(self):
|
||||
result = decode_soniox_form_params({"context": '{"general": []}', "enable_speaker_diarization": "yes"})
|
||||
assert result == SonioxInvalidBoolParam(key="enable_speaker_diarization", value="yes")
|
||||
|
||||
def test_should_map_invalid_bool_error_to_400(self):
|
||||
with pytest.raises(SonioxException) as exc_info:
|
||||
decode_soniox_form_params({"enable_speaker_diarization": "yes"})
|
||||
raise_soniox_form_error(SonioxInvalidBoolParam(key="enable_speaker_diarization", value="yes"))
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "enable_speaker_diarization" in str(exc_info.value)
|
||||
assert "yes" in str(exc_info.value)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw, expected",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue