mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
Merge 726a343bc4 into aea5358c48
This commit is contained in:
commit
f3369fcedf
2 changed files with 107 additions and 0 deletions
|
|
@ -2749,6 +2749,43 @@ class SafeRouteAdder:
|
|||
return True
|
||||
return False
|
||||
|
||||
# Every generic native-provider route (files, batches, and any future ones) is
|
||||
# registered as "/{provider}/v1/...", so this literal path-parameter name is a
|
||||
# reliable, future-proof marker -- no need to enumerate specific provider routes.
|
||||
_GENERIC_PROVIDER_PATH_MARKER: Final = "{provider}"
|
||||
|
||||
@staticmethod
|
||||
def _move_before_generic_provider_routes(app: FastAPI) -> None:
|
||||
"""
|
||||
Custom pass-through routes registered from config.yaml are always appended to
|
||||
app.routes, since they're added during proxy startup, strictly after every
|
||||
built-in router (including the generic "/{provider}/v1/files" and
|
||||
"/{provider}/v1/batches" routes) is mounted at module-import time. Starlette
|
||||
resolves overlapping path templates by registration order, so an appended
|
||||
custom route can never win against those generic routes -- they always match
|
||||
first and misinterpret the custom prefix as a provider name (see
|
||||
https://github.com/BerriAI/litellm/issues/37925).
|
||||
|
||||
Move the just-appended route (the last item in app.routes) to sit immediately
|
||||
before the first such generic route, so it is matched first instead. If no
|
||||
generic provider route is registered (e.g. a minimal deployment), leave the
|
||||
route appended -- current behavior is preserved as a safe fallback.
|
||||
|
||||
Builds the reordered list in one expression and reassigns app.router.routes
|
||||
wholesale, rather than mutating the existing list in place with pop()/insert().
|
||||
"""
|
||||
routes: Final = app.routes
|
||||
new_route: Final = routes[-1]
|
||||
for index, route in enumerate(routes[:-1]):
|
||||
route_path = getattr(route, "path", None)
|
||||
if route_path and SafeRouteAdder._GENERIC_PROVIDER_PATH_MARKER in route_path:
|
||||
app.router.routes = [ # mutable-ok: framework's list # rebind-ok: reordering is the fix
|
||||
*routes[:index],
|
||||
new_route,
|
||||
*routes[index:-1],
|
||||
]
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def add_api_route_if_not_exists(
|
||||
app: FastAPI,
|
||||
|
|
@ -2784,6 +2821,7 @@ class SafeRouteAdder:
|
|||
methods=methods,
|
||||
dependencies=dependencies,
|
||||
)
|
||||
SafeRouteAdder._move_before_generic_provider_routes(app=app)
|
||||
verbose_proxy_logger.debug(
|
||||
"Successfully added route: %s with methods %s",
|
||||
path,
|
||||
|
|
|
|||
|
|
@ -3370,6 +3370,75 @@ def test_native_provider_routes_are_unchanged(method, path, expected_name):
|
|||
assert _resolve_route_name(method, path) == expected_name
|
||||
|
||||
|
||||
def test_custom_pass_through_endpoint_prefix_wins_over_native_provider_routes():
|
||||
"""
|
||||
A pass_through_endpoints entry registered under an arbitrary, non-built-in
|
||||
prefix (e.g. a self-hosted Anthropic-compatible endpoint reached via a
|
||||
"/claude-aws" prefix) must win over the native /{provider}/v1/files and
|
||||
/{provider}/v1/batches routes, which would otherwise misinterpret the
|
||||
custom prefix as a provider name and 422/500 instead of forwarding
|
||||
(see https://github.com/BerriAI/litellm/issues/37925).
|
||||
"""
|
||||
from litellm.proxy.pass_through_endpoints.pass_through_endpoints import (
|
||||
InitPassThroughEndpointHelpers,
|
||||
)
|
||||
from litellm.proxy.proxy_server import app
|
||||
|
||||
for suffix in ("files", "batches"):
|
||||
InitPassThroughEndpointHelpers.add_exact_path_route(
|
||||
app=app,
|
||||
path=f"/claude-aws/v1/{suffix}",
|
||||
target=f"https://example.com/v1/{suffix}",
|
||||
custom_headers=None,
|
||||
forward_headers=False,
|
||||
merge_query_params=False,
|
||||
dependencies=None,
|
||||
cost_per_request=None,
|
||||
endpoint_id=f"test-claude-aws-{suffix}",
|
||||
)
|
||||
|
||||
assert _resolve_route_name("POST", "/claude-aws/v1/files") == "endpoint_func"
|
||||
assert _resolve_route_name("POST", "/claude-aws/v1/batches") == "endpoint_func"
|
||||
|
||||
# registering a custom prefix must not disturb resolution of unrelated,
|
||||
# already-registered native-provider routes
|
||||
assert _resolve_route_name("POST", "/openai/v1/files") == "create_file"
|
||||
assert _resolve_route_name("GET", "/azure/v1/files") == "list_files"
|
||||
assert _resolve_route_name("POST", "/v1/files") == "create_file"
|
||||
assert _resolve_route_name("POST", "/v1/batches") == "create_batch"
|
||||
|
||||
|
||||
def test_move_before_generic_provider_routes_is_a_no_op_without_a_generic_route():
|
||||
"""
|
||||
If no generic "/{provider}/..." route is registered on the app (e.g. a minimal
|
||||
deployment without the files/batches routers mounted), the newly-appended custom
|
||||
route is left exactly where it was appended -- a safe no-op fallback.
|
||||
"""
|
||||
from litellm.proxy.pass_through_endpoints.pass_through_endpoints import (
|
||||
SafeRouteAdder,
|
||||
)
|
||||
|
||||
class _FakeRoute:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
class _FakeRouter:
|
||||
def __init__(self, routes):
|
||||
self.routes = routes
|
||||
|
||||
class _FakeApp:
|
||||
def __init__(self, routes):
|
||||
self.routes = routes
|
||||
self.router = _FakeRouter(routes)
|
||||
|
||||
routes = [_FakeRoute("/health"), _FakeRoute("/claude-aws/v1/files")]
|
||||
app = _FakeApp(routes)
|
||||
|
||||
SafeRouteAdder._move_before_generic_provider_routes(app=app)
|
||||
|
||||
assert app.router.routes == routes
|
||||
|
||||
|
||||
class TestCursorProxyRoute:
|
||||
"""Tests for the Cursor Cloud Agents pass-through route."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue