From 87bd2d91e0c260dc8962d25b2293ea1e014a0818 Mon Sep 17 00:00:00 2001 From: Yash Singh <123385188+yashs33244@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:27:43 +0530 Subject: [PATCH] fix(llms): preserve reasoning fields in base-to-provider config conversion (#5638) --- mem0/utils/factory.py | 9 +++++++ tests/utils/test_factory.py | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/utils/test_factory.py diff --git a/mem0/utils/factory.py b/mem0/utils/factory.py index 451726bcf..cf01ccafc 100644 --- a/mem0/utils/factory.py +++ b/mem0/utils/factory.py @@ -1,4 +1,5 @@ import importlib +import inspect from typing import Dict, Optional, Union from mem0.configs.embeddings.base import BaseEmbedderConfig @@ -102,6 +103,14 @@ class LlmFactory: "vision_details": config.vision_details, "http_client_proxies": config.http_client_proxies, } + # Only forward reasoning fields to provider configs that accept them + # (explicitly or via **kwargs); others would raise on unexpected kwargs. + params = inspect.signature(config_class).parameters + accepts_kwargs = any(p.kind == p.VAR_KEYWORD for p in params.values()) + if accepts_kwargs or "reasoning_effort" in params: + config_dict["reasoning_effort"] = config.reasoning_effort + if accepts_kwargs or "is_reasoning_model" in params: + config_dict["is_reasoning_model"] = config.is_reasoning_model config_dict.update(kwargs) config = config_class(**config_dict) else: diff --git a/tests/utils/test_factory.py b/tests/utils/test_factory.py new file mode 100644 index 000000000..0f07bd2d5 --- /dev/null +++ b/tests/utils/test_factory.py @@ -0,0 +1,53 @@ +from unittest.mock import Mock, patch + +from mem0.configs.llms.anthropic import AnthropicConfig +from mem0.configs.llms.aws_bedrock import AWSBedrockConfig +from mem0.configs.llms.base import BaseLlmConfig +from mem0.configs.llms.openai import OpenAIConfig +from mem0.utils.factory import LlmFactory + + +def _capture_config(provider_name, config): + """Build an LLM via the factory and return the config it was constructed with.""" + captured = {} + + def fake_llm_class(built_config): + captured["config"] = built_config + return Mock() + + with patch("mem0.utils.factory.load_class", return_value=fake_llm_class): + LlmFactory.create(provider_name, config) + + return captured["config"] + + +def test_base_to_openai_preserves_reasoning_fields(): + base_config = BaseLlmConfig(model="o3", reasoning_effort="high", is_reasoning_model=True) + + built = _capture_config("openai", base_config) + + assert isinstance(built, OpenAIConfig) + assert built.reasoning_effort == "high" + assert built.is_reasoning_model is True + + +def test_base_to_kwargs_provider_preserves_reasoning_fields(): + # AWSBedrockConfig accepts the reasoning fields via **kwargs, so they must survive. + base_config = BaseLlmConfig(model="amazon.nova", reasoning_effort="medium", is_reasoning_model=True) + + built = _capture_config("aws_bedrock", base_config) + + assert isinstance(built, AWSBedrockConfig) + assert built.reasoning_effort == "medium" + assert built.is_reasoning_model is True + + +def test_base_to_provider_without_reasoning_fields_still_builds(): + # Anthropic config does not accept reasoning_effort/is_reasoning_model; + # the conversion must not forward unsupported kwargs to it. + base_config = BaseLlmConfig(model="claude-3-5-sonnet-20240620") + + built = _capture_config("anthropic", base_config) + + assert isinstance(built, AnthropicConfig) + assert built.model == "claude-3-5-sonnet-20240620"