mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-10 16:22:17 +00:00
fix(anthropic): route thinking requests through OpenAI responses (#20755)
* fix(anthropic): route thinking requests through OpenAI responses * Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
committed by
Sameer Kankute
co-authored by
Krish Dholakia
greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
parent
7f89508806
commit
59c81a30a0
@@ -30,6 +30,56 @@ ANTHROPIC_ADAPTER = AnthropicAdapter()
|
||||
|
||||
|
||||
class LiteLLMMessagesToCompletionTransformationHandler:
|
||||
@staticmethod
|
||||
def _route_openai_thinking_to_responses_api_if_needed(
|
||||
completion_kwargs: Dict[str, Any],
|
||||
*,
|
||||
thinking: Optional[Dict[str, Any]],
|
||||
) -> None:
|
||||
"""
|
||||
When users call `litellm.anthropic.messages.*` with a non-Anthropic model and
|
||||
`thinking={"type": "enabled", ...}`, LiteLLM converts this into OpenAI
|
||||
`reasoning_effort`.
|
||||
|
||||
For OpenAI models, Chat Completions typically does not return reasoning text
|
||||
(only token accounting). To return a thinking-like content block in the
|
||||
Anthropic response format, we route the request through OpenAI's Responses API
|
||||
and request a reasoning summary.
|
||||
"""
|
||||
custom_llm_provider = completion_kwargs.get("custom_llm_provider")
|
||||
if custom_llm_provider is None:
|
||||
try:
|
||||
_, inferred_provider, _, _ = litellm.utils.get_llm_provider(
|
||||
model=cast(str, completion_kwargs.get("model"))
|
||||
)
|
||||
custom_llm_provider = inferred_provider
|
||||
except Exception:
|
||||
custom_llm_provider = None
|
||||
|
||||
if custom_llm_provider != "openai":
|
||||
return
|
||||
|
||||
if not isinstance(thinking, dict) or thinking.get("type") != "enabled":
|
||||
return
|
||||
|
||||
model = completion_kwargs.get("model")
|
||||
if isinstance(model, str) and model and not model.startswith("responses/"):
|
||||
|
||||
reasoning_effort = completion_kwargs.get("reasoning_effort")
|
||||
if isinstance(reasoning_effort, str) and reasoning_effort:
|
||||
completion_kwargs["reasoning_effort"] = {
|
||||
"effort": reasoning_effort,
|
||||
"summary": "detailed",
|
||||
}
|
||||
elif isinstance(reasoning_effort, dict):
|
||||
if (
|
||||
"summary" not in reasoning_effort
|
||||
and "generate_summary" not in reasoning_effort
|
||||
):
|
||||
updated_reasoning_effort = dict(reasoning_effort)
|
||||
updated_reasoning_effort["summary"] = "detailed"
|
||||
completion_kwargs["reasoning_effort"] = updated_reasoning_effort
|
||||
|
||||
@staticmethod
|
||||
def _prepare_completion_kwargs(
|
||||
*,
|
||||
@@ -123,6 +173,11 @@ class LiteLLMMessagesToCompletionTransformationHandler:
|
||||
):
|
||||
completion_kwargs[key] = value
|
||||
|
||||
LiteLLMMessagesToCompletionTransformationHandler._route_openai_thinking_to_responses_api_if_needed(
|
||||
completion_kwargs,
|
||||
thinking=thinking,
|
||||
)
|
||||
|
||||
return completion_kwargs, tool_name_mapping
|
||||
|
||||
@staticmethod
|
||||
|
||||
+8
-1
@@ -185,7 +185,14 @@ def test_openai_model_with_thinking_converts_to_reasoning_effort():
|
||||
|
||||
# Verify reasoning_effort is set (converted from thinking)
|
||||
assert "reasoning_effort" in call_kwargs, "reasoning_effort should be passed to completion"
|
||||
assert call_kwargs["reasoning_effort"] == "minimal", f"reasoning_effort should be 'minimal' for budget_tokens=1024, got {call_kwargs.get('reasoning_effort')}"
|
||||
assert call_kwargs["reasoning_effort"] == {
|
||||
"effort": "minimal",
|
||||
"summary": "detailed",
|
||||
}, f"reasoning_effort should request a reasoning summary for OpenAI responses API, got {call_kwargs.get('reasoning_effort')}"
|
||||
|
||||
# Verify OpenAI thinking requests are routed to the Responses API
|
||||
assert call_kwargs.get("model") == "responses/gpt-5.2"
|
||||
|
||||
|
||||
# Verify thinking is NOT passed (non-Claude model)
|
||||
assert "thinking" not in call_kwargs, "thinking should NOT be passed for non-Claude models"
|
||||
|
||||
Reference in New Issue
Block a user