diff --git a/litellm/proxy/batches_endpoints/endpoints.py b/litellm/proxy/batches_endpoints/endpoints.py index c1bf92d58a2..af9aa901022 100644 --- a/litellm/proxy/batches_endpoints/endpoints.py +++ b/litellm/proxy/batches_endpoints/endpoints.py @@ -49,11 +49,12 @@ async def _resolve_managed_input_file_storage_url(input_file_id: str) -> "str | Provider batch handlers (e.g. Vertex AI, which parses a `publishers/` segment out of the file URI) need a real storage location; the opaque - unified token crashes them. Returns None when there is no database, the - lookup fails, or the row has no storage_url yet, so callers fall back to - the original id (which the managed-files deployment hook can still map). - Raises a 404 when no managed-file row exists, since the token cannot be - resolved and dispatching it would crash the provider. + unified token crashes them. Returns None only when there is no database or + the row has no storage_url yet, so callers fall back to the original id + (which the managed-files deployment hook can still map). Fails closed + rather than dispatch a token that cannot be resolved: 404 when no + managed-file row exists, 503 when the lookup itself errors so the caller + can retry. """ from litellm.proxy.proxy_server import prisma_client @@ -63,7 +64,10 @@ async def _resolve_managed_input_file_storage_url(input_file_id: str) -> "str | db_file = await ManagedFileRepository(prisma_client).table.find_first(where={"unified_file_id": input_file_id}) except Exception as e: verbose_proxy_logger.warning("create_batch: managed file lookup failed for %s: %s", input_file_id, e) - return None + raise HTTPException( + status_code=503, + detail={"error": "Could not resolve managed file; please retry"}, + ) if db_file is None: raise HTTPException( status_code=404, diff --git a/tests/test_litellm/proxy/batches_endpoints/test_endpoints.py b/tests/test_litellm/proxy/batches_endpoints/test_endpoints.py index b18d6ab86e3..fe09241ee5c 100644 --- a/tests/test_litellm/proxy/batches_endpoints/test_endpoints.py +++ b/tests/test_litellm/proxy/batches_endpoints/test_endpoints.py @@ -561,10 +561,10 @@ async def test_create__unified_file_id_resolves_real_storage_url(harness): @pytest.mark.asyncio -async def test_create__unified_file_id_db_error_falls_back_to_raw_id(harness): - """A lookup failure must not abort batch creation. Resolution is best-effort - crash prevention, so on a database error it falls back to dispatching the - original id, which the managed-files deployment hook can still map.""" +async def test_create__unified_file_id_db_error_fails_closed_503(harness): + """A lookup error leaves the token unresolved, so it fails closed with a + retryable 503 rather than dispatching the opaque id into the provider crash + it cannot parse. Nothing is dispatched.""" set_body( harness, { @@ -585,9 +585,12 @@ async def test_create__unified_file_id_db_error_falls_back_to_raw_id(harness): patch.object(proxy_server, "prisma_client", MagicMock()), patch.object(endpoints, "ManagedFileRepository", fake_repo_cls), ): - await call_create(harness) + with pytest.raises(ProxyException) as exc: + await call_create(harness) - assert harness.router_kwargs()["input_file_id"] == "litellm_proxy_unified_id" + assert exc.value.code == "503" + harness.router_acreate.assert_not_called() + harness.litellm_acreate.assert_not_called() @pytest.mark.asyncio