This is an automated email from the ASF dual-hosted git repository.
guan404ming pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new db6c95ae92e Reject invalid Amazon Bedrock model IDs in the Anthropic
provider (#69404)
db6c95ae92e is described below
commit db6c95ae92eb7611fa0c5b1fa761f8f6f9c58918
Author: Guan-Ming Chiu <[email protected]>
AuthorDate: Mon Jul 6 22:40:53 2026 +0800
Reject invalid Amazon Bedrock model IDs in the Anthropic provider (#69404)
---
.../airflow/providers/anthropic/hooks/anthropic.py | 16 +++++++++-
.../tests/unit/anthropic/hooks/test_anthropic.py | 34 ++++++++++++++++++++++
2 files changed, 49 insertions(+), 1 deletion(-)
diff --git
a/providers/anthropic/src/airflow/providers/anthropic/hooks/anthropic.py
b/providers/anthropic/src/airflow/providers/anthropic/hooks/anthropic.py
index dbefdf84d5d..4d13240422c 100644
--- a/providers/anthropic/src/airflow/providers/anthropic/hooks/anthropic.py
+++ b/providers/anthropic/src/airflow/providers/anthropic/hooks/anthropic.py
@@ -297,6 +297,20 @@ class AnthropicHook(BaseHook):
kwargs["scope"] = wif["scope"]
return WorkloadIdentityCredentials(**kwargs)
+ def _resolve_model(self, model: str | None) -> str:
+ """Resolve the effective model id; Bedrock rejects a bare id, so
require its prefix."""
+ resolved = model or self.default_model
+ # Valid Bedrock ids either start with the ``anthropic.`` provider
prefix or carry a
+ # region/profile prefix as a dotted component (e.g. ``us.anthropic.``,
``global.anthropic.``).
+ is_bedrock_model_id = resolved.startswith("anthropic.") or
".anthropic." in resolved
+ if self.platform == "bedrock" and not is_bedrock_model_id:
+ raise AnthropicError(
+ f"Model {resolved!r} is not a valid Amazon Bedrock model id.
Bedrock ids carry a "
+ "provider/region prefix (e.g.
'global.anthropic.claude-opus-4-6-v1'); set one via "
+ "the 'model' argument or the connection's extra['model']."
+ )
+ return resolved
+
def _require_first_party(self, feature: str) -> None:
if self.platform not in FIRST_PARTY_PLATFORMS:
raise AnthropicError(
@@ -348,7 +362,7 @@ class AnthropicHook(BaseHook):
:param system: Optional system prompt.
"""
params: dict[str, Any] = {
- "model": model or self.default_model,
+ "model": self._resolve_model(model),
"max_tokens": max_tokens,
"messages": messages,
**kwargs,
diff --git a/providers/anthropic/tests/unit/anthropic/hooks/test_anthropic.py
b/providers/anthropic/tests/unit/anthropic/hooks/test_anthropic.py
index 1f0974d540c..44a448b2011 100644
--- a/providers/anthropic/tests/unit/anthropic/hooks/test_anthropic.py
+++ b/providers/anthropic/tests/unit/anthropic/hooks/test_anthropic.py
@@ -185,6 +185,40 @@ class TestDefaultModel:
hook.create_agent(name="a")
assert client.beta.agents.create.call_args.kwargs["model"] ==
"claude-sonnet-4-6"
+ @pytest.mark.parametrize(
+ "model",
+ [None, "my-anthropic.model"], # bare default id and a substring that
is not a real prefix
+ )
+ def test_bedrock_rejects_invalid_model_id(self, model):
+ extra = {"platform": "bedrock", **({"model": model} if model else {})}
+ hook, client = _make_hook(extra=extra)
+ with pytest.raises(AnthropicError, match="not a valid Amazon Bedrock
model id"):
+ hook.create_message([{"role": "user", "content": "hi"}])
+ client.messages.create.assert_not_called()
+
+ @pytest.mark.parametrize(
+ ("platform", "model", "expected"),
+ [
+ (
+ "bedrock",
+ "anthropic.claude-sonnet-4-5-20250929-v1:0",
+ "anthropic.claude-sonnet-4-5-20250929-v1:0",
+ ),
+ ("bedrock", "global.anthropic.claude-opus-4-6-v1",
"global.anthropic.claude-opus-4-6-v1"),
+ (
+ "bedrock",
+ "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
+ "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
+ ),
+ ("vertex", None, DEFAULT_MODEL), # non-Bedrock platforms skip the
guard
+ ],
+ )
+ def test_accepts_valid_model_id(self, platform, model, expected):
+ extra = {"platform": platform, **({"model": model} if model else {})}
+ hook, client = _make_hook(extra=extra)
+ hook.create_message([{"role": "user", "content": "hi"}])
+ assert client.messages.create.call_args.kwargs["model"] == expected
+
class TestManagedAgentsHook:
def _hook_with_client(self, extra=None):