From 380f0ed1a3d0560a4f53f710ad2841a3ee3ce536 Mon Sep 17 00:00:00 2001 From: Neimar Avila <19142978+neimaravila@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:19:36 -0400 Subject: [PATCH] fix(transcription): accept fractional usage.seconds in diarized_json responses gpt-4o-transcribe and compatible ASR backends return a diarized_json response with usage={"type": "duration", "seconds": }, e.g. 295.8. TranscriptionUsageDurationObject typed seconds as int, so parsing the response raised a pydantic ValidationError (int_from_float). That error surfaces as an APIConnectionError which the router treats as retryable, so it keeps re-calling the upstream (200 every time) until the upstream rate-limits and returns 429 to the caller. OpenAI specs this field as a float (see openai SDK UsageDuration.seconds), so widen seconds to float. With the parse succeeding there is no exception left to retry, which removes the loop. --- litellm/types/utils.py | 2 +- .../test_transcription_duration_hidden.py | 47 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 32bfc8835fe4..b48acc9ee2d1 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -2439,7 +2439,7 @@ def json(self, **kwargs): # type: ignore class TranscriptionUsageDurationObject(BaseModel): type: Literal["duration"] - seconds: int + seconds: float class TranscriptionUsageInputTokenDetailsObject(BaseModel): diff --git a/tests/test_litellm/llms/openai/transcriptions/test_transcription_duration_hidden.py b/tests/test_litellm/llms/openai/transcriptions/test_transcription_duration_hidden.py index 2b287e456a10..703fa13cbc9f 100644 --- a/tests/test_litellm/llms/openai/transcriptions/test_transcription_duration_hidden.py +++ b/tests/test_litellm/llms/openai/transcriptions/test_transcription_duration_hidden.py @@ -13,7 +13,52 @@ from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import ( convert_to_model_response_object, ) -from litellm.types.utils import TranscriptionResponse +from litellm.types.utils import ( + TranscriptionResponse, + TranscriptionUsageDurationObject, +) + + +class TestDiarizedJsonUsageParsing: + """gpt-4o-transcribe / diarized_json returns a fractional `usage.seconds`.""" + + def test_fractional_duration_seconds_does_not_raise(self): + """ + A diarized_json response carries usage={"type": "duration", "seconds": }. + OpenAI specs `seconds` as a float, so a fractional value must parse cleanly + instead of raising and getting retried until the upstream rate-limits. + """ + response_object = { + "text": "speaker_1: Olá", + "task": "transcribe", + "duration": 295.8, + "segments": [ + { + "id": "seg_001", + "speaker": "speaker_1", + "start": 0.0, + "end": 1.0, + "text": "Olá", + "type": "transcript.text.segment", + } + ], + "usage": {"type": "duration", "seconds": 295.8}, + } + + result = convert_to_model_response_object( + response_object=response_object, + model_response_object=TranscriptionResponse(), + response_type="audio_transcription", + ) + + assert isinstance(result.usage, TranscriptionUsageDurationObject) + assert result.usage.seconds == 295.8 + + def test_usage_duration_object_accepts_float_seconds(self): + assert ( + TranscriptionUsageDurationObject(type="duration", seconds=295.8).seconds + == 295.8 + ) class TestTranscriptionDurationNotInResponseBody: