mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
fix: preserve router deployment credentials on none overrides
This commit is contained in:
parent
3583ac1159
commit
38f6c7f83f
3 changed files with 110 additions and 4 deletions
|
|
@ -90,6 +90,7 @@ from litellm.router_utils.batch_utils import (
|
|||
)
|
||||
from litellm.router_utils.client_initalization_utils import InitalizeCachedClient
|
||||
from litellm.router_utils.clientside_credential_handler import (
|
||||
clientside_credential_keys,
|
||||
get_dynamic_litellm_params,
|
||||
is_clientside_credential,
|
||||
)
|
||||
|
|
@ -2365,6 +2366,22 @@ class Router:
|
|||
if "tool_choice" not in kwargs and dep_params.get("tool_choice") is not None:
|
||||
kwargs["tool_choice"] = dep_params["tool_choice"]
|
||||
|
||||
@staticmethod
|
||||
def _drop_none_clientside_credentials_from_kwargs(
|
||||
deployment: dict, kwargs: dict
|
||||
) -> None:
|
||||
"""
|
||||
Preserve deployment credentials when wrappers pass explicit None values.
|
||||
|
||||
Some SDK wrappers include fields like api_base=None in every call. Treat
|
||||
those as absent, otherwise the request-level merge would erase a
|
||||
configured deployment endpoint/key.
|
||||
"""
|
||||
dep_params = deployment.get("litellm_params", {}) or {}
|
||||
for key in clientside_credential_keys:
|
||||
if kwargs.get(key) is None and dep_params.get(key) is not None:
|
||||
kwargs.pop(key, None)
|
||||
|
||||
def _update_kwargs_with_deployment(
|
||||
self,
|
||||
deployment: dict,
|
||||
|
|
@ -2378,6 +2395,9 @@ class Router:
|
|||
- Merges tools from deployment with request (proxy-configured tools + request tools).
|
||||
"""
|
||||
self._merge_tools_from_deployment(deployment=deployment, kwargs=kwargs)
|
||||
self._drop_none_clientside_credentials_from_kwargs(
|
||||
deployment=deployment, kwargs=kwargs
|
||||
)
|
||||
|
||||
model_info = deployment.get("model_info", {}).copy()
|
||||
deployment_litellm_model_name = deployment["litellm_params"]["model"]
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ def is_clientside_credential(request_kwargs: dict) -> bool:
|
|||
"""
|
||||
Check if the credential is a clientside credential.
|
||||
"""
|
||||
return any(key in request_kwargs for key in clientside_credential_keys)
|
||||
return any(request_kwargs.get(key) is not None for key in clientside_credential_keys)
|
||||
|
||||
|
||||
def get_dynamic_litellm_params(litellm_params: dict, request_kwargs: dict) -> dict:
|
||||
|
|
@ -83,7 +83,7 @@ def get_dynamic_litellm_params(litellm_params: dict, request_kwargs: dict) -> di
|
|||
"""
|
||||
# update litellm_params with clientside credentials
|
||||
for key in clientside_credential_keys:
|
||||
if key in request_kwargs:
|
||||
if request_kwargs.get(key) is not None:
|
||||
litellm_params[key] = request_kwargs[key]
|
||||
|
||||
# If the caller redirected api_base/base_url to a client-controlled value,
|
||||
|
|
@ -95,10 +95,13 @@ def get_dynamic_litellm_params(litellm_params: dict, request_kwargs: dict) -> di
|
|||
# field name (with any value, including an empty string) to keep the
|
||||
# admin's value in ``litellm_params`` and have it forwarded to the
|
||||
# redirected upstream.
|
||||
if "api_base" in request_kwargs or "base_url" in request_kwargs:
|
||||
if (
|
||||
request_kwargs.get("api_base") is not None
|
||||
or request_kwargs.get("base_url") is not None
|
||||
):
|
||||
for field in _ADMIN_CONFIG_FIELDS_TO_CLEAR_ON_BASE_OVERRIDE:
|
||||
litellm_params.pop(field, None)
|
||||
if field in request_kwargs:
|
||||
if request_kwargs.get(field) is not None:
|
||||
litellm_params[field] = request_kwargs[field]
|
||||
|
||||
return litellm_params
|
||||
|
|
|
|||
|
|
@ -54,6 +54,89 @@ def test_update_kwargs_does_not_mutate_defaults_and_merges_metadata():
|
|||
assert kwargs["litellm_metadata"] == {"baz": 123}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_router_preserves_deployment_credentials_when_request_values_are_none():
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "GPT-5.4",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-5.4",
|
||||
"api_base": "https://example-resource.cognitiveservices.azure.com",
|
||||
"api_key": "test-key",
|
||||
"api_version": "2025-04-01-preview",
|
||||
"base_model": "gpt-5.4",
|
||||
},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
with patch("litellm.acompletion", new_callable=AsyncMock) as mock_acompletion:
|
||||
mock_acompletion.return_value = {"choices": []}
|
||||
|
||||
await router.acompletion(
|
||||
model="GPT-5.4",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
api_base=None,
|
||||
api_key=None,
|
||||
reasoning_effort="medium",
|
||||
tools=[
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search",
|
||||
"description": "Search for information.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string"},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
call_kwargs = mock_acompletion.call_args.kwargs
|
||||
assert (
|
||||
call_kwargs["api_base"]
|
||||
== "https://example-resource.cognitiveservices.azure.com"
|
||||
)
|
||||
assert call_kwargs["api_key"] == "test-key"
|
||||
assert call_kwargs["api_version"] == "2025-04-01-preview"
|
||||
|
||||
|
||||
def test_router_keeps_non_none_request_credentials_as_clientside_override():
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "GPT-5.4",
|
||||
"litellm_params": {
|
||||
"model": "azure/gpt-5.4",
|
||||
"api_base": "https://deployment-resource.cognitiveservices.azure.com",
|
||||
"api_key": "deployment-key",
|
||||
},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
with patch("litellm.completion", return_value={"choices": []}) as mock_completion:
|
||||
router.completion(
|
||||
model="GPT-5.4",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
api_base="https://request-resource.cognitiveservices.azure.com",
|
||||
api_key="request-key",
|
||||
)
|
||||
|
||||
call_kwargs = mock_completion.call_args.kwargs
|
||||
assert (
|
||||
call_kwargs["api_base"]
|
||||
== "https://request-resource.cognitiveservices.azure.com"
|
||||
)
|
||||
assert call_kwargs["api_key"] == "request-key"
|
||||
|
||||
|
||||
def test_router_with_model_info_and_model_group():
|
||||
"""
|
||||
Test edge case where user specifies model_group in model_info
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue