From cca48ef6c9acfdeb6070f033e1bf54dc07f06544 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sat, 22 Nov 2025 15:21:56 +0900 Subject: [PATCH 1/4] feat(bedrock): support AWS_BEARER_TOKEN_BEDROCK env var --- src/anthropic/lib/bedrock/_auth.py | 7 +++++++ tests/lib/test_bedrock.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/anthropic/lib/bedrock/_auth.py b/src/anthropic/lib/bedrock/_auth.py index 0a8b2109..d70e1037 100644 --- a/src/anthropic/lib/bedrock/_auth.py +++ b/src/anthropic/lib/bedrock/_auth.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING +import os import httpx from ..._utils import lru_cache @@ -42,6 +43,12 @@ def get_auth_headers( profile: str | None, data: str | None, ) -> dict[str, str]: + bedrock_bearer = os.getenv("AWS_BEARER_TOKEN_BEDROCK") + if bedrock_bearer is not None: + headers = headers.copy() + headers["Authorization"] = f"Bearer {bedrock_bearer}" + return dict(headers) + from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest diff --git a/tests/lib/test_bedrock.py b/tests/lib/test_bedrock.py index fe62da43..f078c31f 100644 --- a/tests/lib/test_bedrock.py +++ b/tests/lib/test_bedrock.py @@ -195,3 +195,32 @@ def test_region_infer_from_specified_profile( client = AnthropicBedrock() assert client.aws_region == next(profile for profile in profiles if profile["name"] == aws_profile)["region"] + + +@pytest.mark.respx() +def test_bearer_token_env(monkeypatch: t.Any, respx_mock: MockRouter) -> None: + respx_mock.post(re.compile(r"https://bedrock-runtime\.us-east-1\.amazonaws\.com/model/.*/invoke")).mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + monkeypatch.setenv("AWS_BEARER_TOKEN_BEDROCK", "test-bearer-token") + + sync_client.messages.create( + max_tokens=1024, + messages=[ + { + "role": "user", + "content": "Say hello there!", + } + ], + model="anthropic.claude-3-5-sonnet-20241022-v2:0", + extra_headers={"Custom-Header": "CustomValue"}, + ) + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 1 + auth_header = calls[0].request.headers.get("Authorization") + assert auth_header == "Bearer test-bearer-token" + + # Verify that custom headers are preserved + custom_header = calls[0].request.headers.get("Custom-Header") + assert custom_header == "CustomValue" From 52065a972ab7c6b180591a4f0680f19ffe0c7d66 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sat, 6 Dec 2025 11:53:37 +0900 Subject: [PATCH 2/4] move to client arg --- README.md | 1 + src/anthropic/lib/bedrock/_auth.py | 7 +++-- src/anthropic/lib/bedrock/_client.py | 7 +++++ tests/lib/test_bedrock.py | 40 +++++++++++++++++++++------- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 12d4910f..df82def2 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,7 @@ AnthropicBedrock( aws_secret_key='...', aws_access_key='...', aws_session_token='...', + api_key='...', # defaults to AWS_BEARER_TOKEN_BEDROCK envvar ) ``` diff --git a/src/anthropic/lib/bedrock/_auth.py b/src/anthropic/lib/bedrock/_auth.py index d70e1037..f0520543 100644 --- a/src/anthropic/lib/bedrock/_auth.py +++ b/src/anthropic/lib/bedrock/_auth.py @@ -2,7 +2,6 @@ from typing import TYPE_CHECKING -import os import httpx from ..._utils import lru_cache @@ -42,11 +41,11 @@ def get_auth_headers( region: str | None, profile: str | None, data: str | None, + aws_bearer_token_bedrock: str | None = None, ) -> dict[str, str]: - bedrock_bearer = os.getenv("AWS_BEARER_TOKEN_BEDROCK") - if bedrock_bearer is not None: + if aws_bearer_token_bedrock is not None: headers = headers.copy() - headers["Authorization"] = f"Bearer {bedrock_bearer}" + headers["Authorization"] = f"Bearer {aws_bearer_token_bedrock}" return dict(headers) from botocore.auth import SigV4Auth diff --git a/src/anthropic/lib/bedrock/_client.py b/src/anthropic/lib/bedrock/_client.py index 013d2702..528fba62 100644 --- a/src/anthropic/lib/bedrock/_client.py +++ b/src/anthropic/lib/bedrock/_client.py @@ -156,6 +156,7 @@ def __init__( # outlining your use-case to help us decide if it should be # part of our public interface in the future. _strict_response_validation: bool = False, + api_key: str | None = None, ) -> None: self.aws_secret_key = aws_secret_key @@ -166,6 +167,11 @@ def __init__( self.aws_session_token = aws_session_token + if api_key is None: + api_key = os.environ.get("AWS_BEARER_TOKEN_BEDROCK") + + self.api_key = api_key + if base_url is None: base_url = os.environ.get("ANTHROPIC_BEDROCK_BASE_URL") if base_url is None: @@ -210,6 +216,7 @@ def _prepare_request(self, request: httpx.Request) -> None: region=self.aws_region or "us-east-1", profile=self.aws_profile, data=data, + aws_bearer_token_bedrock=self.api_key, ) request.headers.update(headers) diff --git a/tests/lib/test_bedrock.py b/tests/lib/test_bedrock.py index f078c31f..ab145340 100644 --- a/tests/lib/test_bedrock.py +++ b/tests/lib/test_bedrock.py @@ -198,14 +198,16 @@ def test_region_infer_from_specified_profile( @pytest.mark.respx() -def test_bearer_token_env(monkeypatch: t.Any, respx_mock: MockRouter) -> None: +def test_bearer_token_client_args(respx_mock: MockRouter) -> None: respx_mock.post(re.compile(r"https://bedrock-runtime\.us-east-1\.amazonaws\.com/model/.*/invoke")).mock( return_value=httpx.Response(200, json={"foo": "bar"}) ) - monkeypatch.setenv("AWS_BEARER_TOKEN_BEDROCK", "test-bearer-token") - - sync_client.messages.create( + client = AnthropicBedrock( + aws_region="us-east-1", + api_key="test-bearer-token-from-args" + ) + client.messages.create( max_tokens=1024, messages=[ { @@ -214,13 +216,33 @@ def test_bearer_token_env(monkeypatch: t.Any, respx_mock: MockRouter) -> None: } ], model="anthropic.claude-3-5-sonnet-20241022-v2:0", - extra_headers={"Custom-Header": "CustomValue"}, ) calls = cast("list[MockRequestCall]", respx_mock.calls) assert len(calls) == 1 auth_header = calls[0].request.headers.get("Authorization") - assert auth_header == "Bearer test-bearer-token" + assert auth_header == "Bearer test-bearer-token-from-args" - # Verify that custom headers are preserved - custom_header = calls[0].request.headers.get("Custom-Header") - assert custom_header == "CustomValue" + +@pytest.mark.respx() +def test_bearer_token_env(monkeypatch: pytest.MonkeyPatch, respx_mock: MockRouter) -> None: + respx_mock.post(re.compile(r"https://bedrock-runtime\.us-east-1\.amazonaws\.com/model/.*/invoke")).mock( + return_value=httpx.Response(200, json={"foo": "bar"}) + ) + + monkeypatch.setenv("AWS_BEARER_TOKEN_BEDROCK", "test-bearer-token-from-env") + + client = AnthropicBedrock(aws_region="us-east-1") + client.messages.create( + max_tokens=1024, + messages=[ + { + "role": "user", + "content": "Say hello there!", + } + ], + model="anthropic.claude-3-5-sonnet-20241022-v2:0", + ) + calls = cast("list[MockRequestCall]", respx_mock.calls) + assert len(calls) == 1 + auth_header = calls[0].request.headers.get("Authorization") + assert auth_header == "Bearer test-bearer-token-from-env" From 872cc59779be089bb3da7d9be65519b49c66fb54 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 9 Dec 2025 00:26:16 +0000 Subject: [PATCH 3/4] ruff format --- tests/lib/test_bedrock.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/lib/test_bedrock.py b/tests/lib/test_bedrock.py index ab145340..210b499a 100644 --- a/tests/lib/test_bedrock.py +++ b/tests/lib/test_bedrock.py @@ -203,10 +203,7 @@ def test_bearer_token_client_args(respx_mock: MockRouter) -> None: return_value=httpx.Response(200, json={"foo": "bar"}) ) - client = AnthropicBedrock( - aws_region="us-east-1", - api_key="test-bearer-token-from-args" - ) + client = AnthropicBedrock(aws_region="us-east-1", api_key="test-bearer-token-from-args") client.messages.create( max_tokens=1024, messages=[ From 1beed86aecfb8e2e0e1ba8332e21231467035f14 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 10 Dec 2025 00:06:39 +0000 Subject: [PATCH 4/4] add bedrock bearer token support to async client too --- src/anthropic/lib/bedrock/_client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/anthropic/lib/bedrock/_client.py b/src/anthropic/lib/bedrock/_client.py index 528fba62..443a135e 100644 --- a/src/anthropic/lib/bedrock/_client.py +++ b/src/anthropic/lib/bedrock/_client.py @@ -305,6 +305,7 @@ def __init__( # outlining your use-case to help us decide if it should be # part of our public interface in the future. _strict_response_validation: bool = False, + api_key: str | None = None, ) -> None: self.aws_secret_key = aws_secret_key @@ -315,6 +316,11 @@ def __init__( self.aws_session_token = aws_session_token + if api_key is None: + api_key = os.environ.get("AWS_BEARER_TOKEN_BEDROCK") + + self.api_key = api_key + if base_url is None: base_url = os.environ.get("ANTHROPIC_BEDROCK_BASE_URL") if base_url is None: @@ -359,6 +365,7 @@ async def _prepare_request(self, request: httpx.Request) -> None: region=self.aws_region or "us-east-1", profile=self.aws_profile, data=data, + aws_bearer_token_bedrock=self.api_key, ) request.headers.update(headers)