mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix(router): consume _target_order at deployment selection so it never reaches a provider
Reading _target_order with .get left it in the request kwargs after selection, and only nine provider boundaries stripped it. _atext_completion and _aadapter_completion spread the raw kwargs, so an order-2 hop on /completions sent _target_order upstream, which real providers reject as an unknown argument. Popping at selection strips it for every path in one place; the PR's retry-keeping test already passed with pop because each retry hands the callee its own kwargs copy. Claude-Session: https://claude.ai/code/session_01XKkTFa6g7Rmd6vtHL91GMn
This commit is contained in:
parent
ac964918c5
commit
0b89c59be2
2 changed files with 71 additions and 16 deletions
|
|
@ -558,11 +558,6 @@ RETRY_BREADCRUMB_EXCLUDED_KWARGS: Final = frozenset(
|
|||
)
|
||||
|
||||
|
||||
def _without_target_order(kwargs: Mapping[str, object]) -> Mapping[str, object]:
|
||||
"""Drop the router-internal order-fallback target so it never reaches a provider call."""
|
||||
return MappingProxyType({k: v for k, v in kwargs.items() if k != "_target_order"})
|
||||
|
||||
|
||||
class Router:
|
||||
model_names: set = set()
|
||||
cache_responses: bool | None = False
|
||||
|
|
@ -2177,7 +2172,7 @@ class Router:
|
|||
"messages": messages,
|
||||
"caching": self.cache_responses,
|
||||
"client": model_client,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
}
|
||||
response: Final = litellm.completion(**input_kwargs)
|
||||
verbose_router_logger.info("litellm.completion(model=%s)\x1b[32m 200 OK\x1b[0m", model_name)
|
||||
|
|
@ -3198,7 +3193,7 @@ class Router:
|
|||
"messages": messages,
|
||||
"caching": self.cache_responses,
|
||||
"client": model_client,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
}
|
||||
input_kwargs.pop("silent_model", None)
|
||||
input_kwargs.pop("include_fallback_errors", None)
|
||||
|
|
@ -4075,7 +4070,7 @@ class Router:
|
|||
"prompt": prompt,
|
||||
"caching": self.cache_responses,
|
||||
"client": model_client,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
}
|
||||
)
|
||||
self.success_calls[model_name] += 1
|
||||
|
|
@ -4135,7 +4130,7 @@ class Router:
|
|||
"prompt": prompt,
|
||||
"caching": self.cache_responses,
|
||||
"client": model_client,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -4239,7 +4234,7 @@ class Router:
|
|||
"file": file,
|
||||
"caching": self.cache_responses,
|
||||
"client": model_client,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -4892,7 +4887,7 @@ class Router:
|
|||
response_kwargs: Final = {
|
||||
**data,
|
||||
"caching": self.cache_responses,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
"model": model_name,
|
||||
}
|
||||
# Only set custom_llm_provider if it's not None
|
||||
|
|
@ -5342,7 +5337,7 @@ class Router:
|
|||
**data,
|
||||
"custom_llm_provider": custom_llm_provider,
|
||||
"caching": self.cache_responses,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -5408,7 +5403,7 @@ class Router:
|
|||
"input": input,
|
||||
"caching": self.cache_responses,
|
||||
"client": model_client,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
}
|
||||
)
|
||||
self.success_calls[model_name] += 1
|
||||
|
|
@ -5471,7 +5466,7 @@ class Router:
|
|||
"input": input,
|
||||
"caching": self.cache_responses,
|
||||
"client": model_client,
|
||||
**_without_target_order(kwargs),
|
||||
**kwargs,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -11933,7 +11928,7 @@ class Router:
|
|||
)
|
||||
|
||||
## ORDER FILTERING ## -> if user set 'order' in deployments, return deployments with lowest order (e.g. order=1 > order=2)
|
||||
_target_order: Final = (request_kwargs or {}).get("_target_order")
|
||||
_target_order: Final = (request_kwargs or {}).pop("_target_order", None)
|
||||
healthy_deployments = litellm.utils._get_order_filtered_deployments(
|
||||
cast(list[dict], healthy_deployments), target_order=_target_order
|
||||
)
|
||||
|
|
@ -12698,7 +12693,7 @@ class Router:
|
|||
)
|
||||
|
||||
## ORDER FILTERING ## -> if user set 'order' in deployments, return deployments with lowest order (e.g. order=1 > order=2)
|
||||
_target_order: Final = (request_kwargs or {}).get("_target_order")
|
||||
_target_order: Final = (request_kwargs or {}).pop("_target_order", None)
|
||||
healthy_deployments = litellm.utils._get_order_filtered_deployments(
|
||||
healthy_deployments, target_order=_target_order
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ should be tried first, and higher order deployments should be used as fallbacks
|
|||
when lower order deployments fail.
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Final, Optional
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
import litellm
|
||||
|
|
@ -580,6 +582,64 @@ async def test_generic_api_call_strips_target_order_from_provider_kwargs():
|
|||
assert "_target_order" not in captured
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_completion_order_fallback_hop_does_not_send_target_order_upstream():
|
||||
upstream_bodies: Final[list[dict]] = []
|
||||
|
||||
def _upstream(request: httpx.Request) -> httpx.Response:
|
||||
upstream_bodies.append(json.loads(request.content))
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"id": "cmpl-1",
|
||||
"object": "text_completion",
|
||||
"created": 0,
|
||||
"model": "gpt-3.5-turbo-instruct",
|
||||
"choices": [{"text": "ok from order 2", "index": 0, "logprobs": None, "finish_reason": "stop"}],
|
||||
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
||||
},
|
||||
)
|
||||
|
||||
session: Final = httpx.AsyncClient(transport=httpx.MockTransport(_upstream))
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
litellm.aclient_session = session
|
||||
router = Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "test-model",
|
||||
"litellm_params": {
|
||||
"model": "text-completion-openai/gpt-3.5-turbo-instruct",
|
||||
"api_key": "key",
|
||||
"mock_response": Exception("fail order 1"),
|
||||
"order": 1,
|
||||
},
|
||||
"model_info": {"id": "1"},
|
||||
},
|
||||
{
|
||||
"model_name": "test-model",
|
||||
"litellm_params": {
|
||||
"model": "text-completion-openai/gpt-3.5-turbo-instruct",
|
||||
"api_key": "key",
|
||||
"api_base": "http://upstream.test",
|
||||
"order": 2,
|
||||
},
|
||||
"model_info": {"id": "2"},
|
||||
},
|
||||
],
|
||||
num_retries=0,
|
||||
)
|
||||
try:
|
||||
response = await router.atext_completion(model="test-model", prompt="hi")
|
||||
finally:
|
||||
litellm.aclient_session = None
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
await session.aclose()
|
||||
|
||||
assert response._hidden_params["model_id"] == "2"
|
||||
assert upstream_bodies
|
||||
assert all("_target_order" not in body for body in upstream_bodies)
|
||||
|
||||
|
||||
def test_check_non_standard_fallback_format():
|
||||
from litellm.router_utils.fallback_event_handlers import (
|
||||
_check_non_standard_fallback_format,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue