mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-02 12:21:10 +00:00
Merge pull request #21663 from BerriAI/litellm_add_reasoning_support_config
[Feat] Add reasoning support via config
This commit is contained in:
@@ -642,6 +642,25 @@ model_list:
|
||||
model: openai/responses/gpt-5-mini
|
||||
```
|
||||
|
||||
**Per-model configuration** (recommended when using Open WebUI or clients that cannot set `extra_body`):
|
||||
|
||||
```yaml
|
||||
model_list:
|
||||
- model_name: gpt-5.1
|
||||
litellm_params:
|
||||
model: openai/gpt-5.1
|
||||
# String format - uses reasoning_auto_summary for summary when set
|
||||
reasoning_effort: "high"
|
||||
model_info:
|
||||
mode: responses # if using Responses API bridge
|
||||
|
||||
- model_name: gpt-5.1-with-summary
|
||||
litellm_params:
|
||||
model: openai/gpt-5.1
|
||||
# Dict format - explicit control over effort and summary
|
||||
reasoning_effort: {"effort": "high", "summary": "detailed"}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ from pydantic import BaseModel
|
||||
|
||||
import litellm
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
LiteLLMResponsesTransformationHandler,
|
||||
)
|
||||
from litellm.constants import request_timeout
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
@@ -670,6 +673,14 @@ def responses(
|
||||
)
|
||||
|
||||
local_vars.update(kwargs)
|
||||
# Map reasoning_effort (from litellm_params/proxy config) to reasoning when not set
|
||||
if reasoning is None and "reasoning_effort" in local_vars:
|
||||
_mapped = LiteLLMResponsesTransformationHandler()._map_reasoning_effort(
|
||||
local_vars.pop("reasoning_effort")
|
||||
)
|
||||
if _mapped is not None:
|
||||
reasoning = _mapped
|
||||
local_vars["reasoning"] = _mapped
|
||||
# Get ResponsesAPIOptionalRequestParams with only valid parameters
|
||||
response_api_optional_params: ResponsesAPIOptionalRequestParams = (
|
||||
ResponsesAPIRequestUtils.get_requested_response_api_optional_param(
|
||||
|
||||
+30
-4
@@ -110,12 +110,12 @@ from litellm.router_utils.handle_error import (
|
||||
async_raise_no_deployment_exception,
|
||||
send_llm_exception_alert,
|
||||
)
|
||||
from litellm.router_utils.pre_call_checks.model_rate_limit_check import (
|
||||
ModelRateLimitingCheck,
|
||||
)
|
||||
from litellm.router_utils.pre_call_checks.deployment_affinity_check import (
|
||||
DeploymentAffinityCheck,
|
||||
)
|
||||
from litellm.router_utils.pre_call_checks.model_rate_limit_check import (
|
||||
ModelRateLimitingCheck,
|
||||
)
|
||||
from litellm.router_utils.pre_call_checks.prompt_caching_deployment_check import (
|
||||
PromptCachingDeploymentCheck,
|
||||
)
|
||||
@@ -1947,6 +1947,29 @@ class Router:
|
||||
) # add new deployment to router
|
||||
return deployment_pydantic_obj
|
||||
|
||||
@staticmethod
|
||||
def _merge_tools_from_deployment(
|
||||
deployment: dict, kwargs: dict
|
||||
) -> None:
|
||||
"""
|
||||
Merge tools from deployment litellm_params with request kwargs.
|
||||
When both have tools, concatenate them (deployment tools first, then request tools).
|
||||
tool_choice: use request value if provided, else deployment's.
|
||||
"""
|
||||
dep_params = deployment.get("litellm_params", {}) or {}
|
||||
dep_params = (
|
||||
dep_params.model_dump(exclude_none=True)
|
||||
if hasattr(dep_params, "model_dump")
|
||||
else dep_params
|
||||
)
|
||||
dep_tools = dep_params.get("tools") or []
|
||||
req_tools = kwargs.get("tools") or []
|
||||
if dep_tools or req_tools:
|
||||
merged = list(dep_tools) + list(req_tools)
|
||||
kwargs["tools"] = merged
|
||||
if "tool_choice" not in kwargs and dep_params.get("tool_choice") is not None:
|
||||
kwargs["tool_choice"] = dep_params["tool_choice"]
|
||||
|
||||
def _update_kwargs_with_deployment(
|
||||
self,
|
||||
deployment: dict,
|
||||
@@ -1954,10 +1977,13 @@ class Router:
|
||||
function_name: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
2 jobs:
|
||||
3 jobs:
|
||||
- Adds selected deployment, model_info and api_base to kwargs["metadata"] (used for logging)
|
||||
- Adds default litellm params to kwargs, if set.
|
||||
- Merges tools from deployment with request (proxy-configured tools + request tools).
|
||||
"""
|
||||
self._merge_tools_from_deployment(deployment=deployment, kwargs=kwargs)
|
||||
|
||||
model_info = deployment.get("model_info", {}).copy()
|
||||
deployment_litellm_model_name = deployment["litellm_params"]["model"]
|
||||
deployment_api_base = deployment["litellm_params"].get("api_base")
|
||||
|
||||
@@ -384,3 +384,35 @@ def test_responses_extra_body_forwarded_to_completion_transformation_handler():
|
||||
assert call_kwargs.kwargs.get("extra_body") == {
|
||||
"custom_key": "custom_value"
|
||||
}
|
||||
|
||||
|
||||
def test_responses_maps_reasoning_effort_from_litellm_params_to_reasoning():
|
||||
"""
|
||||
Test that when reasoning_effort is passed in kwargs (e.g. from proxy litellm_params)
|
||||
and reasoning is None, it is mapped to reasoning before the request.
|
||||
|
||||
Supports per-model reasoning_effort/summary config in proxy for clients like Open WebUI
|
||||
that cannot set extra_body.
|
||||
"""
|
||||
with patch(
|
||||
"litellm.responses.main.ProviderConfigManager.get_provider_responses_api_config",
|
||||
return_value=None,
|
||||
), patch(
|
||||
"litellm.responses.main.litellm_completion_transformation_handler.response_api_handler",
|
||||
) as mock_handler:
|
||||
mock_handler.return_value = MagicMock()
|
||||
|
||||
litellm.responses(
|
||||
model="openai/gpt-4o",
|
||||
input="Hello",
|
||||
reasoning_effort={"effort": "high", "summary": "detailed"},
|
||||
)
|
||||
|
||||
mock_handler.assert_called_once()
|
||||
call_kwargs = mock_handler.call_args
|
||||
responses_api_request = call_kwargs.kwargs.get("responses_api_request", {})
|
||||
assert "reasoning" in responses_api_request
|
||||
assert responses_api_request["reasoning"] == {
|
||||
"effort": "high",
|
||||
"summary": "detailed",
|
||||
}
|
||||
|
||||
@@ -1877,19 +1877,20 @@ async def test_anthropic_messages_call_type_is_cached():
|
||||
in PromptCachingDeploymentCheck.async_log_success_event.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from litellm.caching.dual_cache import DualCache
|
||||
from litellm.router_utils.pre_call_checks.prompt_caching_deployment_check import (
|
||||
PromptCachingDeploymentCheck,
|
||||
)
|
||||
from litellm.router_utils.prompt_caching_cache import PromptCachingCache
|
||||
from litellm.caching.dual_cache import DualCache
|
||||
from litellm.types.utils import CallTypes
|
||||
from litellm.types.utils import (
|
||||
StandardLoggingPayload,
|
||||
StandardLoggingModelInformation,
|
||||
StandardLoggingMetadata,
|
||||
CallTypes,
|
||||
StandardLoggingHiddenParams,
|
||||
StandardLoggingMetadata,
|
||||
StandardLoggingModelInformation,
|
||||
StandardLoggingPayload,
|
||||
)
|
||||
|
||||
|
||||
# Create mock standard logging payload inline
|
||||
def create_standard_logging_payload() -> StandardLoggingPayload:
|
||||
return StandardLoggingPayload(
|
||||
@@ -2081,3 +2082,107 @@ def test_update_kwargs_with_deployment_no_tags():
|
||||
|
||||
# No tags key should be added if deployment has no tags
|
||||
assert "tags" not in kwargs["metadata"]
|
||||
|
||||
|
||||
def test_update_kwargs_with_deployment_merges_tools():
|
||||
"""
|
||||
Test that when both deployment litellm_params and request have tools,
|
||||
they are merged (deployment tools first, then request tools).
|
||||
|
||||
Supports proxy-configured tools (e.g. for o3 deep research) merged with
|
||||
client-provided tools.
|
||||
"""
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "o3-deep-research",
|
||||
"litellm_params": {
|
||||
"model": "openai/o3-deep-research",
|
||||
"api_key": "fake-key",
|
||||
"tools": [{"type": "web_search"}],
|
||||
"tool_choice": "auto",
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
kwargs: dict = {
|
||||
"metadata": {},
|
||||
"tools": [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {"name": "get_weather", "description": "Get weather"},
|
||||
},
|
||||
],
|
||||
}
|
||||
deployment = router.get_deployment_by_model_group_name(
|
||||
model_group_name="o3-deep-research"
|
||||
)
|
||||
router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs)
|
||||
|
||||
# Tools should be merged: deployment first, then request
|
||||
assert "tools" in kwargs
|
||||
assert len(kwargs["tools"]) == 2
|
||||
assert kwargs["tools"][0] == {"type": "web_search"}
|
||||
assert kwargs["tools"][1]["function"]["name"] == "get_weather"
|
||||
# tool_choice from request (none) - deployment's should be used
|
||||
assert kwargs["tool_choice"] == "auto"
|
||||
|
||||
|
||||
def test_update_kwargs_with_deployment_merge_tools_deployment_only():
|
||||
"""
|
||||
Test that when only deployment has tools, they are applied to kwargs.
|
||||
"""
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "o3-deep-research",
|
||||
"litellm_params": {
|
||||
"model": "openai/o3-deep-research",
|
||||
"api_key": "fake-key",
|
||||
"tools": [{"type": "web_search"}],
|
||||
"tool_choice": "required",
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
kwargs: dict = {"metadata": {}}
|
||||
deployment = router.get_deployment_by_model_group_name(
|
||||
model_group_name="o3-deep-research"
|
||||
)
|
||||
router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs)
|
||||
|
||||
assert kwargs["tools"] == [{"type": "web_search"}]
|
||||
assert kwargs["tool_choice"] == "required"
|
||||
|
||||
|
||||
def test_update_kwargs_with_deployment_merge_tools_request_overrides_tool_choice():
|
||||
"""
|
||||
Test that when request has tool_choice, it overrides deployment's.
|
||||
"""
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "o3-deep-research",
|
||||
"litellm_params": {
|
||||
"model": "openai/o3-deep-research",
|
||||
"api_key": "fake-key",
|
||||
"tools": [{"type": "web_search"}],
|
||||
"tool_choice": "auto",
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
kwargs: dict = {
|
||||
"metadata": {},
|
||||
"tool_choice": "none",
|
||||
}
|
||||
deployment = router.get_deployment_by_model_group_name(
|
||||
model_group_name="o3-deep-research"
|
||||
)
|
||||
router._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs)
|
||||
|
||||
# Request tool_choice should be preserved (merged tools still applied)
|
||||
assert kwargs["tool_choice"] == "none"
|
||||
|
||||
Reference in New Issue
Block a user