fix(transcription): tolerate non-conforming usage in json response_format - #33769
Conversation
…rmat OpenAI-compatible servers such as llama.cpp can return a transcription usage object of type tokens that nulls or omits input_token_details (and other fields OpenAI always sends). Pydantic validation then raised and sank an otherwise successful transcription (#33764). Make input_token_details optional and route usage parsing through a helper that drops an unparseable usage object instead of crashing the request, so the transcription text is preserved
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
|
Greptile SummaryThis PR fixes a crash where transcription requests against non-OpenAI OpenAI-compatible servers (e.g. llama.cpp) would surface as an
Confidence Score: 5/5Safe to merge — the change is isolated to transcription usage parsing, is strictly more permissive than before, and all five new tests pass in-process with no network dependency. The fix is minimal and targeted: one field made optional, one helper added with a narrow exception catch, and the new behavior (drop usage on parse failure) is explicitly tested for every relevant shape. Existing OpenAI-shaped responses are covered by a regression test and continue to parse identically. No auth paths, no schema changes, and no backwards-incompatible behavior for callers that already received a working usage object. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/types/utils.py | Makes input_token_details optional (defaulting to None) in TranscriptionUsageTokensObject, directly fixing the pydantic ValidationError when non-OpenAI servers omit or null the field. |
| litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py | Introduces _parse_transcription_usage helper that uses model_validate with a ValidationError catch, so a malformed usage dict drops the usage field at debug-log level instead of propagating an APIConnectionError. |
| tests/test_litellm/llms/openai/transcriptions/test_transcription_duration_hidden.py | Adds TestTokensUsageParsingIsResilient with five new unit tests covering null input_token_details, missing field, full valid payload, completely invalid dict, and unknown type — all using in-process mock data with no real network calls. |
Reviews (1): Last reviewed commit: "fix(transcription): tolerate non-conform..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Reviewed this PR against the reported crash and also dug into the follow-up data @niosHD posted (the CI failures: The 3 failing checks ( On the So this isn't a second real schema litellm needs to parse — it's provider-specific extra data. Rather than adding fragile logic to map/recover that specific key, it'd be more robust (and consistent with the OpenAI SDK's own behavior) if |
Relevant issues
Fixes #33764
Linear ticket
Pre-Submission checklist
Type
🐛 Bug Fix
Changes
Transcription requests with
response_format=jsonagainst OpenAI-compatible servers that are not OpenAI (the reporter hit this with llama.cpp) could fail even though the server returned a perfectly good transcription. Those servers return ausageobject oftype: "tokens"whoseinput_token_detailsisnull(or absent), while OpenAI always populates it, soTranscriptionUsageTokensObject(**usage)raised a pydanticValidationErroratconvert_dict_to_response.py:837and the whole call surfaced as anAPIConnectionErrorTwo changes make usage parsing tolerant without losing data:
TranscriptionUsageInputTokenDetailsObjecton the tokens usage model is nowOptional[...] = None, so a null or missinginput_token_detailsstill parses and the token counts are preservedUsage construction now goes through a small
_parse_transcription_usagehelper that validates the dict against the duration or tokens model and returnsNone(logging at debug) when it does not conform, instead of throwing. A malformed or unrecognizedusageshape now drops just the usage field rather than sinking a successful transcription.usagewith the expected OpenAI shape is unchangedScreenshots / Proof of Fix
Reproduced end to end against a live proxy pointed at a local server that emulates llama.cpp's
response_format=jsontranscription response ({"text": ..., "usage": {"type": "tokens", ..., "input_token_details": null}}), which is exactly the shape the reporter's server returnsBefore the fix (parsing code at pre-fix
HEAD~1, proxy on :4001):After the fix (commit
a38f050944, proxy on :4000):Final Attestation
Link to Devin session: https://app.devin.ai/sessions/c11c456e42f74f77ba41e0697049d2d9