Skip to content

fix: send whisper timestamp_granularities as bracketed array field - #36036

Open
richboyneedcash wants to merge 18 commits into
BerriAI:litellm_internal_stagingfrom
richboyneedcash:fix/whisper-timestamp-granularities-array
Open

fix: send whisper timestamp_granularities as bracketed array field#36036
richboyneedcash wants to merge 18 commits into
BerriAI:litellm_internal_stagingfrom
richboyneedcash:fix/whisper-timestamp-granularities-array

Conversation

@richboyneedcash

Copy link
Copy Markdown

Fixes #35937

Problem

Calling /v1/audio/transcriptions (whisper-1) with response_format="verbose_json" and a two-element timestamp_granularities array only honors the last element:

["segment"]         -> segments 10 words 0
["word"]            -> segments 0  words 54
["word", "segment"] -> segments 10 words 0   # segment wins
["segment", "word"] -> segments 0  words 54   # word wins

Calling OpenAI directly with ["segment", "word"] returns both.

Root cause

In whisper_transformation.py, transform_audio_transcription_request stores the list under the bare key timestamp_granularities. OpenAI's multipart form API expects array params as repeated bracketed fields (timestamp_granularities[]=segment + timestamp_granularities[]=word). With the bare key, OpenAI keeps only the last value.

This matches how the official openai-python client serializes array form fields — it uses array_format="brackets".

Fix

Rename the key to timestamp_granularities[] so httpx encodes it as the repeated bracketed fields OpenAI expects.

Verification

Added regression tests in test_whisper_transformation.py (multi-value, single-value, and absent cases). Confirmed end-to-end that the resulting multipart body now contains two timestamp_granularities[] fields (both segment and word), with no bare-key residual.

11 passed

Single-value arrays already worked and continue to. Note: this is distinct from the previously-fixed #12407 / #10282 (param dropped entirely); this addresses multi-value arrays being truncated to one element.

yuneng-berri and others added 18 commits July 21, 2026 19:03
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
_await_model_servable used poll_timeout (120s), the spend/log read-back
budget. A stuck model reload therefore stalled every suite that creates a
deployment for two minutes before failing

Give create_model a fixed harness middle ground: model_servable_timeout=40s,
polled every 2s, with each /v1/models call capped at 5s and clamped to the
remaining deadline so one slow GET cannot overrun the wait. Happy path still
returns on the first listing. Not derived from proxy general_settings or env

Transport.get accepts an optional per-call timeout for that clamp. Unit tests
cover the deadline arithmetic and clamp without a live proxy

(cherry picked from commit c082a0e)
create_model returned after the first /v1/models hit that listed the model,
so chat could still land on a cold gateway worker (numWorkers>1 / peer pod)
and 400 Invalid model name. Require continuous listing for the product
default add_deployment interval (30s) after first sight so every worker has
synced from the DB; first listing still bounded at 40s

(cherry picked from commit 7d1ee2f)
Keep the create_model DB-sync wait in the harness; the pure-function unit
file is not needed for this PR

(cherry picked from commit 8920465)
When less than one full poll interval remained in the first-listing budget,
the pre-sleep check returned NotServable without another /v1/models call.
Sleep only min(interval, time left) so a model that becomes listable in the
last seconds of the timeout still gets a clamped final poll

(cherry picked from commit 8439195)
A poll may start with remaining budget and still return after started+timeout
if the transport overruns its clamp. Recheck the first-listing deadline after
the response so a late listing does not open the continuous DB-sync phase

(cherry picked from commit 7ff2bcb)
…l_servable_timeout

test(e2e): bound the post-/model/new servable wait at 40s
* fix(mcp): resolve call_tool by registry without requiring tool map

Multi-worker reloads put MCP servers in the registry from the DB but do
not re-run tools/list on every process. Gating call_tool on
tool_name_to_mcp_server_name_mapping made cold workers 500 with Tool not
found after another worker had already listed the tool. Treat a registry
match on server id/name/alias as enough; upstream rejects unknown tools

* test(e2e): poll MCP register, tools/list, and tools/call across multi-worker lag

Stage multi-worker gateways only load MCP servers and tool maps on the
process that handled the request. Poll until the server is listed, the
tool appears on tools/list, and tools/call is not a cold-worker 500 so
key-access and Datadog MCP e2e stop racing the LB

* Revert "fix(mcp): resolve call_tool by registry without requiring tool map"

This reverts commit 8b56e51.

* test(e2e): tighten MCP multi-worker lag classifier

Only retry tools/call on gateway shapes Tool <name> not found and
server_not_found, not any 500 that mentions tool/server not found, so
upstream failures are not retried until the poll deadline

* test(e2e): drop unit file for MCP lag classifier

The live await_call_tool polls already cover multi-worker lag; a separate
string-match unit module is not worth keeping

(cherry picked from commit c274cf3)
…p_e2e_poll

test(e2e): poll MCP tools across multi-worker lag (BerriAI#35047)
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
chore(ci): promote internal staging to main
OpenAI's multipart transcription API expects array params as repeated
bracketed fields (timestamp_granularities[]=segment &
timestamp_granularities[]=word). LiteLLM stored the list under the bare
timestamp_granularities key, so OpenAI kept only the last value and a
combined ["segment", "word"] request silently returned just one
granularity (words OR segments, never both).

Rename the key to timestamp_granularities[] in
transform_audio_transcription_request so httpx encodes it as the repeated
bracketed fields OpenAI expects. This matches how the official
openai-python client serializes array form fields (array_format="brackets").

Fixes the multi-value case; single-value arrays already worked and
continue to.

Co-authored-by: TRAE CLI <noreply@bytedance.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR changes OpenAI Whisper timestamp granularity parameters to use the bracketed multipart-array field name and adds regression coverage for multiple, single, and absent values

  • Renames timestamp_granularities to timestamp_granularities[] during OpenAI Whisper request transformation
  • Adds focused transformation tests for the supported input cases

Confidence Score: 4/5

The functional change appears safe to merge after removing the newly added comments required by repository guidance

The OpenAI Whisper transformation now emits the bracketed array key on the established SDK path, and the only accepted concern is a non-blocking repository convention violation

Files Needing Attention: litellm/llms/openai/transcriptions/whisper_transformation.py, tests/test_litellm/llms/openai/transcriptions/test_whisper_transformation.py

Important Files Changed

Filename Overview
litellm/llms/openai/transcriptions/whisper_transformation.py Corrects the OpenAI-specific multipart field name, with a non-blocking repository-guidance violation from newly added comments
tests/test_litellm/llms/openai/transcriptions/test_whisper_transformation.py Adds focused regression cases for key renaming and omission, with one additional newly introduced inline comment

Reviews (1): Last reviewed commit: "fix: send whisper timestamp_granularitie..." | Re-trigger Greptile

Comment on lines +112 to +118
# OpenAI's multipart form API expects array params as repeated
# bracketed fields (`timestamp_granularities[]=segment` +
# `timestamp_granularities[]=word`). Sending the list under the bare
# `timestamp_granularities` key makes OpenAI keep only the last value,
# so a combined `["segment", "word"]` request silently returns just one
# granularity. Rename the key so the list is encoded as `[]` fields.
granularities = data.pop("timestamp_granularities", None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New explanatory comments violate guidance

The new production comment and the inline comment in test_whisper_transformation.py:78 violate the repository instruction prohibiting new comments unless explicitly requested, adding prose that must be removed to conform to project standards

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing richboyneedcash:fix/whisper-timestamp-granularities-array (7ec44c0) with main (ead6252)

Open in CodSpeed

@richboyneedcash

Copy link
Copy Markdown
Author

recheck

@richboyneedcash
richboyneedcash changed the base branch from main to litellm_internal_staging August 6, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants