diff --git a/docs/integrations/bedrock.md b/docs/integrations/bedrock.md index 2ad2c0dad..a79a5eb91 100644 --- a/docs/integrations/bedrock.md +++ b/docs/integrations/bedrock.md @@ -65,6 +65,29 @@ Or configure using AWS CLI: aws configure ``` +### Bedrock API Key (Bearer Token) Authentication + +Instead of AWS credentials, you can authenticate with an [Amazon Bedrock API key](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html). Bedrock offers short-term keys (up to 12 hours, recommended for production) and long-term keys (recommended only for exploration). + +Pass the key directly via `api_key`: + +```python +import instructor + +client = instructor.from_provider( + "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0", + api_key="your-bedrock-api-key", +) +``` + +Or set the environment variable that botocore reads natively: + +```bash +export AWS_BEARER_TOKEN_BEDROCK=your-bedrock-api-key +``` + +> **Note:** Bearer token authentication requires `boto3>=1.39.0`. When a Bedrock API key is set, it takes precedence over AWS credentials for Bedrock requests. + ## Sync Example ```python diff --git a/instructor/v2/auto_client.py b/instructor/v2/auto_client.py index 54335e960..75a3f4726 100644 --- a/instructor/v2/auto_client.py +++ b/instructor/v2/auto_client.py @@ -969,7 +969,7 @@ def _build_bedrock( model_name: str, async_client: bool, mode: Mode | None, - api_key: str | None, # noqa: ARG001 + api_key: str | None, kwargs: dict[str, Any], provider_info: dict[str, str], ) -> InstructorType: @@ -987,6 +987,13 @@ def _build_bedrock( ) region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1") + # Bedrock API keys (bearer tokens) authenticate without SigV4 credentials. + # botocore >= 1.39.0 reads AWS_BEARER_TOKEN_BEDROCK at request time, so + # exporting the key is the supported way to enable bearer auth. + if api_key: + os.environ["AWS_BEARER_TOKEN_BEDROCK"] = api_key + logger.debug("Using Bedrock API key (bearer token) authentication") + # Extract AWS-specific parameters # Dictionary to collect AWS credentials and session parameters for boto3 client aws_kwargs = {} diff --git a/tests/v2/test_auto_client_deterministic.py b/tests/v2/test_auto_client_deterministic.py index cae767607..6577bfe7b 100644 --- a/tests/v2/test_auto_client_deterministic.py +++ b/tests/v2/test_auto_client_deterministic.py @@ -205,6 +205,69 @@ def fake_from_bedrock(_client: Any, **kwargs: Any) -> dict[str, Any]: assert calls[1]["mode"] == Mode.MD_JSON +def test_build_bedrock_api_key_enables_bearer_token_auth( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import os + + boto3_module = ModuleType("boto3") + setattr(boto3_module, "client", lambda *_args, **_kwargs: object()) # noqa: B010 + monkeypatch.setitem(__import__("sys").modules, "boto3", boto3_module) + + import instructor.v2.providers.bedrock.client as bedrock_client + + monkeypatch.setattr( + bedrock_client, "from_bedrock", lambda _client, **kwargs: kwargs + ) + + # Ensure the variable is absent but restored to its original state on teardown + monkeypatch.setenv("AWS_BEARER_TOKEN_BEDROCK", "sentinel") + monkeypatch.delenv("AWS_BEARER_TOKEN_BEDROCK") + + auto_client._build_bedrock( + provider="bedrock", + model_name="anthropic.claude-3-7-sonnet", + async_client=False, + mode=None, + api_key="bedrock-api-key", + kwargs={}, + provider_info={"provider": "bedrock", "operation": "initialize"}, + ) + + assert os.environ["AWS_BEARER_TOKEN_BEDROCK"] == "bedrock-api-key" + + +def test_build_bedrock_without_api_key_leaves_bearer_token_unset( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import os + + boto3_module = ModuleType("boto3") + setattr(boto3_module, "client", lambda *_args, **_kwargs: object()) # noqa: B010 + monkeypatch.setitem(__import__("sys").modules, "boto3", boto3_module) + + import instructor.v2.providers.bedrock.client as bedrock_client + + monkeypatch.setattr( + bedrock_client, "from_bedrock", lambda _client, **kwargs: kwargs + ) + + monkeypatch.setenv("AWS_BEARER_TOKEN_BEDROCK", "sentinel") + monkeypatch.delenv("AWS_BEARER_TOKEN_BEDROCK") + + auto_client._build_bedrock( + provider="bedrock", + model_name="anthropic.claude-3-7-sonnet", + async_client=False, + mode=None, + api_key=None, + kwargs={}, + provider_info={"provider": "bedrock", "operation": "initialize"}, + ) + + assert "AWS_BEARER_TOKEN_BEDROCK" not in os.environ + + def test_build_ollama_uses_tool_mode_only_for_tool_capable_models( monkeypatch: pytest.MonkeyPatch, ) -> None: