From e2dad6c914d337471ef9d8b6c08db72502b5f16b Mon Sep 17 00:00:00 2001 From: kerry Date: Fri, 13 Nov 2026 00:52:40 +0000 Subject: [PATCH] fix(files): reduce any scheme or case wrapping files/ the way gemini does before the ownership lookup Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../openai_files_endpoints/common_utils.py | 24 +++++++++++++++---- .../proxy/hooks/test_managed_files.py | 5 ++-- .../test_files_common_utils.py | 13 ++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/openai_files_endpoints/common_utils.py b/litellm/proxy/openai_files_endpoints/common_utils.py index e4b6fc9290e..be4ef57f1fb 100644 --- a/litellm/proxy/openai_files_endpoints/common_utils.py +++ b/litellm/proxy/openai_files_endpoints/common_utils.py @@ -3,7 +3,7 @@ import itertools import logging import mimetypes import re -from collections.abc import Mapping, Sequence +from collections.abc import Iterator, Mapping, Sequence from dataclasses import dataclass, field from datetime import datetime, timezone from types import MappingProxyType @@ -457,11 +457,27 @@ def canonical_provider_file_id(file_id: str) -> str: _GEMINI_FILES_URL: Final = "https://generativelanguage.googleapis.com/v1beta/files/" +def _gemini_files_tail(file_id: str) -> str: + """The id Gemini resolves ``file_id`` to, whatever scheme or case wraps it: the text after its ``files/``.""" + files_index: Final = file_id.find("files/") + return file_id[files_index:].strip("/").removeprefix("files/").strip("/") if files_index != -1 else "" + + +def _iter_stored_forms(bare_ids: Sequence[str]) -> Iterator[str]: + for bare_id in bare_ids: + yield bare_id + yield f"files/{bare_id}" + yield f"{_GEMINI_FILES_URL}{bare_id}" + + def provider_file_id_spellings(file_id: str) -> tuple[str, ...]: """Every spelling an ownership row may hold for ``file_id``: as sent, unwrapped from a model-encoded id and - canonicalized, plus the ``files/`` and Gemini URL forms that upload rows stored before ids were canonicalized.""" - canonical: Final = canonical_provider_file_id(get_original_file_id(file_id)) - return tuple(dict.fromkeys((file_id, canonical, f"files/{canonical}", f"{_GEMINI_FILES_URL}{canonical}"))) + canonicalized, reduced the way Gemini reduces it, plus the ``files/`` and Gemini URL forms that upload rows + stored before ids were canonicalized.""" + original: Final = get_original_file_id(file_id) + reductions: Final = (canonical_provider_file_id(original), _gemini_files_tail(unquote(original))) + bare_ids: Final = tuple(dict.fromkeys(bare for bare in reductions if bare)) + return tuple(dict.fromkeys((file_id, *_iter_stored_forms(bare_ids)))) def is_model_embedded_id(file_id: str) -> bool: diff --git a/tests/unit/enterprise/proxy/hooks/test_managed_files.py b/tests/unit/enterprise/proxy/hooks/test_managed_files.py index 40f33d8e78b..1af55932964 100644 --- a/tests/unit/enterprise/proxy/hooks/test_managed_files.py +++ b/tests/unit/enterprise/proxy/hooks/test_managed_files.py @@ -61,8 +61,7 @@ class _InMemoryManagedFileTable: or_filters: Final = where.get("OR") if isinstance(or_filters, list): for clause in or_filters: - matched: Final = await self.find_first(clause) - if matched is not None: + if (matched := await self.find_first(clause)) is not None: return matched return None unified_file_id: Final = where.get("unified_file_id") @@ -6581,6 +6580,8 @@ _GEMINI_OUTPUT_SPELLINGS: Final = ( "files%2Fbatch-out1", "batch-out1/", "https://generativelanguage.googleapis.com/v1beta/files/batch-out1", + "HTTPS://generativelanguage.googleapis.com/v1beta/files/batch-out1", + "x://h/files/batch-out1", ) diff --git a/tests/unit/proxy/openai_files_endpoint/test_files_common_utils.py b/tests/unit/proxy/openai_files_endpoint/test_files_common_utils.py index ac6cf88b477..80655415e76 100644 --- a/tests/unit/proxy/openai_files_endpoint/test_files_common_utils.py +++ b/tests/unit/proxy/openai_files_endpoint/test_files_common_utils.py @@ -1003,3 +1003,16 @@ def test_provider_file_id_spellings_unwraps_a_model_encoded_id_and_covers_the_st ) for spelling in ("batch-out1", "files%2Fbatch-out1", "batch-out1/"): assert set(provider_file_id_spellings(spelling)) >= set(provider_file_id_spellings(wrapped)[1:]), spelling + + +@pytest.mark.parametrize( + "respelling", + ( + "HTTPS://generativelanguage.googleapis.com/v1beta/files/batch-out1", + "x://h/files/batch-out1", + "S3://b/files/batch-out1", + "x%3A%2F%2Fh%2Ffiles%2Fbatch-out1", + ), +) +def test_provider_file_id_spellings_reduce_any_scheme_the_way_gemini_does(respelling: str): + assert "batch-out1" in provider_file_id_spellings(respelling)