feat: add reasoning_effort parameter support for reasoning models (#4461)
This commit is contained in:
@@ -21,6 +21,7 @@ class AzureOpenAIConfig(BaseLlmConfig):
|
||||
top_k: int = 1,
|
||||
enable_vision: bool = False,
|
||||
vision_details: Optional[str] = "auto",
|
||||
reasoning_effort: Optional[str] = None,
|
||||
http_client_proxies: Optional[dict] = None,
|
||||
# Azure OpenAI-specific parameters
|
||||
azure_kwargs: Optional[Dict[str, Any]] = None,
|
||||
@@ -37,6 +38,7 @@ class AzureOpenAIConfig(BaseLlmConfig):
|
||||
top_k: Top-k sampling parameter, defaults to 1
|
||||
enable_vision: Enable vision capabilities, defaults to False
|
||||
vision_details: Vision detail level, defaults to "auto"
|
||||
reasoning_effort: Effort level for reasoning models ("low", "medium", "high"), defaults to None
|
||||
http_client_proxies: HTTP client proxy settings, defaults to None
|
||||
azure_kwargs: Azure-specific configuration, defaults to None
|
||||
"""
|
||||
@@ -50,6 +52,7 @@ class AzureOpenAIConfig(BaseLlmConfig):
|
||||
top_k=top_k,
|
||||
enable_vision=enable_vision,
|
||||
vision_details=vision_details,
|
||||
reasoning_effort=reasoning_effort,
|
||||
http_client_proxies=http_client_proxies,
|
||||
)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ class BaseLlmConfig(ABC):
|
||||
top_k: int = 1,
|
||||
enable_vision: bool = False,
|
||||
vision_details: Optional[str] = "auto",
|
||||
reasoning_effort: Optional[str] = None,
|
||||
http_client_proxies: Optional[Union[Dict, str]] = None,
|
||||
):
|
||||
"""
|
||||
@@ -48,6 +49,9 @@ class BaseLlmConfig(ABC):
|
||||
Only applicable to vision-enabled models. Defaults to False
|
||||
vision_details: Level of detail for vision processing.
|
||||
Options: "low", "high", "auto". Defaults to "auto"
|
||||
reasoning_effort: Effort level for reasoning models (e.g., o1, o3, gpt-5).
|
||||
Options: "low", "medium", "high". Only applicable to reasoning models.
|
||||
Defaults to None (uses the model's default reasoning effort)
|
||||
http_client_proxies: Proxy settings for HTTP client.
|
||||
Can be a dict or string. Defaults to None
|
||||
"""
|
||||
@@ -59,4 +63,5 @@ class BaseLlmConfig(ABC):
|
||||
self.top_k = top_k
|
||||
self.enable_vision = enable_vision
|
||||
self.vision_details = vision_details
|
||||
self.reasoning_effort = reasoning_effort
|
||||
self.http_client = httpx.Client(proxies=http_client_proxies) if http_client_proxies else None
|
||||
|
||||
@@ -20,6 +20,7 @@ class OpenAIConfig(BaseLlmConfig):
|
||||
top_k: int = 1,
|
||||
enable_vision: bool = False,
|
||||
vision_details: Optional[str] = "auto",
|
||||
reasoning_effort: Optional[str] = None,
|
||||
http_client_proxies: Optional[dict] = None,
|
||||
# OpenAI-specific parameters
|
||||
openai_base_url: Optional[str] = None,
|
||||
@@ -44,6 +45,7 @@ class OpenAIConfig(BaseLlmConfig):
|
||||
top_k: Top-k sampling parameter, defaults to 1
|
||||
enable_vision: Enable vision capabilities, defaults to False
|
||||
vision_details: Vision detail level, defaults to "auto"
|
||||
reasoning_effort: Effort level for reasoning models ("low", "medium", "high"), defaults to None
|
||||
http_client_proxies: HTTP client proxy settings, defaults to None
|
||||
openai_base_url: OpenAI API base URL, defaults to None
|
||||
models: List of models for OpenRouter, defaults to None
|
||||
@@ -63,6 +65,7 @@ class OpenAIConfig(BaseLlmConfig):
|
||||
top_k=top_k,
|
||||
enable_vision=enable_vision,
|
||||
vision_details=vision_details,
|
||||
reasoning_effort=reasoning_effort,
|
||||
http_client_proxies=http_client_proxies,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class AzureOpenAILLM(LLMBase):
|
||||
top_k=config.top_k,
|
||||
enable_vision=config.enable_vision,
|
||||
vision_details=config.vision_details,
|
||||
reasoning_effort=getattr(config, 'reasoning_effort', None),
|
||||
http_client_proxies=config.http_client,
|
||||
)
|
||||
|
||||
|
||||
+6
-1
@@ -88,7 +88,12 @@ class LLMBase(ABC):
|
||||
supported_params["tools"] = kwargs["tools"]
|
||||
if "tool_choice" in kwargs:
|
||||
supported_params["tool_choice"] = kwargs["tool_choice"]
|
||||
|
||||
|
||||
# Add reasoning_effort if configured
|
||||
reasoning_effort = getattr(self.config, 'reasoning_effort', None)
|
||||
if reasoning_effort:
|
||||
supported_params["reasoning_effort"] = reasoning_effort
|
||||
|
||||
return supported_params
|
||||
else:
|
||||
# For regular models, include all common parameters
|
||||
|
||||
@@ -29,6 +29,7 @@ class OpenAILLM(LLMBase):
|
||||
top_k=config.top_k,
|
||||
enable_vision=config.enable_vision,
|
||||
vision_details=config.vision_details,
|
||||
reasoning_effort=getattr(config, 'reasoning_effort', None),
|
||||
http_client_proxies=config.http_client,
|
||||
)
|
||||
|
||||
|
||||
@@ -91,6 +91,57 @@ def test_generate_response_with_tools(mock_openai_client):
|
||||
assert response["tool_calls"][0]["arguments"] == {"data": "Today is a sunny day."}
|
||||
|
||||
|
||||
def test_reasoning_model_with_reasoning_effort(mock_openai_client):
|
||||
"""Test that reasoning_effort is passed to the API for Azure reasoning models."""
|
||||
config = AzureOpenAIConfig(model="o3-mini", reasoning_effort="low")
|
||||
llm = AzureOpenAILLM(config)
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a helpful ai."},
|
||||
{"role": "user", "content": "Hello"},
|
||||
]
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.choices = [Mock(message=Mock(content="Response from o3-mini"))]
|
||||
mock_openai_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
response = llm.generate_response(messages)
|
||||
|
||||
call_kwargs = mock_openai_client.chat.completions.create.call_args
|
||||
assert call_kwargs[1]["reasoning_effort"] == "low"
|
||||
assert "temperature" not in call_kwargs[1]
|
||||
assert response == "Response from o3-mini"
|
||||
|
||||
|
||||
def test_azure_reasoning_effort_not_passed_when_none(mock_openai_client):
|
||||
"""Test that reasoning_effort is not passed when not configured on Azure."""
|
||||
config = AzureOpenAIConfig(model="o3-mini")
|
||||
llm = AzureOpenAILLM(config)
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a helpful ai."},
|
||||
{"role": "user", "content": "Hello"},
|
||||
]
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.choices = [Mock(message=Mock(content="Response"))]
|
||||
mock_openai_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
llm.generate_response(messages)
|
||||
|
||||
call_kwargs = mock_openai_client.chat.completions.create.call_args
|
||||
assert "reasoning_effort" not in call_kwargs[1]
|
||||
|
||||
|
||||
def test_azure_config_accepts_reasoning_effort():
|
||||
"""Test that AzureOpenAIConfig accepts reasoning_effort without TypeError (issue #3651)."""
|
||||
config = AzureOpenAIConfig(
|
||||
model="o3-mini",
|
||||
reasoning_effort="low",
|
||||
azure_kwargs={"api_key": "test"},
|
||||
)
|
||||
assert config.reasoning_effort == "low"
|
||||
assert config.model == "o3-mini"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"default_headers",
|
||||
[None, {"Firstkey": "FirstVal", "SecondKey": "SecondVal"}],
|
||||
|
||||
@@ -170,6 +170,67 @@ def test_callback_exception_handling(mock_openai_client):
|
||||
assert llm.config.response_callback is faulty_callback
|
||||
|
||||
|
||||
def test_reasoning_model_with_reasoning_effort(mock_openai_client):
|
||||
"""Test that reasoning_effort is passed to the API for reasoning models."""
|
||||
config = OpenAIConfig(model="o3-mini", reasoning_effort="low")
|
||||
llm = OpenAILLM(config)
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.choices = [Mock(message=Mock(content="Response from o3-mini"))]
|
||||
mock_openai_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
response = llm.generate_response(messages)
|
||||
|
||||
call_kwargs = mock_openai_client.chat.completions.create.call_args
|
||||
assert call_kwargs[1]["reasoning_effort"] == "low"
|
||||
assert "temperature" not in call_kwargs[1] # reasoning models don't get temperature
|
||||
assert response == "Response from o3-mini"
|
||||
|
||||
|
||||
def test_reasoning_model_without_reasoning_effort(mock_openai_client):
|
||||
"""Test that reasoning_effort is not passed when not configured."""
|
||||
config = OpenAIConfig(model="o3-mini")
|
||||
llm = OpenAILLM(config)
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.choices = [Mock(message=Mock(content="Response"))]
|
||||
mock_openai_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
llm.generate_response(messages)
|
||||
|
||||
call_kwargs = mock_openai_client.chat.completions.create.call_args
|
||||
assert "reasoning_effort" not in call_kwargs[1]
|
||||
|
||||
|
||||
def test_non_reasoning_model_ignores_reasoning_effort(mock_openai_client):
|
||||
"""Test that reasoning_effort is not passed for non-reasoning models."""
|
||||
config = OpenAIConfig(model="gpt-4.1-nano-2025-04-14", reasoning_effort="high")
|
||||
llm = OpenAILLM(config)
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.choices = [Mock(message=Mock(content="Response"))]
|
||||
mock_openai_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
llm.generate_response(messages)
|
||||
|
||||
call_kwargs = mock_openai_client.chat.completions.create.call_args
|
||||
# Non-reasoning models use common params path, reasoning_effort not added there
|
||||
assert "reasoning_effort" not in call_kwargs[1]
|
||||
|
||||
|
||||
def test_reasoning_effort_config_values():
|
||||
"""Test that reasoning_effort can be set to all valid values."""
|
||||
for effort in ["low", "medium", "high"]:
|
||||
config = OpenAIConfig(model="o3", reasoning_effort=effort)
|
||||
assert config.reasoning_effort == effort
|
||||
|
||||
config = OpenAIConfig(model="o3")
|
||||
assert config.reasoning_effort is None
|
||||
|
||||
|
||||
def test_callback_with_tools(mock_openai_client):
|
||||
mock_callback = Mock()
|
||||
config = OpenAIConfig(model="gpt-4.1-nano-2025-04-14", response_callback=mock_callback)
|
||||
|
||||
Reference in New Issue
Block a user