Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions litellm/litellm_core_utils/get_supported_openai_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ def get_supported_openai_params( # noqa: PLR0915
```

Args:
base_model: For Azure, the true underlying model (e.g. ``"azure/gpt-5.2"``)
when the deployment name differs. Used for model-type detection so that
non-standard deployment names route to the correct config.
base_model: An optional capability hint for deployments whose ``model``
label isn't recognized on its own (e.g. an Azure deployment name, or a
friendly Bedrock alias). It is additive: the result is the union of the
params supported by ``model`` and by ``base_model``, so a hint can only
add capabilities, never strip ones the real model already supports.

Returns:
- List if custom_llm_provider is mapped
Expand Down Expand Up @@ -52,7 +54,15 @@ def get_supported_openai_params( # noqa: PLR0915
provider_config = None

if provider_config and request_type == "chat_completion":
return provider_config.get_supported_openai_params(model=base_model or model)
supported_params = provider_config.get_supported_openai_params(model=model)
if base_model and base_model != model:
base_model_params = provider_config.get_supported_openai_params(
model=base_model
)
supported_params = list(
dict.fromkeys([*supported_params, *base_model_params])
)
return supported_params

if custom_llm_provider == "bedrock":
return litellm.AmazonConverseConfig().get_supported_openai_params(model=model)
Expand Down
10 changes: 4 additions & 6 deletions litellm/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1319,7 +1319,9 @@ def completion( # type: ignore # noqa: PLR0915
preset_cache_key = kwargs.get("preset_cache_key", None)
hf_model_name = kwargs.get("hf_model_name", None)
supports_system_message = kwargs.get("supports_system_message", None)
base_model = kwargs.get("base_model", None)
base_model = kwargs.get("base_model", None) or (
model_info.get("base_model") if isinstance(model_info, dict) else None
)
### DISABLE FLAGS ###
disable_add_transform_inline_image_block = kwargs.get(
"disable_add_transform_inline_image_block", None
Expand Down Expand Up @@ -1531,11 +1533,7 @@ def completion( # type: ignore # noqa: PLR0915
"logit_bias": logit_bias,
"user": user,
# params to identify the model
"model": (
model_info.get("base_model")
if isinstance(model_info, dict) and model_info.get("base_model")
else model
),
"model": model,
"custom_llm_provider": custom_llm_provider,
"response_format": response_format,
"seed": seed,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import os
import sys

import pytest

sys.path.insert(0, os.path.abspath("../../.."))

from litellm.litellm_core_utils.get_supported_openai_params import (
get_supported_openai_params,
)

BEDROCK_REAL_MODEL = "eu.anthropic.claude-haiku-4-5-20251001-v1:0"
BEDROCK_LABEL = "claude-haiku-4-5"


def test_base_model_label_does_not_strip_bedrock_tools():
"""Regression for #29618.

A Bedrock deployment whose ``model_info.base_model`` is a friendly label
(``claude-haiku-4-5``) must still advertise ``tools``/``tool_choice``. The label
on its own resolves to no tool support, so before the fix it stripped the
capability the real model id exposes, silently dropping function calling under
``drop_params``."""
params = get_supported_openai_params(
model=BEDROCK_REAL_MODEL,
custom_llm_provider="bedrock",
base_model=BEDROCK_LABEL,
)

assert params is not None
assert "tools" in params
assert "tool_choice" in params


def test_base_model_label_alone_lacks_bedrock_tools():
"""The label by itself does not advertise tools; this is what made the union
necessary. Guards against the discrepancy disappearing (and the regression test
above silently passing for the wrong reason)."""
params = get_supported_openai_params(
model=BEDROCK_LABEL, custom_llm_provider="bedrock"
)

assert params is not None
assert "tools" not in params


def test_base_model_is_additive_not_replacement():
"""``base_model`` may only add capabilities, never remove ones the real model has.

Bedrock: real id supports ``tools`` but not the label's reasoning hint; the union
must contain the real model's ``tools`` regardless of the label being a subset."""
real_only = set(
get_supported_openai_params(
model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock"
)
)
label_only = set(
get_supported_openai_params(model=BEDROCK_LABEL, custom_llm_provider="bedrock")
)
combined = set(
get_supported_openai_params(
model=BEDROCK_REAL_MODEL,
custom_llm_provider="bedrock",
base_model=BEDROCK_LABEL,
)
)

assert combined == real_only | label_only
assert real_only - label_only # the label really is a strict subset here
assert real_only <= combined


def test_base_model_adds_capabilities_the_real_model_lacks():
"""Regression for #27717 (the behavior the union must preserve).

``gemini-3.1-pro`` isn't in the cost map so it advertises no reasoning support,
but the registered ``gemini-3.1-pro-preview`` base_model does. The hint must add
``reasoning_effort``/``thinking`` without the call erroring."""
real_only = set(
get_supported_openai_params(
model="gemini-3.1-pro", custom_llm_provider="gemini"
)
)
assert "reasoning_effort" not in real_only

combined = set(
get_supported_openai_params(
model="gemini-3.1-pro",
custom_llm_provider="gemini",
base_model="gemini-3.1-pro-preview",
)
)
assert "reasoning_effort" in combined
assert "thinking" in combined


def test_no_base_model_is_unchanged():
"""Omitting ``base_model`` must resolve purely from ``model``."""
with_none = get_supported_openai_params(
model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock", base_model=None
)
plain = get_supported_openai_params(
model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock"
)

assert with_none == plain


def test_base_model_equal_to_model_is_unchanged():
"""A ``base_model`` identical to ``model`` must not double-resolve or reorder."""
plain = get_supported_openai_params(
model=BEDROCK_REAL_MODEL, custom_llm_provider="bedrock"
)
same = get_supported_openai_params(
model=BEDROCK_REAL_MODEL,
custom_llm_provider="bedrock",
base_model=BEDROCK_REAL_MODEL,
)

assert same == plain


def test_azure_base_model_detection_preserved():
"""Azure relies on ``base_model`` for model-type detection when the deployment
name is opaque; the union must keep advertising the gpt-5 capabilities."""
params = get_supported_openai_params(
model="my-opaque-deployment",
custom_llm_provider="azure",
base_model="azure/gpt-5.2",
)

assert params is not None
assert "reasoning_effort" in params
assert "tools" in params
18 changes: 12 additions & 6 deletions tests/test_litellm/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,12 +849,13 @@ def test_gpt_5_4_responses_bridge_preserves_reasoning_summary_dict(


@pytest.mark.parametrize(
"model, model_info, expected_model_param",
"model, model_info, expected_model_param, expected_base_model_param",
[
("gemini/gemini-3.1-pro", None, "gemini-3.1-pro"),
("gemini/gemini-3.1-pro", None, "gemini-3.1-pro", None),
(
"gemini/gemini-3.1-pro",
{"base_model": "gemini-3.1-pro-preview"},
"gemini-3.1-pro",
"gemini-3.1-pro-preview",
),
],
Expand All @@ -863,7 +864,13 @@ def test_completion_optional_params_base_model(
model: str,
model_info: dict | None,
expected_model_param: str,
expected_base_model_param: str | None,
):
"""``model_info.base_model`` must reach ``get_optional_params`` as ``base_model``
(an additive capability hint), without overwriting ``model`` with the label.

Regression for #29618: overwriting ``model`` with a friendly ``base_model``
label made Bedrock drop ``tools``/``tool_choice`` under ``drop_params``."""
with patch("litellm.main.get_optional_params") as mock_get_optional_params:
mock_get_optional_params.return_value = MagicMock()

Expand All @@ -881,10 +888,9 @@ def test_completion_optional_params_base_model(
litellm.completion(**kwargs)

assert mock_get_optional_params.called is True
get_optional_params_model_param = mock_get_optional_params.call_args.kwargs[
"model"
]
assert get_optional_params_model_param == expected_model_param
call_kwargs = mock_get_optional_params.call_args.kwargs
assert call_kwargs["model"] == expected_model_param
assert call_kwargs["base_model"] == expected_base_model_param
Comment thread
andrey-dubnik marked this conversation as resolved.


@patch("litellm.completion_extras.responses_api_bridge.completion")
Expand Down
48 changes: 48 additions & 0 deletions tests/test_litellm/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4144,3 +4144,51 @@ def test_original_dict_not_mutated(self):
validate_and_fix_thinking_param(thinking=thinking)
assert "budgetTokens" in thinking
assert "budget_tokens" not in thinking


class TestBedrockBaseModelLabelKeepsTools:
"""Regression for #29618: a Bedrock deployment whose ``base_model`` is a friendly
label must not silently drop ``tools``/``tool_choice`` under ``drop_params``."""

TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
},
},
}
]

def test_base_model_label_keeps_tools_with_drop_params(self):
from litellm.utils import get_optional_params

result = get_optional_params(
model="eu.anthropic.claude-haiku-4-5-20251001-v1:0",
custom_llm_provider="bedrock",
base_model="claude-haiku-4-5",
tools=self.TOOLS,
tool_choice="auto",
drop_params=True,
)

assert "tools" in result
assert "tool_choice" in result

def test_base_model_label_alone_drops_tools(self):
"""Without the real model id the label resolves to no tool support, so passing
the label as ``model`` is exactly what dropped tools before the fix."""
from litellm.utils import get_optional_params

result = get_optional_params(
model="claude-haiku-4-5",
custom_llm_provider="bedrock",
tools=self.TOOLS,
tool_choice="auto",
drop_params=True,
)

assert "tools" not in result
Loading