mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
fix(utils): normalize camelCase thinking param keys to snake_case (#21762)
Clients like OpenCode's @ai-sdk/openai-compatible send budgetTokens (camelCase) instead of budget_tokens in the thinking parameter, causing validation errors. Add early normalization in completion().
This commit is contained in:
parent
e0aaedc9d1
commit
cc6ef0e3f7
3 changed files with 60 additions and 0 deletions
|
|
@ -147,6 +147,7 @@ from litellm.utils import (
|
|||
token_counter,
|
||||
validate_and_fix_openai_messages,
|
||||
validate_and_fix_openai_tools,
|
||||
validate_and_fix_thinking_param,
|
||||
validate_chat_completion_tool_choice,
|
||||
validate_openai_optional_params,
|
||||
)
|
||||
|
|
@ -1102,6 +1103,8 @@ def completion( # type: ignore # noqa: PLR0915
|
|||
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 #####################
|
||||
args = locals()
|
||||
|
|
|
|||
|
|
@ -7666,6 +7666,23 @@ def validate_and_fix_openai_tools(tools: Optional[List]) -> Optional[List[dict]]
|
|||
return new_tools
|
||||
|
||||
|
||||
def validate_and_fix_thinking_param(
|
||||
thinking: Optional["AnthropicThinkingParam"],
|
||||
) -> Optional["AnthropicThinkingParam"]:
|
||||
"""
|
||||
Normalizes camelCase keys in the thinking param to snake_case.
|
||||
Handles clients that send budgetTokens instead of budget_tokens.
|
||||
"""
|
||||
if thinking is None or not isinstance(thinking, dict):
|
||||
return thinking
|
||||
normalized = dict(thinking)
|
||||
if "budgetTokens" in normalized and "budget_tokens" not in normalized:
|
||||
normalized["budget_tokens"] = normalized.pop("budgetTokens")
|
||||
elif "budgetTokens" in normalized and "budget_tokens" in normalized:
|
||||
normalized.pop("budgetTokens")
|
||||
return cast("AnthropicThinkingParam", normalized)
|
||||
|
||||
|
||||
def cleanup_none_field_in_message(message: AllMessageValues):
|
||||
"""
|
||||
Cleans up the message by removing the none field.
|
||||
|
|
|
|||
|
|
@ -3478,3 +3478,43 @@ class TestMetadataNoneHandling:
|
|||
litellm_params = {"metadata": None}
|
||||
metadata = litellm_params.get("metadata") or {}
|
||||
assert metadata == {}
|
||||
|
||||
|
||||
class TestValidateAndFixThinkingParam:
|
||||
"""Tests for validate_and_fix_thinking_param."""
|
||||
|
||||
def test_none_returns_none(self):
|
||||
from litellm.utils import validate_and_fix_thinking_param
|
||||
|
||||
assert validate_and_fix_thinking_param(thinking=None) is None
|
||||
|
||||
def test_already_snake_case(self):
|
||||
from litellm.utils import validate_and_fix_thinking_param
|
||||
|
||||
thinking = {"type": "enabled", "budget_tokens": 32000}
|
||||
result = validate_and_fix_thinking_param(thinking=thinking)
|
||||
assert result == {"type": "enabled", "budget_tokens": 32000}
|
||||
|
||||
def test_camel_case_normalized(self):
|
||||
from litellm.utils import validate_and_fix_thinking_param
|
||||
|
||||
thinking = {"type": "enabled", "budgetTokens": 32000}
|
||||
result = validate_and_fix_thinking_param(thinking=thinking)
|
||||
assert result == {"type": "enabled", "budget_tokens": 32000}
|
||||
assert "budgetTokens" not in result
|
||||
|
||||
def test_both_keys_snake_case_wins(self):
|
||||
from litellm.utils import validate_and_fix_thinking_param
|
||||
|
||||
thinking = {"type": "enabled", "budget_tokens": 10000, "budgetTokens": 50000}
|
||||
result = validate_and_fix_thinking_param(thinking=thinking)
|
||||
assert result == {"type": "enabled", "budget_tokens": 10000}
|
||||
assert "budgetTokens" not in result
|
||||
|
||||
def test_original_dict_not_mutated(self):
|
||||
from litellm.utils import validate_and_fix_thinking_param
|
||||
|
||||
thinking = {"type": "enabled", "budgetTokens": 32000}
|
||||
validate_and_fix_thinking_param(thinking=thinking)
|
||||
assert "budgetTokens" in thinking
|
||||
assert "budget_tokens" not in thinking
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue