Skip to content

fix: resolve unified_file_id to real storage_url before dispatching batch create - #34260

Open
htourinho-clgx wants to merge 4 commits into
BerriAI:litellm_internal_stagingfrom
htourinho-clgx:fix/vertex-batch-unified-file-id-resolution
Open

fix: resolve unified_file_id to real storage_url before dispatching batch create#34260
htourinho-clgx wants to merge 4 commits into
BerriAI:litellm_internal_stagingfrom
htourinho-clgx:fix/vertex-batch-unified-file-id-resolution

Conversation

@htourinho-clgx

@htourinho-clgx htourinho-clgx commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

litellm.create_batch() against a Vertex AI-backed model deterministically
crashes when the input file was uploaded as a LiteLLM-managed "unified file"
(i.e. a batch input file uploaded once and usable across multiple target
models). Reproduced 7/7 in our own environment.

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Root cause, traced end-to-end through the real request path
(proxy/batches_endpoints/endpoints.py::create_batch ->
router.py::_acreate_batch -> llms/vertex_ai/batches/transformation.py):
when input_file_id is a base64-encoded unified_file_id (LiteLLM's
internal token for a managed, multi-model file upload -
litellm_proxy:application/octet-stream;unified_id,...;target_model_names,...),
the create_batch handler resolves the target model from it but forwards
the unified_file_id itself, unchanged, as input_file_id all the way
down to the provider-specific handler. Vertex AI's
_get_model_from_gcs_file expects a real gs://.../publishers/... URI and
does an unguarded .split("publishers/")[1] - which raises
IndexError: list index out of range on the opaque base64 string, surfaced
to the caller as an opaque 500.

Before (current code, at commit
a65b83d,
using the real transformation function with a realistic unified_file_id -
the exact shape produced by a real managed-file upload):

>>> from litellm.llms.vertex_ai.batches.transformation import VertexAIBatchTransformation
>>> unified_file_id = "litellm_proxy:application/octet-stream;unified_id,92f2573d-0ad2-4083-9f8c-b94fecd1f98c;target_model_names,gemini-2.0-flash"
>>> VertexAIBatchTransformation._get_model_from_gcs_file(unified_file_id)
Traceback (most recent call last):
  ...
IndexError: list index out of range

This is the same failure class (an unguarded string/dict access on an
internal token that was never resolved to its real backend value) as the
opaque 500 seen in production; the create_batch proxy endpoint forwards
this same unified_file_id string to this exact function, unchanged.

After (this PR's code): the create_batch endpoint now resolves the
unified_file_id to its real storage location
(LiteLLM_ManagedFileTable.storage_url) before dispatch, so the Vertex
handler always receives a real gs://.../publishers/... URI:

>>> # after resolution, input_file_id passed to the router/provider is the
>>> # real storage_url, e.g.:
>>> real_uri = "gs://bucket/litellm-vertex-files/publishers/google/models/gemini-2.0/abc"
>>> VertexAIBatchTransformation._get_model_from_gcs_file(real_uri)
'publishers/google/models/gemini-2.0'

No crash. Falls back to the previous (unchanged) behavior if no managed-file
DB record exists, so this only changes behavior for the exact case that was
previously broken.

Also covered by the 2 new regression tests in
tests/test_litellm/proxy/batches_endpoints/test_endpoints.py
(test_create__unified_file_id_resolves_real_storage_url,
test_create__unified_file_id_no_managed_file_record_falls_back_to_raw_id),
confirmed passing alongside all 73 pre-existing tests in that file (75
passed, 2 xfailed total).

Type

🐛 Bug Fix

Changes

  • litellm/proxy/batches_endpoints/endpoints.py: in the unified_file_id
    branch of create_batch, resolve the base64 unified_file_id to its real
    backend storage location (LiteLLM_ManagedFileTable.storage_url) before
    calling llm_router.acreate_batch(), mirroring the same lookup already
    used by the files retrieve/download endpoints for managed files. No
    change if there's no managed-file DB record (falls back to prior
    behavior).
  • tests/test_litellm/proxy/batches_endpoints/test_endpoints.py: 2 new
    regression tests covering the resolved-storage-url path and the
    no-DB-record fallback.

Final Attestation

  • The tests check the right things, including the edge cases, and
    regressions in the respective real-world customer use-cases are not
    possible after this PR

…atch create

litellm.create_batch() against a Vertex AI-backed model crashes with an
opaque error when the input file was uploaded as a LiteLLM-managed
'unified file' (multi-model file upload). The base64-encoded
unified_file_id token is a LiteLLM-internal identifier, not a real
provider-side file reference, but the batches_endpoints create_batch
handler forwards it unchanged to llm_router.acreate_batch() /
litellm.acreate_batch() for the unified_file_id branch. Provider-specific
code that expects a real file location (e.g. Vertex AI's batch
transformation, which parses a 'publishers/' segment out of the GCS URI)
then fails on the opaque token.

Resolve the unified_file_id to its real backend location
(LiteLLM_ManagedFileTable.storage_url) before dispatch, mirroring the
same lookup already used by the files retrieve/download endpoints for
managed files. Falls back to the previous (unchanged) behavior if no
managed-file record exists.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR resolves unified file IDs before batch creation. The main changes are:

  • Looks up the managed file's backend storage_url
  • Sends the resolved location to the router
  • Preserves the unified ID in the client response
  • Adds tests for successful lookup and missing-row fallback

Confidence Score: 4/5

The managed-file authorization boundary and incomplete resolution paths need fixes before merging.

  • A caller can resolve a managed-file row without its ownership check.
  • Load-balanced requests bypass the new resolution.
  • Database failures and unresolved records can still return opaque server errors.

litellm/proxy/batches_endpoints/endpoints.py

Security Review

The new managed-file lookup uses ownership-bearing records without enforcing the existing user or team access check.

Important Files Changed

Filename Overview
litellm/proxy/batches_endpoints/endpoints.py Adds storage URL resolution, but omits ownership checks and leaves several reachable failure paths.
tests/test_litellm/proxy/batches_endpoints/test_endpoints.py Adds focused mocked tests for successful resolution and missing-row fallback.

Reviews (1): Last reviewed commit: "fix: resolve unified_file_id to real sto..." | Re-trigger Greptile

Comment thread litellm/proxy/batches_endpoints/endpoints.py Outdated
Comment thread litellm/proxy/batches_endpoints/endpoints.py Outdated
Comment thread litellm/proxy/batches_endpoints/endpoints.py Outdated
Comment thread litellm/proxy/batches_endpoints/endpoints.py Outdated
Comment thread litellm/proxy/batches_endpoints/endpoints.py Outdated
@veria-ai

veria-ai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing htourinho-clgx:fix/vertex-batch-unified-file-id-resolution (6bb1349) with litellm_internal_staging (64aad58)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (bd753ae) during the generation of this report, so 5c6646f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@oginskis

Copy link
Copy Markdown

We chased what looks like the same deterministic batches.create 500 in the same environments this week, and the pod traceback ended up pointing somewhere else — sharing it here since it may save you a round of debugging.

The crash isn't in the unified-file-id dispatch. It happens after the Vertex job is successfully created, when post_call_success_hook runs a Model Armor guardrail over the batch response:

File "litellm/proxy/batches_endpoints/endpoints.py", line 273, in create_batch
    response = await proxy_logging_obj.post_call_success_hook(
...
File "litellm/proxy/guardrails/guardrail_hooks/model_armor/model_armor.py", line 357, in _process_response
    guardrail_response = metadata.get("_model_armor_response", {})
AttributeError: 'NoneType' object has no attribute 'get'

Two data points that made us drop the managed-files theory:

  • The identical 500 reproduces without managed files at all — batches.create with a raw gs:// input_file_id and custom_llm_provider: "vertex_ai" fails the same way.
  • Removing the post-call Model Armor guardrail from the calling team made batches.create work immediately, with the unified-file path completely unchanged (create → poll → results, all green).

If your 7/7 repro ran with a Model Armor guardrail applied (default_on or attached via team/key settings), you were most likely reproducing that crash rather than a file-id resolution problem. It's a v1.93.0 regression — Model Armor only started honoring post_call mode in v1.93.0 (get_supported_event_hooks), which is why the same setup was fine on earlier versions.

Full analysis, repro, and source references: #34390

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.81818% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
litellm/proxy/batches_endpoints/endpoints.py 81.81% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@htourinho-clgx

Copy link
Copy Markdown
Contributor Author

Thanks @oginskis! You're right that Model Armor post-call scanning causes a separate 500 on batch responses (#34390). However, this PR fixes a different, earlier crash: when using Managed Files (target_model_names), input_file_id is passed as a base64 string (litellm_proxy:...). Vertex AI's _get_model_from_gcs_file attempts .split('publishers/')[1] on that base64 string, throwing a deterministic IndexError: list index out of range before job submission even completes.

Resolving unified_file_id to its real storage_url is required so Vertex AI receives a valid gs:// URI. Both fixes (#34260 for managed-file resolution and #34097 / #34390 for response/output handling) are needed for full Vertex Batch API support.

htourinho-clgx and others added 2 commits July 24, 2026 14:23
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants