mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-23 00:41:40 +00:00
fix(transformation): convert 'thinking' parameter to 'reasoning_effort' for Claude models
This commit is contained in:
parent
bcbae93ecc
commit
3c378f4bfe
2 changed files with 150 additions and 0 deletions
|
|
@ -130,6 +130,54 @@ class GithubCopilotConfig(OpenAIConfig):
|
|||
|
||||
return base_params
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict,
|
||||
optional_params: dict,
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict:
|
||||
"""
|
||||
Map OpenAI params to GitHub Copilot params.
|
||||
|
||||
GitHub Copilot uses an OpenAI-compatible API and does not understand the
|
||||
Anthropic-native ``thinking`` parameter. When Claude Code calls the proxy's
|
||||
``/v1/messages`` endpoint with ``thinking`` and the request is routed to a
|
||||
``github_copilot/claude-*`` model, ``thinking`` must be converted to
|
||||
``reasoning_effort`` before being forwarded to the Copilot API.
|
||||
|
||||
``reasoning_effort`` is written directly to ``optional_params`` rather than
|
||||
deferred to the parent: ``OpenAIConfig.map_openai_params`` dispatches to the
|
||||
global ``openAIGPTConfig`` whose supported-params list does not include
|
||||
``reasoning_effort`` for Claude models, so a deferred write would be dropped.
|
||||
"""
|
||||
thinking = non_default_params.pop("thinking", None)
|
||||
existing_reasoning_effort = non_default_params.get("reasoning_effort")
|
||||
if (
|
||||
thinking is not None
|
||||
and isinstance(thinking, dict)
|
||||
and thinking.get("type") == "enabled"
|
||||
and existing_reasoning_effort is None
|
||||
):
|
||||
budget_tokens = thinking.get("budget_tokens", 0)
|
||||
if budget_tokens >= 10000:
|
||||
reasoning_effort = "high"
|
||||
elif budget_tokens >= 5000:
|
||||
reasoning_effort = "medium"
|
||||
elif budget_tokens >= 2000:
|
||||
reasoning_effort = "low"
|
||||
else:
|
||||
reasoning_effort = "minimal"
|
||||
optional_params["reasoning_effort"] = reasoning_effort
|
||||
elif existing_reasoning_effort is not None:
|
||||
optional_params["reasoning_effort"] = non_default_params.pop(
|
||||
"reasoning_effort"
|
||||
)
|
||||
|
||||
return super().map_openai_params(
|
||||
non_default_params, optional_params, model, drop_params
|
||||
)
|
||||
|
||||
def _determine_initiator(self, messages: List[AllMessageValues]) -> str:
|
||||
"""
|
||||
Determine if request is user or agent initiated based on message roles.
|
||||
|
|
|
|||
|
|
@ -433,6 +433,108 @@ def test_get_supported_openai_params_case_insensitive():
|
|||
assert "reasoning_effort" not in supported_params_35
|
||||
|
||||
|
||||
def test_map_openai_params_converts_thinking_to_reasoning_effort():
|
||||
"""
|
||||
Test that Anthropic-native ``thinking`` is converted to ``reasoning_effort`` for
|
||||
GitHub Copilot Claude models.
|
||||
|
||||
GitHub Copilot uses an OpenAI-compatible API and does not understand the
|
||||
Anthropic ``thinking`` parameter. When Claude Code sends ``thinking`` via the
|
||||
proxy's ``/v1/messages`` endpoint, litellm must translate it to
|
||||
``reasoning_effort`` before forwarding to Copilot.
|
||||
"""
|
||||
config = GithubCopilotConfig()
|
||||
model = "claude-sonnet-4-20250514"
|
||||
|
||||
# budget_tokens < 2000 -> "minimal"
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 1024}},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
assert optional_params["reasoning_effort"] == "minimal"
|
||||
|
||||
# 2000 <= budget_tokens < 5000 -> "low"
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 2000}},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
assert optional_params["reasoning_effort"] == "low"
|
||||
|
||||
# 5000 <= budget_tokens < 10000 -> "medium"
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 5000}},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
assert optional_params["reasoning_effort"] == "medium"
|
||||
|
||||
# budget_tokens >= 10000 -> "high"
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 15000}},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
assert optional_params["reasoning_effort"] == "high"
|
||||
|
||||
|
||||
def test_map_openai_params_thinking_does_not_overwrite_existing_reasoning_effort():
|
||||
"""Caller-supplied ``reasoning_effort`` must not be overwritten by the
|
||||
``thinking`` translation."""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={
|
||||
"thinking": {"type": "enabled", "budget_tokens": 15000},
|
||||
"reasoning_effort": "low",
|
||||
},
|
||||
optional_params={},
|
||||
model="claude-sonnet-4-20250514",
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
assert optional_params["reasoning_effort"] == "low"
|
||||
|
||||
|
||||
def test_map_openai_params_disabled_thinking_is_dropped():
|
||||
"""``thinking`` with ``type=disabled`` should be dropped without producing
|
||||
``reasoning_effort``."""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "disabled"}},
|
||||
optional_params={},
|
||||
model="claude-sonnet-4-20250514",
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
assert "reasoning_effort" not in optional_params
|
||||
|
||||
|
||||
def test_map_openai_params_no_thinking_leaves_params_unchanged():
|
||||
"""A request without ``thinking`` should not gain a ``reasoning_effort`` value."""
|
||||
config = GithubCopilotConfig()
|
||||
|
||||
optional_params = config.map_openai_params(
|
||||
non_default_params={"temperature": 0.5},
|
||||
optional_params={},
|
||||
model="claude-sonnet-4-20250514",
|
||||
drop_params=False,
|
||||
)
|
||||
assert "thinking" not in optional_params
|
||||
assert "reasoning_effort" not in optional_params
|
||||
assert optional_params.get("temperature") == 0.5
|
||||
|
||||
|
||||
def test_copilot_vision_request_header_with_image():
|
||||
"""Test that Copilot-Vision-Request header is added when messages contain images"""
|
||||
config = GithubCopilotConfig()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue