diff --git a/litellm/llms/github_copilot/chat/transformation.py b/litellm/llms/github_copilot/chat/transformation.py index 0536782ad09..d74a478882f 100644 --- a/litellm/llms/github_copilot/chat/transformation.py +++ b/litellm/llms/github_copilot/chat/transformation.py @@ -17,7 +17,7 @@ from ..common_utils import ( get_copilot_default_headers, ) -_CLAUDE_REASONING_PARAMS: Final = frozenset({"thinking", "reasoning_effort"}) +_REASONING_PARAMS: Final = frozenset({"thinking", "reasoning_effort"}) class GithubCopilotConfig(OpenAIConfig): @@ -116,23 +116,22 @@ class GithubCopilotConfig(OpenAIConfig): """ Get supported OpenAI parameters for GitHub Copilot. - For Claude models that support extended thinking (Claude 4 family and Claude 3-7), includes thinking and reasoning_effort parameters. - For other models, returns standard OpenAI parameters (which may include reasoning_effort for o-series models). + Claude models that support extended thinking accept both thinking and reasoning_effort. + Other reasoning-capable Copilot models (Gemini, Grok, gpt-5 family) accept reasoning_effort only. """ from litellm.utils import supports_reasoning - # Get base OpenAI parameters base_params: Final = super().get_supported_openai_params(model) + normalized: Final = model.lower() - # Add Claude-specific parameters for models that support extended thinking - if "claude" in model.lower() and supports_reasoning( - model=model.lower(), - ): - if "thinking" not in base_params: - base_params.append("thinking") - # reasoning_effort is not included by parent for Claude models, so add it - if "reasoning_effort" not in base_params: - base_params.append("reasoning_effort") + if not supports_reasoning(model=normalized): + return base_params + + # thinking is Anthropic-native; only Claude accepts it + if "claude" in normalized and "thinking" not in base_params: + base_params.append("thinking") + if "reasoning_effort" not in base_params: + base_params.append("reasoning_effort") return base_params @@ -145,13 +144,13 @@ class GithubCopilotConfig(OpenAIConfig): ) -> dict: # OpenAIConfig routes non-o-series/non-gpt-5 models to OpenAIGPTConfig, whose # whitelist has neither key, so advertising them alone dropped them (#25666) - supported: Final = frozenset(self.get_supported_openai_params(model)) & _CLAUDE_REASONING_PARAMS - claude_params: Final = {k: v for k, v in non_default_params.items() if k in supported} + supported: Final = frozenset(self.get_supported_openai_params(model)) & _REASONING_PARAMS + reasoning_params: Final = {k: v for k, v in non_default_params.items() if k in supported} remaining: Final = {k: v for k, v in non_default_params.items() if k not in supported} return super().map_openai_params( non_default_params=remaining, - optional_params={**optional_params, **claude_params}, + optional_params={**optional_params, **reasoning_params}, model=model, drop_params=drop_params, ) diff --git a/tests/test_litellm/llms/github_copilot/test_github_copilot_transformation.py b/tests/test_litellm/llms/github_copilot/test_github_copilot_transformation.py index fdffdc83541..d97be9fb161 100644 --- a/tests/test_litellm/llms/github_copilot/test_github_copilot_transformation.py +++ b/tests/test_litellm/llms/github_copilot/test_github_copilot_transformation.py @@ -1050,3 +1050,45 @@ def test_map_openai_params_preserves_standard_openai_params_for_claude(): ) assert mapped.get("temperature") == 0.5 assert mapped.get("max_tokens") == 128 + + +def test_non_claude_reasoning_model_forwards_reasoning_effort(): + """Reasoning-capable non-Claude Copilot models must also keep reasoning_effort. + + Copilot serves Gemini, Grok and MAI models that advertise reasoning_effort in + its own model catalog, but the provider previously gated the param on the + model id containing "claude", so every other family had it dropped while the + upstream API would have accepted it. + """ + config = GithubCopilotConfig() + model = "gemini-2.5-pro" + + with patch( + "litellm.utils.supports_reasoning", + return_value=True, + ): + supported = config.get_supported_openai_params(model) + assert "reasoning_effort" in supported + # thinking is Anthropic-native and must stay Claude-only + assert "thinking" not in supported + + mapped = config.map_openai_params( + non_default_params={"reasoning_effort": "high"}, + optional_params={}, + model=model, + drop_params=False, + ) + assert mapped.get("reasoning_effort") == "high" + + +def test_non_reasoning_model_keeps_reasoning_effort_out(): + """A model the catalog reports as non-reasoning must not gain the param.""" + config = GithubCopilotConfig() + + with patch( + "litellm.utils.supports_reasoning", + return_value=False, + ): + supported = config.get_supported_openai_params("gpt-4o") + assert "reasoning_effort" not in supported + assert "thinking" not in supported