chore(release): backport #31036, #31342, #31653 to stable/1.90.x and cut 1.90.1 (litellm-enterprise 0.1.43.post1) - #31667
Merged
yuneng-berri merged 6 commits intoJun 30, 2026
Conversation
* fix(vertex/files): stream OpenAI->Vertex batch JSONL uploads to fix OOM on large files
Large (1GB+) batch JSONL uploads to Vertex AI / GCS caused OOM or killed the worker
because the request body was buffered and multiplied 2-3x in size. The create-file
path is now streaming end-to-end: transform_create_file_request returns a
ResumableChunkedUploadConfig carrying a lazy _OpenAIToVertexBatchUploadStream, and the
HTTP handler opens a GCS resumable session and PUTs the body in bounded 8 MiB chunks
(Content-Range, 308 between chunks) so the transformed payload is never held in full.
The proxy /v1/files endpoint streams from Starlette's spooled upload handle instead of
reading the whole body, and batch rate limiting counts tokens and models in a single
streaming pass.
Only gcs_bucket_name is supported for the GCS target; the legacy bucket_name key is
intentionally not read.
Also removes the unreachable VertexAIFilesHandler create path and everything only it
kept alive (VertexAIJsonlFilesTransformation, _stream_openai_jsonl_to_vertex, the legacy
transform helpers), plus the orphaned batch_utils helpers the streaming rewrite replaced.
* fix(batches): return original JSONL on unparseable row to avoid silent batch truncation
The streaming rewrite of replace_model_in_jsonl accumulated physical lines and
skipped a row on JSONDecodeError to support multi-line objects, but a genuinely
malformed or truncated row never completes: it poisons the buffer, swallows every
following row, and the function still returned the partial rewrite (the rows before
the bad one, already model-rewritten) as if the batch were complete. That turned the
pre-rewrite behavior of returning the original file unchanged (so the provider rejects
the bad batch loudly) into a silent partial submission.
Restore the original-content fallback: when an unparseable remainder is left after the
loop, return the original file_content (rewinding a consumed seekable source) instead of
the truncated output. The multi-line happy path is unchanged.
* test(batches): mock resumable GCS upload in vertex batch prediction test
The vertex batch file-create path now streams to a GCS resumable session via
_aresumable_chunked_upload (httpx send) instead of AsyncHTTPHandler.post, so the
existing test's post mock no longer intercepted the upload and a real request hit
GCS (401). Mock _aresumable_chunked_upload to return the GCS object response; the
resumable protocol itself is covered in test_vertex_ai_files_streaming.py.
* fix(batches): resilient per-row token accounting; no hard-block on count failure
The batch input-file pass iterated a generator whose json.loads raised on a
malformed line; the outer except caught it and stopped the loop, so any body.model
on rows after a bad line was never collected and the model allowlist check ran
against a partial set. It also hard-blocked the batch with a 400 whenever token
counting raised, a backwards-incompatible change from the prior swallow-and-proceed
behavior that breaks legitimate rows the token counter cannot measure (e.g. some
multimodal content).
Iterate the JSONL line-by-line and account each row independently. A malformed line
is skipped (its request cannot run upstream anyway) and a row the counter cannot
measure falls back to a conservative size-based estimate. The loop never aborts, so
the allowlist check always sees every parseable model, and the token total is never
zeroed, so a crafted uncountable row still cannot evade the TPM limit, without
hard-rejecting a legitimate batch.
* perf(vertex/files): unblock async upload; drop empty finalize; widen batch MIME types
Three review follow-ups on the resumable batch upload:
- _aresumable_chunked_upload pulled chunks from a synchronous generator that runs
the per-row transform inline on the event loop thread, blocking other requests
between PUTs on large uploads. Each chunk is now produced via asyncio.to_thread.
- _iter_resumable_chunks no longer yields a trailing empty chunk, so an exactly
chunk-aligned upload finalizes on its last data chunk instead of an extra
zero-byte PUT; a 0-byte stream still finalizes via the caller's empty request.
- valid_content_type now accepts the MIME types clients label .jsonl batch uploads
with (text/plain, application/json, ndjson, ...), so such a batch file no longer
silently bypasses the streaming path into the buffered media upload.
* fix(vertex/files): keep legacy bucket_name as GCS bucket fallback
The rename to gcs_bucket_name dropped the legacy bucket_name key entirely, so an SDK caller passing bucket_name to a Vertex AI file create/retrieve/content call with GCS_BUCKET_NAME unset got ValueError("GCS bucket_name is required") where it previously resolved the bucket. _get_configured_bucket_name now reads gcs_bucket_name, then bucket_name, then the env var, and bucket_name is restored to OPTIONAL_KWARGS_KEYS so it survives get_litellm_params on the retrieve and content paths. gcs_bucket_name keeps precedence when both are present
* style: sort imports in llm_http_handler to satisfy I001 budget
---------
Co-authored-by: Yuneng Jiang <yuneng@berri.ai>
(cherry picked from commit 5682592)
…#31342) * fix(proxy/client): redact api key from key/info client error messages The keys management client builds GET /key/info?key=<key> and lets the requests HTTPError propagate. str(HTTPError) renders the failing request URL verbatim ("... for url: .../key/info?key=sk-..."), so any caller that logs the exception leaks the full key; the 401 branch leaked the same way through UnauthorizedError(str(orig_exception)) Redact both branches with the existing redact_secrets helper so the secret-bearing query param is scrubbed to ?REDACTED while the status code, reason, and response object are preserved. Server-side responses already mask the key, so this closes the remaining client-side surface * fix: preserve key info unauthorized response --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> (cherry picked from commit 71ee1a8)
… on large uploads (#31653) * fix(vertex_ai/files): upload batch files in a single media request to fix 499s on large uploads PR #31036 switched the vertex batch file upload from a single GCS media upload to a chunked resumable session. The resumable path sends the body as many sequential PUTs, each waiting a full round-trip to GCS before the next, so a multi-GB upload accumulates hundreds of round-trips and overruns the client/load-balancer request timeout, surfacing as 499s (client closed connection) on files as small as 500MB. This was a regression from the last-known-good commit, where the upload completed as one continuous request. Revert the batch upload to a single uploadType=media request, but stage the transformed payload to a temp file first so peak memory stays bounded (the goal of the resumable rewrite) without the per-chunk round-trips. The temp file is closed deterministically (TemporaryFile unlinks on close), not left to the GC. The now-unused resumable chunked-upload plumbing is removed. Also swap the per-row transform's stdlib json for orjson (parse + serialize), which is ~4x faster on this hot path; the streaming body now emits compact orjson bytes. The request stays synchronous, so the returned file object is real and POST /v1/batches keeps working immediately against the uploaded object. Tests: single media request carries the whole payload with a real Content-Length (no chunked transfer-encoding); failed upload raises; the staged temp file is closed deterministically; byte-for-byte transform parity. * test(vertex_ai/files): mock single media upload POST instead of removed resumable method test_avertex_batch_prediction patched BaseLLMHTTPHandler._aresumable_chunked_upload, which was removed when the batch jsonl upload moved from a chunked resumable GCS session to a single uploadType=media request. Patch the raw httpx.AsyncClient.post that _astage_and_upload_media issues so the real staging, upload and response transform run while the GCS object response is mocked, and assert the media URL and Content-Type. * fix(vertex_ai/files): forward request timeout to media upload, drop orjson, sort imports Forward the per-request timeout through _stage_and_upload_media / _astage_and_upload_media to the GCS POST. Every other upload branch forwards it; the new media path was dropping it, so a caller-provided timeout was silently ignored (the files path passes 600s by default, but a custom request_timeout would not have reached this upload). Regression test asserts the resolved timeout reaches the request (mutation-verified). Revert the orjson swap in the batch transform: importing orjson at module load in this core-path file broke `import litellm` on environments without orjson (the Windows import test). Back to stdlib json; the upload leg dominates large uploads anyway, so the transform-side win was marginal. Fix import ordering in llm_http_handler.py (I001) introduced by the new imports. * fix(vertex_ai/files): stream batch upload to GCS instead of staging to a temp file Addresses a disk-exhaustion concern: staging the full transformed batch body to a local temp file before the GCS request meant an authenticated user could fill the proxy's temp volume with large concurrent uploads (on top of Starlette's input spool). GCS's simple/media upload accepts chunked transfer-encoding, so stream the transform straight to the single media request instead. Each block is produced on a worker thread (the transform never runs on the event loop) and sent chunked, so the body is neither buffered in memory nor written to disk, and the upload is still one continuous request (no per-chunk round-trips, no 499). Drops the temp-file staging, the tempfile/IO imports, and Content-Length computation. Regression test asserts the upload streams (chunked transfer-encoding, no Content-Length) and creates no temp file; mutation-verified that reintroducing staging fails it. (cherry picked from commit 85840ae)
Contributor
Greptile SummaryThis PR backports Vertex batch upload and proxy client reliability fixes to
Confidence Score: 5/5Safe to merge based on the reviewed changes. The changes are targeted backports with focused test coverage for the streaming upload paths, proxy file handling, batch utilities, and key redaction behavior. No blocking correctness or security issues were identified.
What T-Rex did
Reviews (1): Last reviewed commit: "chore: refresh uv.lock for 1.90.1 and li..." | Re-trigger Greptile |
2 tasks
blake-hamm
added a commit
to blake-hamm/bhamm-lab
that referenced
this pull request
Jul 4, 2026
…to v1.90.3 (#257) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [https://github.com/BerriAI/litellm.git](https://github.com/BerriAI/litellm) | patch | `v1.90.0` → `v1.90.3` | --- ### Release Notes <details> <summary>BerriAI/litellm (https://github.com/BerriAI/litellm.git)</summary> ### [`v1.90.3`](https://github.com/BerriAI/litellm/releases/tag/v1.90.3) [Compare Source](BerriAI/litellm@v1.90.2...v1.90.3) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.90.3 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.90.3/cosign.pub \ ghcr.io/berriai/litellm:v1.90.3 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - chore(release): backport [#​31923](BerriAI/litellm#31923), [#​31929](BerriAI/litellm#31929), [#​31393](BerriAI/litellm#31393) to stable/1.90.x and cut 1.90.3 by [@​mateo-berri](https://github.com/mateo-berri) in [#​32025](BerriAI/litellm#32025) **Full Changelog**: <BerriAI/litellm@v1.90.2...v1.90.3> ### [`v1.90.2`](https://github.com/BerriAI/litellm/releases/tag/v1.90.2) [Compare Source](BerriAI/litellm@v1.90.1...v1.90.2) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.90.2 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.90.2/cosign.pub \ ghcr.io/berriai/litellm:v1.90.2 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - chore(release): backport [#​31519](BerriAI/litellm#31519), [#​31733](BerriAI/litellm#31733) to stable/1.90.x and cut 1.90.2 by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31782](BerriAI/litellm#31782) **Full Changelog**: <BerriAI/litellm@v1.90.1...v1.90.2> ### [`v1.90.1`](https://github.com/BerriAI/litellm/releases/tag/v1.90.1) [Compare Source](BerriAI/litellm@v1.90.0-rc.1...v1.90.1) #### Verify Docker Image Signature All LiteLLM Docker images are signed with [cosign](https://docs.sigstore.dev/cosign/overview/). Every release is signed with the same key introduced in [commit `0112e53`](BerriAI/litellm@0112e53). **Verify using the pinned commit hash (recommended):** A commit hash is cryptographically immutable, so this is the strongest way to ensure you are using the original signing key: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/0112e53046018d726492c814b3644b7d376029d0/cosign.pub \ ghcr.io/berriai/litellm:v1.90.1 ``` **Verify using the release tag (convenience):** Tags are protected in this repository and resolve to the same key. This option is easier to read but relies on tag protection rules: ```bash cosign verify \ --key https://raw.githubusercontent.com/BerriAI/litellm/v1.90.1/cosign.pub \ ghcr.io/berriai/litellm:v1.90.1 ``` Expected output: ``` The following checks were performed on each of these signatures: - The cosign claims were validated - The signatures were verified against the specified public key ``` *** #### What's Changed - chore(release): backport [#​31036](BerriAI/litellm#31036), [#​31342](BerriAI/litellm#31342), [#​31653](BerriAI/litellm#31653) to stable/1.90.x and cut 1.90.1 (litellm-enterprise 0.1.43.post1) by [@​yuneng-berri](https://github.com/yuneng-berri) in [#​31667](BerriAI/litellm#31667) **Full Changelog**: <BerriAI/litellm@v1.90.0...v1.90.1> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiXX0=--> Co-authored-by: Renovate Bot <renovate@bhamm-lab.com> Reviewed-on: https://codeberg.org/blake-hamm/bhamm-lab/pulls/257
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relevant issues
Backports three already-merged staging fixes onto
stable/1.90.xand cuts1.90.1, plus thelitellm-enterprise0.1.43.post1required because one pick touches an enterprise source file. The headline reason is feature parity on upgrade:stable/1.88.xandstable/1.89.xcarry the streaming Vertex batch-upload feature (#31036) butstable/1.90.xdoes not, so a user upgrading from 1.89.x to 1.90.x silently loses large-file Vertex batch uploads. This brings #31036 to the line together with its follow-up #31653, so 1.90.x gets the fixed version of the feature (a single streaming media upload, not the chunked-resumable path that caused 499s) rather than the buggy interim state the older lines still carry. It also picks an unrelated client-side reliability fix (#31342) that scrubs a secret-bearing query param out ofkey/infoerror strings.There is no
litellmdouble-bump risk:1.90.0is already released (tagv1.90.0andrelease/v1.90.0both exist), so the picks cut1.90.1.0.1.43.post1is the correct enterprise increment:0.1.43is published and0.1.44is staging's next, so the post-release sorts0.1.43 < 0.1.43.post1 < 0.1.44.What is included
In cherry-pick (merge) order:
-xfrom staging56825926af7)-xfrom71ee1a8)-xfrom85840ae)Adaptation notes
Two picks are ADAPTED; #31342 applied verbatim.
#31036: the OOM-fix core (the vertex transformation and handler, the files endpoint, the router and batch utilities, the types and the enterprise managed-files hook) and all test files, including the new 696-line streaming suite, applied as on staging. One file diverges:
litellm/llms/custom_httpx/llm_http_handler.pyhad an import-block conflict from the pick's import-sort. Resolved by keeping the streaming body and dropping three imports the sort carried but that this line's code never references:functools.lru_cache,litellm.litellm_core_utils.asyncify.run_async_function, and a duplicateurllib.parseline. Their only consumer on staging,_responses_api_optional_request_param_names, is not part of #31036 and is absent on this line, so importing them here would be unused-import violations. Unlike the 1.88.x/1.89.x backports,RealtimeQueryParamsis genuinely used on this line, so it was kept.#31653: the upload-path rewrite conflicted against #31036's just-landed resumable code (ten regions in
llm_http_handler.pyand one intransformation.py), because #31653's upstream base also carried intermediate staging commits this line does not (a repo-wide ruff reformat and unrelated realtime/websearch fixes). Resolved by taking #31653'sstreaming_media_uploadrewrite for every region; the removed resumable code is wider-wrapped on this line, which accounts for the higher deletion count versus the upstream commit. TheStreamingMediaUploadConfigandBaseFileUploadStreamimports auto-merged. Verified no resumable-upload symbol remains referenced and the added test functions exactly match the upstream PR's own (no absorbed neighbor tests).Known noise on this line
The targeted test set (each picked PR's own test files plus the mirrored tests for every touched source) was fully green on the line tip before any pick once the proxy extras are synced: 242 passed, 0 failed. The only pre-existing lint nit on a touched file is a bare
# noqa(PGH004) inlitellm/types/router.py, present on the line tip before the picks;ruff checkon every touched module otherwise passes. The line'sruff formatbaseline is already dirty against itsline-length = 120config (the bulk of the code predates the width change), so no reformat was applied;ruff check, the enforced gate, passes.Screenshots / Proof of Fix
Proxy booted on the worktree against real provider keys, before and after the picks.
Before the picks:
After the picks (proxy auto-reloaded on the picked code; confirms the streaming rewrite and the client redaction do not regress startup or serving):
Targeted test deltas against the 242-passing baseline: 329 passed / 2 skipped after #31036 (including the new streaming suite), 29 passed for #31342's client redaction suite, 326 passed / 2 skipped after #31653 (the net decrease is the resumable-upload tests being replaced by the media-upload tests; zero failures throughout). A behavioral verification pass with adversarial subagents returned a clean verdict on all three sub-claims: every identifier the picked diffs reference resolves on this line, each pick's own tests pass, and no existing caller of the modified functions (the vertex transformation, the handler create-file paths, the proxy client) regresses. The only dissent was a cosmetic stale docstring on
StreamingMediaUploadConfigthat exists upstream as well, not a behavioral defect.The end-to-end large (1GB+) Vertex upload itself needs live GCS and Vertex service-account credentials, which were not available in this environment, so it is covered by the streaming suite rather than a live replay. To confirm on a real deployment: configure a Vertex AI model and a GCS bucket, then POST a >500MB batch JSONL to
/v1/fileswithpurpose=batchand submit it via/v1/batches; before #31653 the chunked-resumable upload accumulates hundreds of sequential round-trips and surfaces as a 499, after it the upload streams in one continuous media request and completes.Type
🐛 Bug Fix
🚄 Infrastructure
Changes
Brings the streaming Vertex batch-upload feature (#31036) and its single-media-upload follow-up (#31653) to
stable/1.90.xso upgrading from 1.89.x does not lose the feature, plus a client-side error-redaction fix (#31342), cutting1.90.1and thelitellm-enterprise0.1.43.post1required because #31036 touches an enterprise source file.