mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(batches): let provider-filtered list requests bypass managed-files listing
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
f05d468769
commit
f25f4a68ad
5 changed files with 48 additions and 38 deletions
|
|
@ -344,16 +344,18 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints):
|
|||
# This is because the encoded object ids stored in the managed objects table do not contain the provider information
|
||||
# To support provider filtering, we would need to store the provider information in the encoded object ids
|
||||
if provider:
|
||||
raise Exception(
|
||||
"Filtering by 'provider' is not supported when using managed batches."
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Filtering by 'provider' is not supported when using managed batches.",
|
||||
)
|
||||
|
||||
# Model name filtering is not supported for managed batches
|
||||
# This is because the encoded object ids stored in the managed objects table do not contain the model name
|
||||
# A hash of the model name + litellm_params for the model name is encoded as the model id. This is not sufficient to reliably map the target model names to the model ids.
|
||||
if target_model_names:
|
||||
raise Exception(
|
||||
"Filtering by 'target_model_names' is not supported when using managed batches."
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Filtering by 'target_model_names' is not supported when using managed batches.",
|
||||
)
|
||||
|
||||
owner_filter = build_owner_filter(user_api_key_dict)
|
||||
|
|
|
|||
|
|
@ -682,7 +682,12 @@ async def list_batches(
|
|||
|
||||
# Try to use managed objects table for listing batches (returns encoded IDs).
|
||||
managed_files_obj: Final = proxy_logging_obj.get_proxy_hook("managed_files")
|
||||
if managed_files_obj is not None and hasattr(managed_files_obj, "list_user_batches"):
|
||||
if (
|
||||
managed_files_obj is not None
|
||||
and hasattr(managed_files_obj, "list_user_batches")
|
||||
and not provider
|
||||
and not target_model_names
|
||||
):
|
||||
verbose_proxy_logger.debug("Using managed objects table for batch listing")
|
||||
response = await cast(Any, managed_files_obj).list_user_batches(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
|
|
|
|||
|
|
@ -295,31 +295,10 @@ def test_batch_lifecycle(
|
|||
)
|
||||
|
||||
if cap.can_list:
|
||||
list_result = client.list_batches(key=key, provider=provider)
|
||||
managed_filter_unsupported = False
|
||||
match list_result:
|
||||
case UnknownApiError(body=body) if (
|
||||
"Filtering by 'provider' is not supported when using managed batches" in body
|
||||
):
|
||||
managed_filter_unsupported = True
|
||||
listed = unwrap(client.list_batches(key=key, provider=None))
|
||||
case _:
|
||||
listed = unwrap(list_result)
|
||||
listed = unwrap(client.list_batches(key=key, provider=provider))
|
||||
if listed.object is not None:
|
||||
assert listed.object == "list", f"list envelope object={listed.object!r}"
|
||||
match = next((b for b in listed.data if b.id == batch.id), None)
|
||||
if (
|
||||
match is None
|
||||
and managed_filter_unsupported
|
||||
and cap.scenario == "provider_fallback"
|
||||
):
|
||||
# provider_fallback keeps the provider's raw batch id (not re-encoded
|
||||
# into a managed/proxy id). When the gateway rejects provider-scoped
|
||||
# list, the only available list is the unfiltered managed view, which
|
||||
# does not index raw provider ids. Membership cannot be asserted here;
|
||||
# create + retrieve (and raw_id_matches_provider above) already pin
|
||||
# routing for this scenario.
|
||||
return
|
||||
assert match is not None, "created batch absent from list"
|
||||
assert match.object == "batch"
|
||||
|
||||
|
|
|
|||
|
|
@ -2007,15 +2007,16 @@ async def test_list_batches_from_managed_objects_table_provider_filter_raises_ex
|
|||
DualCache(), prisma_client=prisma_client
|
||||
)
|
||||
|
||||
# Filtering by provider should raise Exception
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
# Filtering by provider should raise HTTPException
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await proxy_managed_files.list_user_batches(
|
||||
user_api_key_dict=UserAPIKeyAuth(user_id="test-user"),
|
||||
limit=10,
|
||||
provider="openai",
|
||||
)
|
||||
|
||||
assert str(exc_info.value) == (
|
||||
assert exc_info.value.status_code == 400
|
||||
assert exc_info.value.detail == (
|
||||
"Filtering by 'provider' is not supported when using managed batches."
|
||||
)
|
||||
|
||||
|
|
@ -2033,15 +2034,16 @@ async def test_list_batches_from_managed_objects_table_target_model_name_filter_
|
|||
DualCache(), prisma_client=prisma_client
|
||||
)
|
||||
|
||||
# Filtering by provider should raise Exception
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
# Filtering by target_model_names should raise HTTPException
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await proxy_managed_files.list_user_batches(
|
||||
user_api_key_dict=UserAPIKeyAuth(user_id="test-user"),
|
||||
limit=10,
|
||||
target_model_names="gpt-5.5,gpt-3.5",
|
||||
)
|
||||
|
||||
assert str(exc_info.value) == (
|
||||
assert exc_info.value.status_code == 400
|
||||
assert exc_info.value.detail == (
|
||||
"Filtering by 'target_model_names' is not supported when using managed batches."
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1461,7 +1461,10 @@ async def call_list(
|
|||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Branch 1 - ManagedObjectTable listing. This is the default production path
|
||||
# (the managed_files hook is registered) and wins over every other branch.
|
||||
# (the managed_files hook is registered) and wins over every other branch, as
|
||||
# long as the request carries no provider / target_model_names filter: the
|
||||
# managed objects table cannot satisfy either, so filtered requests fall
|
||||
# through to the provider seams instead of 500ing out of the hook.
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
|
||||
|
|
@ -1476,8 +1479,6 @@ async def test_list__managed_files_path(list_harness):
|
|||
user=user,
|
||||
limit=7,
|
||||
after="batch-cursor",
|
||||
provider="openai",
|
||||
target_model_names="m1,m2",
|
||||
)
|
||||
|
||||
# DISPATCH - managed-files seam fired, neither provider seam did.
|
||||
|
|
@ -1485,8 +1486,8 @@ async def test_list__managed_files_path(list_harness):
|
|||
user_api_key_dict=user,
|
||||
limit=7,
|
||||
after="batch-cursor",
|
||||
provider="openai",
|
||||
target_model_names="m1,m2",
|
||||
provider=None,
|
||||
target_model_names=None,
|
||||
llm_router=list_harness.router,
|
||||
)
|
||||
list_harness.litellm_alist.assert_not_called()
|
||||
|
|
@ -1494,6 +1495,27 @@ async def test_list__managed_files_path(list_harness):
|
|||
assert resp is page
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list__provider_filter_skips_managed_files(list_harness):
|
||||
list_user_batches = list_harness.set_managed_files(FakeListPage([]))
|
||||
|
||||
await call_list(list_harness, provider="vertex_ai")
|
||||
|
||||
list_user_batches.assert_not_called()
|
||||
assert list_harness.alist_kwargs()["custom_llm_provider"] == "vertex_ai"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list__target_model_names_filter_skips_managed_files(list_harness):
|
||||
list_user_batches = list_harness.set_managed_files(FakeListPage([]))
|
||||
|
||||
await call_list(list_harness, target_model_names="m1,m2")
|
||||
|
||||
list_user_batches.assert_not_called()
|
||||
list_harness.litellm_alist.assert_not_called()
|
||||
assert list_harness.router_kwargs()["model"] == "m1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list__managed_files_beats_model_param(list_harness):
|
||||
"""Branch 1 is checked before the model branch: a model in the body does not
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue