fix: avoid sending both temperature and top_p to Anthropic API (#4471)
Co-authored-by: utkarsh240799 <utkarsh240799@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
utkarsh240799
Claude Opus 4.6
parent
16bcc91716
commit
bda5b726bd
@@ -16,7 +16,7 @@ class AnthropicConfig(BaseLlmConfig):
|
|||||||
temperature: float = 0.1,
|
temperature: float = 0.1,
|
||||||
api_key: Optional[str] = None,
|
api_key: Optional[str] = None,
|
||||||
max_tokens: int = 2000,
|
max_tokens: int = 2000,
|
||||||
top_p: float = 0.1,
|
top_p: Optional[float] = None,
|
||||||
top_k: int = 1,
|
top_k: int = 1,
|
||||||
enable_vision: bool = False,
|
enable_vision: bool = False,
|
||||||
vision_details: Optional[str] = "auto",
|
vision_details: Optional[str] = "auto",
|
||||||
@@ -32,7 +32,7 @@ class AnthropicConfig(BaseLlmConfig):
|
|||||||
temperature: Controls randomness, defaults to 0.1
|
temperature: Controls randomness, defaults to 0.1
|
||||||
api_key: Anthropic API key, defaults to None
|
api_key: Anthropic API key, defaults to None
|
||||||
max_tokens: Maximum tokens to generate, defaults to 2000
|
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
|
top_k: Top-k sampling parameter, defaults to 1
|
||||||
enable_vision: Enable vision capabilities, defaults to False
|
enable_vision: Enable vision capabilities, defaults to False
|
||||||
vision_details: Vision detail level, defaults to "auto"
|
vision_details: Vision detail level, defaults to "auto"
|
||||||
|
|||||||
@@ -40,6 +40,31 @@ class AnthropicLLM(LLMBase):
|
|||||||
api_key = self.config.api_key or os.getenv("ANTHROPIC_API_KEY")
|
api_key = self.config.api_key or os.getenv("ANTHROPIC_API_KEY")
|
||||||
self.client = anthropic.Anthropic(api_key=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(
|
def generate_response(
|
||||||
self,
|
self,
|
||||||
messages: List[Dict[str, str]],
|
messages: List[Dict[str, str]],
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user