fix(llms): add is_reasoning_model config override for versioned deployments (#5327)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
@@ -186,6 +186,72 @@ def test_azure_config_accepts_reasoning_effort():
|
||||
assert config.model == "o3-mini"
|
||||
|
||||
|
||||
def test_is_reasoning_model_override_forces_reasoning_path(mock_openai_client):
|
||||
"""Versioned Azure gpt-5.x deployments can opt in via is_reasoning_model=True.
|
||||
|
||||
Regression test for https://github.com/mem0ai/mem0/issues/5296 — the
|
||||
name-based heuristic does not recognize dated deployment names like
|
||||
``gpt-5.4-nano-2026-03-17``, so the call sent max_tokens and Azure replied
|
||||
400. The explicit override forces the reasoning-model parameter set, which
|
||||
drops max_tokens (and temperature).
|
||||
"""
|
||||
config = AzureOpenAIConfig(
|
||||
model="gpt-5.4-nano-2026-03-17",
|
||||
temperature=TEMPERATURE,
|
||||
max_tokens=MAX_TOKENS,
|
||||
is_reasoning_model=True,
|
||||
)
|
||||
llm = AzureOpenAILLM(config)
|
||||
messages = [{"role": "user", "content": "I have oily skin."}]
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.choices = [Mock(message=Mock(content="ok"))]
|
||||
mock_openai_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
llm.generate_response(messages)
|
||||
|
||||
call_kwargs = mock_openai_client.chat.completions.create.call_args[1]
|
||||
assert "max_tokens" not in call_kwargs
|
||||
assert "temperature" not in call_kwargs
|
||||
|
||||
|
||||
def test_is_reasoning_model_override_false_keeps_standard_params(mock_openai_client):
|
||||
"""is_reasoning_model=False forces the standard param set even for o-series names."""
|
||||
config = AzureOpenAIConfig(
|
||||
model="o3-mini",
|
||||
temperature=TEMPERATURE,
|
||||
max_tokens=MAX_TOKENS,
|
||||
top_p=TOP_P,
|
||||
is_reasoning_model=False,
|
||||
)
|
||||
llm = AzureOpenAILLM(config)
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.choices = [Mock(message=Mock(content="ok"))]
|
||||
mock_openai_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
llm.generate_response(messages)
|
||||
|
||||
call_kwargs = mock_openai_client.chat.completions.create.call_args[1]
|
||||
assert call_kwargs["max_tokens"] == MAX_TOKENS
|
||||
assert call_kwargs["temperature"] == TEMPERATURE
|
||||
|
||||
|
||||
def test_is_reasoning_model_defaults_to_name_heuristic(mock_openai_client):
|
||||
"""When is_reasoning_model is None (default), classification stays name-based."""
|
||||
config = AzureOpenAIConfig(
|
||||
model="gpt-5.4-nano-2026-03-17",
|
||||
temperature=TEMPERATURE,
|
||||
max_tokens=MAX_TOKENS,
|
||||
top_p=TOP_P,
|
||||
)
|
||||
llm = AzureOpenAILLM(config)
|
||||
# Unrecognized versioned name -> heuristic says "not reasoning" (unchanged).
|
||||
assert config.is_reasoning_model is None
|
||||
assert llm._is_reasoning_model("gpt-5.4-nano-2026-03-17") is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"default_headers",
|
||||
[None, {"Firstkey": "FirstVal", "SecondKey": "SecondVal"}],
|
||||
|
||||
@@ -328,6 +328,53 @@ def test_is_reasoning_model_classification(mock_openai_client):
|
||||
assert llm._is_reasoning_model("gpt-4.1-nano-2025-04-14") is False
|
||||
|
||||
|
||||
def test_is_reasoning_model_explicit_override(mock_openai_client):
|
||||
"""Explicit is_reasoning_model overrides the name-based heuristic both ways.
|
||||
|
||||
See https://github.com/mem0ai/mem0/issues/5296 — deployments with custom or
|
||||
versioned names need to opt in/out without relying on string matching.
|
||||
None (default) must preserve the existing heuristic.
|
||||
"""
|
||||
# Force True: a name the heuristic would reject is now reasoning.
|
||||
config_true = OpenAIConfig(model="gpt-5.4-nano-2026-03-17", is_reasoning_model=True)
|
||||
llm_true = OpenAILLM(config_true)
|
||||
assert llm_true._is_reasoning_model("gpt-5.4-nano-2026-03-17") is True
|
||||
|
||||
# Force False: an o-series name the heuristic would accept is now standard.
|
||||
config_false = OpenAIConfig(model="o3-mini", is_reasoning_model=False)
|
||||
llm_false = OpenAILLM(config_false)
|
||||
assert llm_false._is_reasoning_model("o3-mini") is False
|
||||
|
||||
# None (default) preserves the existing heuristic.
|
||||
config_none = OpenAIConfig(model="gpt-4.1")
|
||||
llm_none = OpenAILLM(config_none)
|
||||
assert config_none.is_reasoning_model is None
|
||||
assert llm_none._is_reasoning_model("o3-mini") is True
|
||||
assert llm_none._is_reasoning_model("gpt-5.4-mini") is False
|
||||
|
||||
|
||||
def test_is_reasoning_model_override_generates_correct_params(mock_openai_client):
|
||||
"""End-to-end: is_reasoning_model=True drops max_tokens/temperature from the actual API call."""
|
||||
config = OpenAIConfig(
|
||||
model="gpt-5.4-nano-2026-03-17",
|
||||
temperature=0.7,
|
||||
max_tokens=100,
|
||||
is_reasoning_model=True,
|
||||
)
|
||||
llm = OpenAILLM(config)
|
||||
messages = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
mock_response = Mock()
|
||||
mock_response.choices = [Mock(message=Mock(content="ok"))]
|
||||
mock_openai_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
llm.generate_response(messages)
|
||||
|
||||
call_kwargs = mock_openai_client.chat.completions.create.call_args[1]
|
||||
assert "max_tokens" not in call_kwargs
|
||||
assert "temperature" not in call_kwargs
|
||||
|
||||
|
||||
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