mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Merge 6f614c484b into e16aa9f512
This commit is contained in:
commit
ac57e3bfe2
2 changed files with 127 additions and 13 deletions
|
|
@ -17,6 +17,8 @@ from ..common_utils import (
|
|||
get_copilot_default_headers,
|
||||
)
|
||||
|
||||
_REASONING_PARAMS: Final = frozenset({"thinking", "reasoning_effort"})
|
||||
|
||||
|
||||
class GithubCopilotConfig(OpenAIConfig):
|
||||
def __init__(
|
||||
|
|
@ -114,26 +116,45 @@ 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
|
||||
|
||||
def map_openai_params(
|
||||
self,
|
||||
non_default_params: dict, # mutable-ok: matches the BaseConfig override signature
|
||||
optional_params: dict, # mutable-ok: matches the BaseConfig override signature
|
||||
model: str,
|
||||
drop_params: bool,
|
||||
) -> dict: # mutable-ok: matches the BaseConfig override signature
|
||||
# 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)) & _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, **reasoning_params},
|
||||
model=model,
|
||||
drop_params=drop_params,
|
||||
)
|
||||
|
||||
def _determine_initiator(self, messages: list[AllMessageValues]) -> str:
|
||||
"""
|
||||
Determine if request is user or agent initiated based on message roles.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import asyncio
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from typing import AsyncGenerator
|
||||
from typing import AsyncGenerator, Final
|
||||
from unittest.mock import AsyncMock, MagicMock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
|
|
@ -978,3 +978,96 @@ def test_openai_handler_repairs_github_copilot_empty_choices(
|
|||
assert result.choices[0].message.content == "Hi there"
|
||||
assert result.choices[0].finish_reason == "stop"
|
||||
mock_request.assert_called_once()
|
||||
|
||||
|
||||
def test_map_openai_params_forwards_reasoning_effort_for_claude():
|
||||
"""Claude reasoning params must survive mapping, not just be advertised (#25666)."""
|
||||
config: Final = GithubCopilotConfig()
|
||||
model: Final = "claude-sonnet-4-20250514"
|
||||
|
||||
assert "reasoning_effort" in config.get_supported_openai_params(model)
|
||||
|
||||
mapped: Final = config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "high"},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped.get("reasoning_effort") == "high"
|
||||
|
||||
mapped_thinking: Final = config.map_openai_params(
|
||||
non_default_params={"thinking": {"type": "enabled", "budget_tokens": 4096}},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped_thinking.get("thinking") == {
|
||||
"type": "enabled",
|
||||
"budget_tokens": 4096,
|
||||
}
|
||||
|
||||
|
||||
def test_map_openai_params_does_not_forward_reasoning_for_unsupported_claude():
|
||||
"""A Claude model without extended thinking must not gain reasoning params."""
|
||||
config: Final = GithubCopilotConfig()
|
||||
model: Final = "claude-3.5-sonnet"
|
||||
|
||||
assert "reasoning_effort" not in config.get_supported_openai_params(model)
|
||||
|
||||
mapped: Final = config.map_openai_params(
|
||||
non_default_params={"reasoning_effort": "high"},
|
||||
optional_params={},
|
||||
model=model,
|
||||
drop_params=True,
|
||||
)
|
||||
assert "reasoning_effort" not in mapped
|
||||
|
||||
|
||||
def test_map_openai_params_preserves_standard_openai_params_for_claude():
|
||||
"""The Claude passthrough must not regress ordinary OpenAI param mapping."""
|
||||
config: Final = GithubCopilotConfig()
|
||||
|
||||
mapped: Final = config.map_openai_params(
|
||||
non_default_params={"temperature": 0.5, "max_tokens": 128},
|
||||
optional_params={},
|
||||
model="claude-sonnet-4-20250514",
|
||||
drop_params=False,
|
||||
)
|
||||
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."""
|
||||
config: Final = GithubCopilotConfig()
|
||||
model: Final = "gemini-2.5-pro"
|
||||
|
||||
with patch(
|
||||
"litellm.utils.supports_reasoning",
|
||||
return_value=True,
|
||||
):
|
||||
supported: Final = 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: Final = 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: Final = GithubCopilotConfig()
|
||||
|
||||
with patch(
|
||||
"litellm.utils.supports_reasoning",
|
||||
return_value=False,
|
||||
):
|
||||
supported: Final = config.get_supported_openai_params("gpt-4o")
|
||||
assert "reasoning_effort" not in supported
|
||||
assert "thinking" not in supported
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue