mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge pull request #37423 from BerriAI/litellm_fix_thinking_bool_crash
fix: accept bool thinking param instead of crashing with AttributeError
This commit is contained in:
commit
559310f077
9 changed files with 55 additions and 10 deletions
|
|
@ -5,7 +5,7 @@ Common base config for all LLM providers
|
|||
import types
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import AsyncIterator, Iterator
|
||||
from typing import TYPE_CHECKING, Any, Final, Union, cast
|
||||
from typing import TYPE_CHECKING, Any, Final, Union
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
|
|
@ -90,9 +90,9 @@ class BaseConfig(ABC):
|
|||
return type_to_response_format_param(response_format=response_format)
|
||||
|
||||
def is_thinking_enabled(self, non_default_params: dict) -> bool:
|
||||
return (non_default_params.get("thinking") or {}).get("type") == "enabled" or non_default_params.get(
|
||||
"reasoning_effort"
|
||||
) is not None
|
||||
thinking: Final = non_default_params.get("thinking")
|
||||
thinking_type: Final = thinking.get("type") if isinstance(thinking, dict) else None
|
||||
return thinking is True or thinking_type == "enabled" or non_default_params.get("reasoning_effort") is not None
|
||||
|
||||
def is_max_tokens_in_request(self, non_default_params: dict) -> bool:
|
||||
"""
|
||||
|
|
@ -112,7 +112,10 @@ class BaseConfig(ABC):
|
|||
if is_thinking_enabled and (
|
||||
"max_tokens" not in non_default_params and "max_completion_tokens" not in non_default_params
|
||||
):
|
||||
thinking_token_budget: Final = cast(dict, optional_params["thinking"]).get("budget_tokens", None)
|
||||
thinking_value: Final = optional_params.get("thinking")
|
||||
thinking_token_budget: Final = (
|
||||
thinking_value.get("budget_tokens") if isinstance(thinking_value, dict) else None
|
||||
)
|
||||
if thinking_token_budget is not None:
|
||||
optional_params["max_tokens"] = thinking_token_budget + DEFAULT_MAX_TOKENS
|
||||
|
||||
|
|
|
|||
|
|
@ -1090,7 +1090,10 @@ class AmazonConverseConfig(BaseConfig):
|
|||
is_thinking_enabled: Final = self.is_thinking_enabled(optional_params)
|
||||
is_max_tokens_in_request: Final = self.is_max_tokens_in_request(non_default_params)
|
||||
if is_thinking_enabled and not is_max_tokens_in_request:
|
||||
thinking_token_budget: Final = cast(dict, optional_params["thinking"]).get("budget_tokens", None)
|
||||
thinking_value: Final = optional_params.get("thinking")
|
||||
thinking_token_budget: Final = (
|
||||
thinking_value.get("budget_tokens") if isinstance(thinking_value, dict) else None
|
||||
)
|
||||
if thinking_token_budget is not None:
|
||||
optional_params["maxTokens"] = thinking_token_budget + DEFAULT_MAX_TOKENS
|
||||
|
||||
|
|
|
|||
|
|
@ -131,9 +131,11 @@ class DeepSeekChatConfig(OpenAIGPTConfig):
|
|||
- model supports reasoning (capability check)
|
||||
- user explicitly passed thinking={"type": "enabled"} (opt-in check)
|
||||
"""
|
||||
thinking: Final = optional_params.get("thinking")
|
||||
return (
|
||||
supports_reasoning(model=model, custom_llm_provider="deepseek")
|
||||
and (optional_params.get("thinking") or {}).get("type") == "enabled"
|
||||
and isinstance(thinking, dict)
|
||||
and thinking.get("type") == "enabled"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -5007,7 +5007,6 @@ def completion(
|
|||
tool_choice = validate_chat_completion_tool_choice(tool_choice=tool_choice)
|
||||
# validate optional params
|
||||
stop = validate_openai_optional_params(stop=stop)
|
||||
# normalize camelCase thinking keys (e.g. budgetTokens -> budget_tokens)
|
||||
thinking = validate_and_fix_thinking_param(thinking=thinking)
|
||||
|
||||
######### unpacking kwargs #####################
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ from litellm.constants import (
|
|||
DEFAULT_EMBEDDING_PARAM_VALUES,
|
||||
DEFAULT_MAX_LRU_CACHE_SIZE,
|
||||
DEFAULT_MINIMUM_PROMPT_CACHE_TOKEN_COUNT,
|
||||
DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
|
||||
DEFAULT_TRIM_RATIO,
|
||||
FUNCTION_DEFINITION_TOKEN_COUNT,
|
||||
INITIAL_RETRY_DELAY,
|
||||
|
|
@ -7638,12 +7639,20 @@ def validate_and_fix_openai_tools(tools: list | None) -> list[dict] | None:
|
|||
|
||||
|
||||
def validate_and_fix_thinking_param(
|
||||
thinking: AnthropicThinkingParam | None,
|
||||
thinking: AnthropicThinkingParam | bool | None,
|
||||
) -> AnthropicThinkingParam | None:
|
||||
"""
|
||||
Normalizes camelCase keys in the thinking param to snake_case.
|
||||
Coerces bool thinking values (True becomes enabled with the default medium budget, False becomes None)
|
||||
and normalizes camelCase keys in the thinking param to snake_case.
|
||||
Handles clients that send budgetTokens instead of budget_tokens.
|
||||
"""
|
||||
if thinking is True:
|
||||
return cast(
|
||||
"AnthropicThinkingParam",
|
||||
{"type": "enabled", "budget_tokens": DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET},
|
||||
)
|
||||
if thinking is False:
|
||||
return None
|
||||
if thinking is None or not isinstance(thinking, dict):
|
||||
return thinking
|
||||
normalized: Final = dict(thinking)
|
||||
|
|
|
|||
|
|
@ -6043,3 +6043,12 @@ def test_streaming_usage_chunk_is_transformed():
|
|||
assert chunk.usage.prompt_tokens == 11
|
||||
assert chunk.usage.completion_tokens == 4
|
||||
assert chunk.usage.total_tokens == 15
|
||||
|
||||
|
||||
def test_update_optional_params_with_thinking_tokens_bool_thinking_does_not_crash():
|
||||
config = AmazonConverseConfig()
|
||||
optional_params = {"thinking": True}
|
||||
config.update_optional_params_with_thinking_tokens(
|
||||
non_default_params={"thinking": True}, optional_params=optional_params
|
||||
)
|
||||
assert "maxTokens" not in optional_params
|
||||
|
|
|
|||
|
|
@ -101,3 +101,8 @@ async def test_async_transform_request_strips_unsupported_tools_from_body():
|
|||
|
||||
assert [tool["type"] for tool in body["tools"]] == ["function"]
|
||||
assert body["tools"][0]["function"]["name"] == "shell"
|
||||
|
||||
|
||||
def test_thinking_mode_active_bool_thinking_returns_false_without_crashing():
|
||||
config = DeepSeekChatConfig()
|
||||
assert config._thinking_mode_active(model="deepseek-reasoner", optional_params={"thinking": True}) is False
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class TestIsThinkingEnabled:
|
|||
({"reasoning_effort": "medium"}, True),
|
||||
# both thinking enabled and reasoning_effort returns True
|
||||
({"thinking": {"type": "enabled"}, "reasoning_effort": "high"}, True),
|
||||
({"thinking": True}, True),
|
||||
# falsy thinking values should not crash
|
||||
({"thinking": False}, False),
|
||||
({"thinking": 0}, False),
|
||||
|
|
|
|||
|
|
@ -3766,6 +3766,20 @@ class TestValidateAndFixThinkingParam:
|
|||
assert "budgetTokens" in thinking
|
||||
assert "budget_tokens" not in thinking
|
||||
|
||||
def test_bool_true_maps_to_enabled_with_default_budget(self):
|
||||
from litellm.constants import DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET
|
||||
from litellm.utils import validate_and_fix_thinking_param
|
||||
|
||||
assert validate_and_fix_thinking_param(thinking=True) == {
|
||||
"type": "enabled",
|
||||
"budget_tokens": DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
|
||||
}
|
||||
|
||||
def test_bool_false_returns_none(self):
|
||||
from litellm.utils import validate_and_fix_thinking_param
|
||||
|
||||
assert validate_and_fix_thinking_param(thinking=False) is None
|
||||
|
||||
|
||||
def test_deepseek_v4_models_in_cost_map():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue