mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-28 01:32:17 +00:00
fix: preserve JSON provider contracts in typed parameter mapping
This commit is contained in:
parent
b953695723
commit
ff4db7dbd9
3 changed files with 58 additions and 17 deletions
|
|
@ -2,7 +2,7 @@
|
|||
Dynamic configuration class generator for JSON-based providers.
|
||||
"""
|
||||
|
||||
from collections.abc import Coroutine
|
||||
from collections.abc import Coroutine, Mapping
|
||||
from typing import Any, Final, Literal, Protocol, overload, runtime_checkable
|
||||
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
|
|
@ -20,14 +20,14 @@ from .json_loader import SimpleProviderConfig
|
|||
class BaseModelAwareConfig(Protocol):
|
||||
supports_base_model_hint: bool
|
||||
|
||||
def map_openai_params(
|
||||
def map_openai_params_with_base_model(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
optional_params: dict,
|
||||
non_default_params: Mapping[str, object],
|
||||
optional_params: dict[str, object], # mutable-ok: BaseConfig mapping updates the caller-owned output dict
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
base_model: str | None = None,
|
||||
) -> dict: ...
|
||||
) -> dict[str, object]: ... # mutable-ok: BaseConfig mapping returns the same caller-owned output dict
|
||||
|
||||
|
||||
def create_config_class(provider: SimpleProviderConfig):
|
||||
|
|
@ -102,7 +102,7 @@ def create_config_class(provider: SimpleProviderConfig):
|
|||
|
||||
return api_base
|
||||
|
||||
def _get_supported_openai_params_for_model(self, model: str) -> list:
|
||||
def _get_supported_openai_params_for_model(self, model: str) -> tuple[str, ...]:
|
||||
from litellm.utils import supports_function_calling, supports_reasoning
|
||||
|
||||
tool_params: Final = ("tools", "tool_choice", "function_call", "functions", "parallel_tool_calls")
|
||||
|
|
@ -119,14 +119,17 @@ def create_config_class(provider: SimpleProviderConfig):
|
|||
supports_reasoning(model=model, custom_llm_provider=provider.slug)
|
||||
and "reasoning_effort" not in supported_params
|
||||
):
|
||||
return [*supported_params, "reasoning_effort"]
|
||||
return list(supported_params)
|
||||
return (*supported_params, "reasoning_effort")
|
||||
return supported_params
|
||||
|
||||
def get_supported_openai_params(self, model: str, base_model: str | None = None) -> list:
|
||||
def get_supported_openai_params(self, model: str, base_model: str | None = None) -> list[str]:
|
||||
supported_params: Final = self._get_supported_openai_params_for_model(model)
|
||||
if not base_model or base_model == model:
|
||||
return supported_params
|
||||
return list(dict.fromkeys([*supported_params, *self._get_supported_openai_params_for_model(base_model)]))
|
||||
combined_params: Final = (
|
||||
tuple(dict.fromkeys((*supported_params, *self._get_supported_openai_params_for_model(base_model))))
|
||||
if base_model and base_model != model
|
||||
else supported_params
|
||||
)
|
||||
return list(combined_params)
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
|
|
@ -171,6 +174,8 @@ def create_config_class(provider: SimpleProviderConfig):
|
|||
|
||||
return optional_params
|
||||
|
||||
map_openai_params_with_base_model: Final = map_openai_params
|
||||
|
||||
@property
|
||||
def custom_llm_provider(self) -> str | None:
|
||||
return provider.slug
|
||||
|
|
|
|||
|
|
@ -4718,7 +4718,7 @@ def get_optional_params(
|
|||
from litellm.llms.openai_like.dynamic_config import BaseModelAwareConfig
|
||||
|
||||
if isinstance(provider_config, BaseModelAwareConfig):
|
||||
optional_params = provider_config.map_openai_params(
|
||||
optional_params = provider_config.map_openai_params_with_base_model(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model=model,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from types import MappingProxyType
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.openai_like import dynamic_config
|
||||
|
|
@ -33,8 +35,9 @@ class TestBaseModelParamSupport:
|
|||
}
|
||||
]
|
||||
|
||||
def test_generated_chat_config_uses_base_model_for_supported_params(self, local_model_cost_map):
|
||||
config = dynamic_config.create_config_class(_provider("publicai", base_class="openai_gpt"))()
|
||||
@pytest.mark.parametrize("base_class", ["openai_gpt", "openai_like"])
|
||||
def test_generated_chat_config_uses_base_model_for_supported_params(self, local_model_cost_map, base_class):
|
||||
config = dynamic_config.create_config_class(_provider("publicai", base_class=base_class))()
|
||||
|
||||
endpoint_params = config.get_supported_openai_params(model="ep-publicai")
|
||||
assert "tools" not in endpoint_params
|
||||
|
|
@ -54,7 +57,40 @@ class TestBaseModelParamSupport:
|
|||
assert "tools" in thinking_params
|
||||
assert "reasoning_effort" in thinking_params
|
||||
|
||||
def test_get_optional_params_passes_base_model_to_json_provider(self, local_model_cost_map):
|
||||
@pytest.mark.parametrize("base_class", ["openai_gpt", "openai_like"])
|
||||
@pytest.mark.parametrize("base_model", [None, "ep-publicai", "publicai/allenai/Olmo-3-7B-Think"])
|
||||
def test_supported_params_allow_per_call_extensions_without_leaking(
|
||||
self, local_model_cost_map, base_class, base_model
|
||||
):
|
||||
config = dynamic_config.create_config_class(_provider("publicai", base_class=base_class))()
|
||||
supported = config.get_supported_openai_params("ep-publicai", base_model=base_model)
|
||||
original = tuple(supported)
|
||||
|
||||
supported.extend(["request_specific_param"])
|
||||
|
||||
assert supported[-1] == "request_specific_param"
|
||||
assert tuple(config.get_supported_openai_params("ep-publicai", base_model=base_model)) == original
|
||||
|
||||
@pytest.mark.parametrize("base_class", ["openai_gpt", "openai_like"])
|
||||
def test_mapping_preserves_caller_owned_output_and_accepts_readonly_input(self, local_model_cost_map, base_class):
|
||||
config = dynamic_config.create_config_class(_provider("publicai", base_class=base_class))()
|
||||
non_default_params = MappingProxyType({"tools": self.TOOLS, "reasoning_effort": "high"})
|
||||
optional_params = {"temperature": 0.4}
|
||||
|
||||
mapped = config.map_openai_params_with_base_model(
|
||||
non_default_params=non_default_params,
|
||||
optional_params=optional_params,
|
||||
model="ep-publicai",
|
||||
drop_params=False,
|
||||
base_model="publicai/allenai/Olmo-3-7B-Think",
|
||||
)
|
||||
|
||||
assert mapped is optional_params
|
||||
assert optional_params == {"temperature": 0.4, "tools": self.TOOLS, "reasoning_effort": "high"}
|
||||
assert non_default_params == {"tools": self.TOOLS, "reasoning_effort": "high"}
|
||||
|
||||
@pytest.mark.parametrize("drop_params", [True, False])
|
||||
def test_get_optional_params_passes_base_model_to_json_provider(self, local_model_cost_map, drop_params):
|
||||
from litellm.utils import get_optional_params
|
||||
|
||||
optional_params = get_optional_params(
|
||||
|
|
@ -63,7 +99,7 @@ class TestBaseModelParamSupport:
|
|||
tools=self.TOOLS,
|
||||
reasoning_effort="high",
|
||||
base_model="publicai/allenai/Olmo-3-7B-Think",
|
||||
drop_params=True,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
|
||||
assert optional_params["tools"] == self.TOOLS
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue