From bda5b726bd87f58635c664d1742fa47be268756b Mon Sep 17 00:00:00 2001 From: Utkarsh Date: Mon, 23 Mar 2026 21:00:41 +0530 Subject: [PATCH] fix: avoid sending both temperature and top_p to Anthropic API (#4471) Co-authored-by: utkarsh240799 Co-authored-by: Claude Opus 4.6 (1M context) --- mem0/configs/llms/anthropic.py | 4 +- mem0/llms/anthropic.py | 25 +++++++++ tests/llms/test_anthropic.py | 100 +++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 tests/llms/test_anthropic.py diff --git a/mem0/configs/llms/anthropic.py b/mem0/configs/llms/anthropic.py index 5fd921a98..45552eeba 100644 --- a/mem0/configs/llms/anthropic.py +++ b/mem0/configs/llms/anthropic.py @@ -16,7 +16,7 @@ class AnthropicConfig(BaseLlmConfig): temperature: float = 0.1, api_key: Optional[str] = None, max_tokens: int = 2000, - top_p: float = 0.1, + top_p: Optional[float] = None, top_k: int = 1, enable_vision: bool = False, vision_details: Optional[str] = "auto", @@ -32,7 +32,7 @@ class AnthropicConfig(BaseLlmConfig): temperature: Controls randomness, defaults to 0.1 api_key: Anthropic API key, defaults to None max_tokens: Maximum tokens to generate, defaults to 2000 - top_p: Nucleus sampling parameter, defaults to 0.1 + top_p: Nucleus sampling parameter, defaults to None (omitted to avoid conflict with temperature) 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" diff --git a/mem0/llms/anthropic.py b/mem0/llms/anthropic.py index 2caaec3a7..4af33dd8d 100644 --- a/mem0/llms/anthropic.py +++ b/mem0/llms/anthropic.py @@ -40,6 +40,31 @@ class AnthropicLLM(LLMBase): api_key = self.config.api_key or os.getenv("ANTHROPIC_API_KEY") self.client = anthropic.Anthropic(api_key=api_key) + def _get_common_params(self, **kwargs) -> Dict: + """Get common parameters, avoiding sending both temperature and top_p together. + + Anthropic rejects requests that include both temperature and top_p. + When both are set, we keep temperature and drop top_p. + """ + params = {} + + if self.config.max_tokens is not None: + params["max_tokens"] = self.config.max_tokens + + has_temperature = self.config.temperature is not None + has_top_p = self.config.top_p is not None + + if has_temperature and has_top_p: + # Anthropic forbids both; prefer temperature + params["temperature"] = self.config.temperature + elif has_temperature: + params["temperature"] = self.config.temperature + elif has_top_p: + params["top_p"] = self.config.top_p + + params.update(kwargs) + return params + def generate_response( self, messages: List[Dict[str, str]], diff --git a/tests/llms/test_anthropic.py b/tests/llms/test_anthropic.py new file mode 100644 index 000000000..ae8e67d45 --- /dev/null +++ b/tests/llms/test_anthropic.py @@ -0,0 +1,100 @@ +from unittest.mock import Mock, patch + +import pytest + +pytest.importorskip("anthropic", reason="anthropic package not installed") + +from mem0.configs.llms.anthropic import AnthropicConfig +from mem0.configs.llms.base import BaseLlmConfig +from mem0.llms.anthropic import AnthropicLLM + + +@pytest.fixture +def mock_anthropic_client(): + with patch("mem0.llms.anthropic.anthropic") as mock_anthropic: + mock_client = Mock() + mock_anthropic.Anthropic.return_value = mock_client + yield mock_client + + +def test_default_config_omits_top_p(mock_anthropic_client): + """Default AnthropicConfig should not set top_p to avoid conflict with temperature.""" + config = AnthropicConfig(model="claude-3-5-sonnet-20240620", api_key="test-key") + assert config.top_p is None + assert config.temperature == 0.1 + + +def test_generate_response_does_not_send_top_p_by_default(mock_anthropic_client): + """Anthropic API rejects temperature and top_p together; top_p must be omitted by default.""" + config = AnthropicConfig(model="claude-3-5-sonnet-20240620", api_key="test-key") + llm = AnthropicLLM(config) + + mock_response = Mock() + mock_response.content = [Mock(text="Hello!")] + mock_anthropic_client.messages.create.return_value = mock_response + + messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hi"}, + ] + + llm.generate_response(messages) + + call_kwargs = mock_anthropic_client.messages.create.call_args[1] + assert "top_p" not in call_kwargs + assert call_kwargs["temperature"] == 0.1 + + +def test_generate_response_sends_top_p_alone_when_no_temperature(mock_anthropic_client): + """When user sets only top_p (no temperature), top_p should be sent.""" + config = AnthropicConfig(model="claude-3-5-sonnet-20240620", api_key="test-key", top_p=0.9, temperature=None) + llm = AnthropicLLM(config) + + mock_response = Mock() + mock_response.content = [Mock(text="Hello!")] + mock_anthropic_client.messages.create.return_value = mock_response + + messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hi"}, + ] + + llm.generate_response(messages) + + call_kwargs = mock_anthropic_client.messages.create.call_args[1] + assert call_kwargs["top_p"] == 0.9 + assert "temperature" not in call_kwargs + + +def test_both_set_prefers_temperature_over_top_p(mock_anthropic_client): + """When both temperature and top_p are set, temperature wins and top_p is dropped.""" + config = AnthropicConfig(model="claude-3-5-sonnet-20240620", api_key="test-key", top_p=0.9, temperature=0.5) + llm = AnthropicLLM(config) + + mock_response = Mock() + mock_response.content = [Mock(text="Hello!")] + mock_anthropic_client.messages.create.return_value = mock_response + + messages = [{"role": "user", "content": "Hi"}] + llm.generate_response(messages) + + call_kwargs = mock_anthropic_client.messages.create.call_args[1] + assert call_kwargs["temperature"] == 0.5 + assert "top_p" not in call_kwargs + + +def test_base_config_conversion_does_not_send_both(mock_anthropic_client): + """BaseLlmConfig defaults both temperature=0.1 and top_p=0.1; Anthropic must not send both.""" + base_config = BaseLlmConfig(model="claude-3-5-sonnet-20240620", api_key="test-key") + llm = AnthropicLLM(base_config) + + mock_response = Mock() + mock_response.content = [Mock(text="Hello!")] + mock_anthropic_client.messages.create.return_value = mock_response + + messages = [{"role": "user", "content": "Hi"}] + llm.generate_response(messages) + + call_kwargs = mock_anthropic_client.messages.create.call_args[1] + assert "temperature" in call_kwargs + assert "top_p" not in call_kwargs