diff --git a/src/strands/models/anthropic.py b/src/strands/models/anthropic.py index b5f6fcf91..c602a3926 100644 --- a/src/strands/models/anthropic.py +++ b/src/strands/models/anthropic.py @@ -8,6 +8,7 @@ import logging import mimetypes from collections.abc import AsyncGenerator +from importlib.metadata import PackageNotFoundError, version from typing import Any, TypedDict, TypeVar, cast import anthropic @@ -76,6 +77,19 @@ def __init__(self, *, client_args: dict[str, Any] | None = None, **model_config: logger.debug("config=<%s> | initializing", self.config) client_args = client_args or {} + + # Add User-Agent header if not already set + if "default_headers" not in client_args: + client_args["default_headers"] = {} + else: + client_args["default_headers"] = {**client_args["default_headers"]} + if "User-Agent" not in client_args["default_headers"]: + try: + strands_version = version("strands-agents") + except PackageNotFoundError: + strands_version = "unknown" + client_args["default_headers"]["User-Agent"] = f"strands-agents/{strands_version}" + self.client = anthropic.AsyncAnthropic(**client_args) @override diff --git a/tests/strands/models/test_anthropic.py b/tests/strands/models/test_anthropic.py index c5aff8062..7c9a92aee 100644 --- a/tests/strands/models/test_anthropic.py +++ b/tests/strands/models/test_anthropic.py @@ -63,6 +63,38 @@ def test__init__model_configs(anthropic_client, model_id, max_tokens): assert tru_temperature == exp_temperature +def test__init__sets_user_agent_header(anthropic_client, model_id, max_tokens): + _ = anthropic_client + + AnthropicModel(model_id=model_id, max_tokens=max_tokens) + + call_kwargs = strands.models.anthropic.anthropic.AsyncAnthropic.call_args[1] + assert "default_headers" in call_kwargs + assert "User-Agent" in call_kwargs["default_headers"] + assert call_kwargs["default_headers"]["User-Agent"].startswith("strands-agents/") + + +def test__init__preserves_custom_user_agent_header(anthropic_client, model_id, max_tokens): + _ = anthropic_client + + custom_headers = {"User-Agent": "my-custom-agent/1.0"} + AnthropicModel(model_id=model_id, max_tokens=max_tokens, client_args={"default_headers": custom_headers}) + + call_kwargs = strands.models.anthropic.anthropic.AsyncAnthropic.call_args[1] + assert call_kwargs["default_headers"]["User-Agent"] == "my-custom-agent/1.0" + + +def test__init__does_not_mutate_caller_headers(anthropic_client, model_id, max_tokens): + _ = anthropic_client + + original_headers = {"X-Custom": "value"} + client_args = {"default_headers": original_headers} + AnthropicModel(model_id=model_id, max_tokens=max_tokens, client_args=client_args) + + assert "User-Agent" not in original_headers + assert original_headers == {"X-Custom": "value"} + + def test_update_config(model, model_id): model.update_config(model_id=model_id)