fix(anthropic): Remove sampling parameters when using new model. (#6211)
Co-authored-by: HJ Qu <35272962+ChristopherQu@users.noreply.github.com>
This commit is contained in:
@@ -23,6 +23,7 @@ class AnthropicConfig(BaseLlmConfig):
|
|||||||
http_client_proxies: Optional[dict] = None,
|
http_client_proxies: Optional[dict] = None,
|
||||||
# Anthropic-specific parameters
|
# Anthropic-specific parameters
|
||||||
anthropic_base_url: Optional[str] = None,
|
anthropic_base_url: Optional[str] = None,
|
||||||
|
enable_sampling_parameters: Optional[bool] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Initialize Anthropic configuration.
|
Initialize Anthropic configuration.
|
||||||
@@ -38,6 +39,7 @@ class AnthropicConfig(BaseLlmConfig):
|
|||||||
vision_details: Vision detail level, defaults to "auto"
|
vision_details: Vision detail level, defaults to "auto"
|
||||||
http_client_proxies: HTTP client proxy settings, defaults to None
|
http_client_proxies: HTTP client proxy settings, defaults to None
|
||||||
anthropic_base_url: Anthropic API base URL, defaults to None
|
anthropic_base_url: Anthropic API base URL, defaults to None
|
||||||
|
enable_sampling_parameters: Enable sampling parameters, defaults to None
|
||||||
"""
|
"""
|
||||||
# Initialize base parameters
|
# Initialize base parameters
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -54,3 +56,4 @@ class AnthropicConfig(BaseLlmConfig):
|
|||||||
|
|
||||||
# Anthropic-specific parameters
|
# Anthropic-specific parameters
|
||||||
self.anthropic_base_url = anthropic_base_url
|
self.anthropic_base_url = anthropic_base_url
|
||||||
|
self.enable_sampling_parameters = enable_sampling_parameters
|
||||||
|
|||||||
+33
-10
@@ -44,6 +44,30 @@ class AnthropicLLM(LLMBase):
|
|||||||
client_kwargs["base_url"] = base_url
|
client_kwargs["base_url"] = base_url
|
||||||
self.client = anthropic.Anthropic(**client_kwargs)
|
self.client = anthropic.Anthropic(**client_kwargs)
|
||||||
|
|
||||||
|
def _enable_sampling_parameters(self):
|
||||||
|
"""Return whether the configured model supports sampling parameters."""
|
||||||
|
explicit = getattr(self.config, "enable_sampling_parameters", None)
|
||||||
|
if explicit is not None:
|
||||||
|
return explicit
|
||||||
|
|
||||||
|
model_name = self.config.model.lower()
|
||||||
|
model_name_parts = model_name.rsplit("[", 1)[0].split("-")
|
||||||
|
_, family, major, minor, *_ = (*model_name_parts, "", "", "", "")
|
||||||
|
|
||||||
|
# Use model_name if only provided a family name, e.g., opus
|
||||||
|
family = family or model_name
|
||||||
|
major = int(major) if major.isdigit() else None
|
||||||
|
minor = int(minor) if minor.isdigit() else None
|
||||||
|
|
||||||
|
if family == "haiku":
|
||||||
|
return True
|
||||||
|
if family == "sonnet" and major is not None:
|
||||||
|
return major < 5
|
||||||
|
if family == "opus" and major is not None and minor is not None:
|
||||||
|
return (major, minor) < (4, 7)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def _get_common_params(self, **kwargs) -> Dict:
|
def _get_common_params(self, **kwargs) -> Dict:
|
||||||
"""Get common parameters, avoiding sending both temperature and top_p together.
|
"""Get common parameters, avoiding sending both temperature and top_p together.
|
||||||
|
|
||||||
@@ -58,13 +82,14 @@ class AnthropicLLM(LLMBase):
|
|||||||
has_temperature = self.config.temperature is not None
|
has_temperature = self.config.temperature is not None
|
||||||
has_top_p = self.config.top_p is not None
|
has_top_p = self.config.top_p is not None
|
||||||
|
|
||||||
if has_temperature and has_top_p:
|
if self._enable_sampling_parameters():
|
||||||
# Anthropic forbids both; prefer temperature
|
if has_temperature and has_top_p:
|
||||||
params["temperature"] = self.config.temperature
|
# Anthropic forbids both; prefer temperature
|
||||||
elif has_temperature:
|
params["temperature"] = self.config.temperature
|
||||||
params["temperature"] = self.config.temperature
|
elif has_temperature:
|
||||||
elif has_top_p:
|
params["temperature"] = self.config.temperature
|
||||||
params["top_p"] = self.config.top_p
|
elif has_top_p:
|
||||||
|
params["top_p"] = self.config.top_p
|
||||||
|
|
||||||
params.update(kwargs)
|
params.update(kwargs)
|
||||||
return params
|
return params
|
||||||
@@ -122,8 +147,6 @@ class AnthropicLLM(LLMBase):
|
|||||||
if block.type == "text":
|
if block.type == "text":
|
||||||
result["content"] = block.text
|
result["content"] = block.text
|
||||||
elif block.type == "tool_use":
|
elif block.type == "tool_use":
|
||||||
result["tool_calls"].append(
|
result["tool_calls"].append({"name": block.name, "arguments": block.input})
|
||||||
{"name": block.name, "arguments": block.input}
|
|
||||||
)
|
|
||||||
return result
|
return result
|
||||||
return response.content[0].text
|
return response.content[0].text
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ def mock_anthropic_client():
|
|||||||
|
|
||||||
def test_default_config_omits_top_p(mock_anthropic_client):
|
def test_default_config_omits_top_p(mock_anthropic_client):
|
||||||
"""Default AnthropicConfig should not set top_p to avoid conflict with temperature."""
|
"""Default AnthropicConfig should not set top_p to avoid conflict with temperature."""
|
||||||
config = AnthropicConfig(model="claude-3-5-sonnet-20240620", api_key="test-key")
|
config = AnthropicConfig(model="claude-opus-4-6", api_key="test-key")
|
||||||
assert config.top_p is None
|
assert config.top_p is None
|
||||||
assert config.temperature == 0.1
|
assert config.temperature == 0.1
|
||||||
|
|
||||||
|
|
||||||
def test_generate_response_does_not_send_top_p_by_default(mock_anthropic_client):
|
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."""
|
"""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")
|
config = AnthropicConfig(model="claude-opus-4-6", api_key="test-key")
|
||||||
llm = AnthropicLLM(config)
|
llm = AnthropicLLM(config)
|
||||||
|
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
@@ -47,7 +47,7 @@ def test_generate_response_does_not_send_top_p_by_default(mock_anthropic_client)
|
|||||||
|
|
||||||
def test_generate_response_sends_top_p_alone_when_no_temperature(mock_anthropic_client):
|
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."""
|
"""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)
|
config = AnthropicConfig(model="claude-opus-4-6", api_key="test-key", top_p=0.9, temperature=None)
|
||||||
llm = AnthropicLLM(config)
|
llm = AnthropicLLM(config)
|
||||||
|
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
@@ -68,7 +68,7 @@ def test_generate_response_sends_top_p_alone_when_no_temperature(mock_anthropic_
|
|||||||
|
|
||||||
def test_both_set_prefers_temperature_over_top_p(mock_anthropic_client):
|
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."""
|
"""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)
|
config = AnthropicConfig(model="claude-opus-4-6", api_key="test-key", top_p=0.9, temperature=0.5)
|
||||||
llm = AnthropicLLM(config)
|
llm = AnthropicLLM(config)
|
||||||
|
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
@@ -87,7 +87,7 @@ def test_configured_base_url_is_passed_to_client():
|
|||||||
"""A configured anthropic_base_url must reach the Anthropic client constructor."""
|
"""A configured anthropic_base_url must reach the Anthropic client constructor."""
|
||||||
with patch("mem0.llms.anthropic.anthropic") as mock_anthropic:
|
with patch("mem0.llms.anthropic.anthropic") as mock_anthropic:
|
||||||
config = AnthropicConfig(
|
config = AnthropicConfig(
|
||||||
model="claude-3-5-sonnet-20240620",
|
model="claude-opus-4-6",
|
||||||
api_key="test-key",
|
api_key="test-key",
|
||||||
anthropic_base_url="https://proxy.example.com",
|
anthropic_base_url="https://proxy.example.com",
|
||||||
)
|
)
|
||||||
@@ -100,7 +100,7 @@ def test_base_url_falls_back_to_env(monkeypatch):
|
|||||||
"""When no base_url is configured, ANTHROPIC_BASE_URL is used."""
|
"""When no base_url is configured, ANTHROPIC_BASE_URL is used."""
|
||||||
monkeypatch.setenv("ANTHROPIC_BASE_URL", "https://env.example.com")
|
monkeypatch.setenv("ANTHROPIC_BASE_URL", "https://env.example.com")
|
||||||
with patch("mem0.llms.anthropic.anthropic") as mock_anthropic:
|
with patch("mem0.llms.anthropic.anthropic") as mock_anthropic:
|
||||||
config = AnthropicConfig(model="claude-3-5-sonnet-20240620", api_key="test-key")
|
config = AnthropicConfig(model="claude-opus-4-6", api_key="test-key")
|
||||||
AnthropicLLM(config)
|
AnthropicLLM(config)
|
||||||
|
|
||||||
assert mock_anthropic.Anthropic.call_args[1]["base_url"] == "https://env.example.com"
|
assert mock_anthropic.Anthropic.call_args[1]["base_url"] == "https://env.example.com"
|
||||||
@@ -110,7 +110,7 @@ def test_base_url_omitted_when_unset(monkeypatch):
|
|||||||
"""With no base_url anywhere, base_url must not be forced to None on the client."""
|
"""With no base_url anywhere, base_url must not be forced to None on the client."""
|
||||||
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
|
||||||
with patch("mem0.llms.anthropic.anthropic") as mock_anthropic:
|
with patch("mem0.llms.anthropic.anthropic") as mock_anthropic:
|
||||||
config = AnthropicConfig(model="claude-3-5-sonnet-20240620", api_key="test-key")
|
config = AnthropicConfig(model="claude-opus-4-6", api_key="test-key")
|
||||||
AnthropicLLM(config)
|
AnthropicLLM(config)
|
||||||
|
|
||||||
assert "base_url" not in mock_anthropic.Anthropic.call_args[1]
|
assert "base_url" not in mock_anthropic.Anthropic.call_args[1]
|
||||||
@@ -118,7 +118,7 @@ def test_base_url_omitted_when_unset(monkeypatch):
|
|||||||
|
|
||||||
def test_base_config_conversion_does_not_send_both(mock_anthropic_client):
|
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."""
|
"""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")
|
base_config = BaseLlmConfig(model="claude-opus-4-6", api_key="test-key")
|
||||||
llm = AnthropicLLM(base_config)
|
llm = AnthropicLLM(base_config)
|
||||||
|
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
@@ -131,3 +131,57 @@ def test_base_config_conversion_does_not_send_both(mock_anthropic_client):
|
|||||||
call_kwargs = mock_anthropic_client.messages.create.call_args[1]
|
call_kwargs = mock_anthropic_client.messages.create.call_args[1]
|
||||||
assert "temperature" in call_kwargs
|
assert "temperature" in call_kwargs
|
||||||
assert "top_p" not in call_kwargs
|
assert "top_p" not in call_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("model", "expected"),
|
||||||
|
[
|
||||||
|
("opus", False),
|
||||||
|
("claude-opus-4-6", True),
|
||||||
|
("claude-opus-4-7", False),
|
||||||
|
("claude-opus-4-8", False),
|
||||||
|
("sonnet", False),
|
||||||
|
("claude-sonnet-4-6", True),
|
||||||
|
("claude-sonnet-5", False),
|
||||||
|
("haiku", True),
|
||||||
|
("claude-haiku-4-5", True),
|
||||||
|
("claude-haiku-4-5-20251001", True),
|
||||||
|
("claude-fable-5", False),
|
||||||
|
("claude-mythos-5", False),
|
||||||
|
("claude-mythos-preview", False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_enable_sampling_parameters_for_current_models(mock_anthropic_client, model, expected):
|
||||||
|
config = AnthropicConfig(model=model, api_key="test-key", top_p=0.9)
|
||||||
|
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 ("temperature" in call_kwargs) is expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_enable_sampling_parameters_respects_explicit_override(mock_anthropic_client):
|
||||||
|
config = AnthropicConfig(
|
||||||
|
model="claude-opus-4-6",
|
||||||
|
api_key="test-key",
|
||||||
|
top_p=0.9,
|
||||||
|
enable_sampling_parameters=False,
|
||||||
|
)
|
||||||
|
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 "temperature" not in call_kwargs
|
||||||
|
assert "top_p" not in call_kwargs
|
||||||
|
|||||||
Reference in New Issue
Block a user