From 68cf4e118da1f31737489296d0bfecdaba9f4443 Mon Sep 17 00:00:00 2001 From: Gopal Bagaswar <67310594+GopalGB@users.noreply.github.com> Date: Fri, 27 Mar 2026 17:54:44 +0530 Subject: [PATCH] feat: add reasoning_effort parameter support for reasoning models (#4461) --- mem0/configs/llms/azure.py | 3 ++ mem0/configs/llms/base.py | 5 +++ mem0/configs/llms/openai.py | 3 ++ mem0/llms/azure_openai.py | 1 + mem0/llms/base.py | 7 +++- mem0/llms/openai.py | 1 + tests/llms/test_azure_openai.py | 51 +++++++++++++++++++++++++++ tests/llms/test_openai.py | 61 +++++++++++++++++++++++++++++++++ 8 files changed, 131 insertions(+), 1 deletion(-) diff --git a/mem0/configs/llms/azure.py b/mem0/configs/llms/azure.py index f4eb859a2..749e39c02 100644 --- a/mem0/configs/llms/azure.py +++ b/mem0/configs/llms/azure.py @@ -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, ) diff --git a/mem0/configs/llms/base.py b/mem0/configs/llms/base.py index 93d5052d0..477479933 100644 --- a/mem0/configs/llms/base.py +++ b/mem0/configs/llms/base.py @@ -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 diff --git a/mem0/configs/llms/openai.py b/mem0/configs/llms/openai.py index e0a0a6f2d..f6cf4365b 100644 --- a/mem0/configs/llms/openai.py +++ b/mem0/configs/llms/openai.py @@ -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, ) diff --git a/mem0/llms/azure_openai.py b/mem0/llms/azure_openai.py index 76ed83f8f..3abd7de49 100644 --- a/mem0/llms/azure_openai.py +++ b/mem0/llms/azure_openai.py @@ -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, ) diff --git a/mem0/llms/base.py b/mem0/llms/base.py index 121254102..d873cc60e 100644 --- a/mem0/llms/base.py +++ b/mem0/llms/base.py @@ -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 diff --git a/mem0/llms/openai.py b/mem0/llms/openai.py index a486ff86b..fa011d9cb 100644 --- a/mem0/llms/openai.py +++ b/mem0/llms/openai.py @@ -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, ) diff --git a/tests/llms/test_azure_openai.py b/tests/llms/test_azure_openai.py index 8febe386c..c10dc1635 100644 --- a/tests/llms/test_azure_openai.py +++ b/tests/llms/test_azure_openai.py @@ -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"}], diff --git a/tests/llms/test_openai.py b/tests/llms/test_openai.py index 700698ba1..971244ea8 100644 --- a/tests/llms/test_openai.py +++ b/tests/llms/test_openai.py @@ -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)