mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(router): forward deployment-level use_chat_completions_api into request kwargs
Deployments configured with `use_chat_completions_api: true` in `litellm_params` (most commonly DB-sourced model rows) were silently dropped: `_update_kwargs_with_deployment` never copied the flag into the outgoing request kwargs, so the underlying call still went through the default Responses API path. Forward the flag explicitly. The merge uses `setdefault`, so any value the caller already set on the request wins over the deployment-level default. We also normalise the `litellm_params` container before reading it, so both DB-sourced (plain `dict`) and code-configured (`LiteLLM_Params` Pydantic) deployments are handled. Implemented as a single-element allowlist rather than a denylist of "known router-internal fields", to avoid the backwards-incompatible risk of injecting unrelated proxy/router-only params (`tpm`, `rpm`, `max_budget`, `litellm_credential_name`, `configurable_clientside_auth_params`, ...) into every LLM call. New fields can be opted in by adding them to the allowlist. Tests cover all four branches of the new behaviour: dict-form deployment, Pydantic-form deployment, request-level override, and the regression guard that router-internal fields are not forwarded.
This commit is contained in:
parent
d120ddf678
commit
11f6a1c712
2 changed files with 147 additions and 0 deletions
|
|
@ -2435,6 +2435,18 @@ class Router:
|
|||
kwargs=kwargs, metadata_variable_name=metadata_variable_name
|
||||
)
|
||||
|
||||
# Forward a small allowlist of deployment-level litellm_params into the
|
||||
# request kwargs so they actually reach the underlying call.
|
||||
_deployment_litellm_params = deployment.get("litellm_params", {}) or {}
|
||||
if not isinstance(_deployment_litellm_params, dict):
|
||||
_deployment_litellm_params = _deployment_litellm_params.model_dump(
|
||||
exclude_none=True
|
||||
)
|
||||
for _key in ("use_chat_completions_api",):
|
||||
_value = _deployment_litellm_params.get(_key)
|
||||
if _value is not None:
|
||||
kwargs.setdefault(_key, _value)
|
||||
|
||||
def _get_async_openai_model_client(self, deployment: dict, kwargs: dict):
|
||||
"""
|
||||
Helper to get AsyncOpenAI or AsyncAzureOpenAI client that was created for the deployment
|
||||
|
|
|
|||
|
|
@ -2817,6 +2817,141 @@ def test_update_kwargs_with_deployment_merge_tools_request_overrides_tool_choice
|
|||
assert kwargs["tool_choice"] == "none"
|
||||
|
||||
|
||||
def test_update_kwargs_with_deployment_forwards_use_chat_completions_api_dict():
|
||||
"""
|
||||
Deployment-level `use_chat_completions_api` set via a dict-form
|
||||
`litellm_params` (the shape that comes from the DB) should be forwarded
|
||||
into the request kwargs.
|
||||
"""
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "gpt-4o-mini",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
deployment = {
|
||||
"model_name": "gpt-4o-mini",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"api_key": "fake-key",
|
||||
"use_chat_completions_api": True,
|
||||
},
|
||||
"model_info": {"id": "test-id"},
|
||||
}
|
||||
|
||||
kwargs: dict = {"metadata": {}}
|
||||
router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs)
|
||||
|
||||
assert kwargs.get("use_chat_completions_api") is True
|
||||
|
||||
|
||||
def test_update_kwargs_with_deployment_forwards_use_chat_completions_api_pydantic():
|
||||
"""
|
||||
Deployment-level `use_chat_completions_api` set via a Pydantic
|
||||
`LiteLLM_Params` (the shape that comes from code-configured deployments
|
||||
via `_create_deployment`) should also be forwarded.
|
||||
"""
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "gpt-4o-mini",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"api_key": "fake-key",
|
||||
"use_chat_completions_api": True,
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
deployment = router.get_deployment_by_model_group_name(
|
||||
model_group_name="gpt-4o-mini"
|
||||
)
|
||||
# Sanity: the Pydantic branch is the one we're exercising here.
|
||||
assert not isinstance(deployment.litellm_params, dict)
|
||||
|
||||
kwargs: dict = {"metadata": {}}
|
||||
router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs)
|
||||
|
||||
assert kwargs.get("use_chat_completions_api") is True
|
||||
|
||||
|
||||
def test_update_kwargs_with_deployment_request_overrides_use_chat_completions_api():
|
||||
"""
|
||||
A value already present in the request kwargs must win over the
|
||||
deployment-level default (setdefault semantics).
|
||||
"""
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "gpt-4o-mini",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"api_key": "fake-key",
|
||||
"use_chat_completions_api": True,
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
deployment = router.get_deployment_by_model_group_name(
|
||||
model_group_name="gpt-4o-mini"
|
||||
)
|
||||
kwargs: dict = {"metadata": {}, "use_chat_completions_api": False}
|
||||
router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs)
|
||||
|
||||
assert kwargs["use_chat_completions_api"] is False
|
||||
|
||||
|
||||
def test_update_kwargs_with_deployment_does_not_forward_router_internal_params():
|
||||
"""
|
||||
Router/proxy-internal fields on `litellm_params` (tpm, rpm, max_budget,
|
||||
litellm_credential_name, ...) must not be injected into the request
|
||||
kwargs — they are not LLM call parameters and forwarding them would be a
|
||||
backwards-incompatible change for any deployment that has them set.
|
||||
"""
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "gpt-4o-mini",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
# Hand-built deployment dict so we can attach router-internal fields
|
||||
# without triggering Router-init side effects (e.g. budget pre-call checks).
|
||||
deployment = {
|
||||
"model_name": "gpt-4o-mini",
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"api_key": "fake-key",
|
||||
"tpm": 1000,
|
||||
"rpm": 60,
|
||||
"max_budget": 10.0,
|
||||
"litellm_credential_name": "openai-prod",
|
||||
},
|
||||
"model_info": {"id": "test-id"},
|
||||
}
|
||||
|
||||
kwargs: dict = {"metadata": {}}
|
||||
router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs)
|
||||
|
||||
for forbidden in ("tpm", "rpm", "max_budget", "litellm_credential_name"):
|
||||
assert (
|
||||
forbidden not in kwargs
|
||||
), f"router-internal param {forbidden!r} must not leak into request kwargs"
|
||||
|
||||
|
||||
def test_credential_name_injected_as_tag():
|
||||
"""
|
||||
Test that litellm_credential_name from deployment litellm_params
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue