mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
Bedrock rejects ``global-anthropic-claude-opus-4-7`` with ``HTTP 400:
The provided model identifier is invalid`` because its inference
profile IDs embed structural dots
(``global.anthropic.claude-opus-4-7``) that ``normalize_model_name``
was converting to hyphens. ``AIAgent._anthropic_preserve_dots`` did
not include ``bedrock`` in its provider allowlist, so every Claude-on-
Bedrock request through the AnthropicBedrock SDK path shipped with
the mangled model ID and failed.
Root cause
----------
``run_agent.py:_anthropic_preserve_dots`` (previously line 6589)
controls whether ``agent.anthropic_adapter.normalize_model_name``
converts dots to hyphens. The function listed Alibaba, MiniMax,
OpenCode Go/Zen and ZAI but not Bedrock, so when a user set
``provider: bedrock`` with a dotted inference-profile model the flag
returned False and ``normalize_model_name`` mangled every dot in the
ID. All four call sites in run_agent.py
(``build_anthropic_kwargs`` + three fallback / review / summary paths
at lines 6707, 7343, 8408, 8440) read from this same helper.
The bug shape matches #5211 for opencode-go, which was fixed in commit
f77be22c by extending this same allowlist.
Fix
---
* Add ``"bedrock"`` to the provider allowlist.
* Add ``"bedrock-runtime."`` to the base-URL heuristic as
defense-in-depth, so a custom-provider-shaped config with
``base_url: https://bedrock-runtime.<region>.amazonaws.com`` also
takes the preserve-dots path even if ``provider`` isn't explicitly
set to ``"bedrock"``. This mirrors how the code downstream at
run_agent.py:759 already treats either signal as "this is Bedrock".
Bedrock model ID shapes covered
-------------------------------
| Shape | Preserved |
| --- | --- |
| ``global.anthropic.claude-opus-4-7`` (reporter's exact ID) | ✓ |
| ``us.anthropic.claude-sonnet-4-5-20250929-v1:0`` | ✓ |
| ``apac.anthropic.claude-haiku-4-5`` | ✓ |
| ``anthropic.claude-3-5-sonnet-20241022-v2:0`` (foundation) | ✓ |
| ``eu.anthropic.claude-3-5-sonnet`` (regional inference profile) | ✓ |
Non-Claude Bedrock models (Nova, Llama, DeepSeek) take the
``bedrock_converse`` / boto3 path which does not call
``normalize_model_name``, so they were never affected by this bug
and remain unaffected by the fix.
Narrow scope — explicitly not changed
-------------------------------------
* ``bedrock_converse`` path (non-Claude Bedrock models) — already
correct; no ``normalize_model_name`` in that pipeline.
* Provider aliases (``aws``, ``aws-bedrock``, ``amazon``,
``amazon-bedrock``) — if a user bypasses the alias-normalization
pipeline and passes ``provider="aws"`` directly, the base-URL
heuristic still catches it because Bedrock always uses a
``bedrock-runtime.`` endpoint. Adding the aliases themselves to the
provider set is cheap but would be scope creep for this fix.
* No other places in ``agent/anthropic_adapter.py`` mangle dots, so
the fix is confined to ``_anthropic_preserve_dots``.
Regression coverage
-------------------
``tests/agent/test_bedrock_integration.py`` gains three new classes:
* ``TestBedrockPreserveDotsFlag`` (5 tests): flag returns True for
``provider="bedrock"`` and for Bedrock runtime URLs (us-east-1 and
ap-northeast-2 — the reporter's region); returns False for non-
Bedrock AWS URLs like ``s3.us-east-1.amazonaws.com``; canary that
Anthropic-native still returns False.
* ``TestBedrockModelNameNormalization`` (5 tests): every documented
Bedrock model-ID shape survives ``normalize_model_name`` with the
flag on; inverse canary pins that ``preserve_dots=False`` still
mangles (so a future refactor can't decouple the flag from its
effect).
* ``TestBedrockBuildAnthropicKwargsEndToEnd`` (2 tests): integration
through ``build_anthropic_kwargs`` shows the reporter's exact model
ID ends up unmangled in the outgoing kwargs.
Three of the new flag tests fail on unpatched ``origin/main`` with
``assert False is True`` (preserve-dots returning False for Bedrock),
confirming the regression is caught.
Validation
----------
``source venv/bin/activate && python -m pytest
tests/agent/test_bedrock_integration.py tests/agent/test_minimax_provider.py
-q`` -> 84 passed (40 new bedrock tests + 44 pre-existing, including
the minimax canaries that pin the pattern this fix mirrors).
CI-aligned broad suite: 12827 passed, 39 skipped, 19 pre-existing
baseline failures (all reproduce on clean ``origin/main``; none in
the touched code path).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
440 lines
19 KiB
Python
440 lines
19 KiB
Python
"""Integration tests for the AWS Bedrock provider wiring.
|
|
|
|
Verifies that the Bedrock provider is correctly registered in the
|
|
provider registry, model catalog, and runtime resolution pipeline.
|
|
These tests do NOT require AWS credentials or boto3 — all AWS calls
|
|
are mocked.
|
|
|
|
Note: Tests that import ``hermes_cli.auth`` or ``hermes_cli.runtime_provider``
|
|
require Python 3.10+ due to ``str | None`` type syntax in the import chain.
|
|
"""
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
class TestProviderRegistry:
|
|
"""Verify Bedrock is registered in PROVIDER_REGISTRY."""
|
|
|
|
def test_bedrock_in_registry(self):
|
|
from hermes_cli.auth import PROVIDER_REGISTRY
|
|
assert "bedrock" in PROVIDER_REGISTRY
|
|
|
|
def test_bedrock_auth_type_is_aws_sdk(self):
|
|
from hermes_cli.auth import PROVIDER_REGISTRY
|
|
pconfig = PROVIDER_REGISTRY["bedrock"]
|
|
assert pconfig.auth_type == "aws_sdk"
|
|
|
|
def test_bedrock_has_no_api_key_env_vars(self):
|
|
"""Bedrock uses the AWS SDK credential chain, not API keys."""
|
|
from hermes_cli.auth import PROVIDER_REGISTRY
|
|
pconfig = PROVIDER_REGISTRY["bedrock"]
|
|
assert pconfig.api_key_env_vars == ()
|
|
|
|
def test_bedrock_base_url_env_var(self):
|
|
from hermes_cli.auth import PROVIDER_REGISTRY
|
|
pconfig = PROVIDER_REGISTRY["bedrock"]
|
|
assert pconfig.base_url_env_var == "BEDROCK_BASE_URL"
|
|
|
|
|
|
class TestProviderAliases:
|
|
"""Verify Bedrock aliases resolve correctly."""
|
|
|
|
def test_aws_alias(self):
|
|
from hermes_cli.models import _PROVIDER_ALIASES
|
|
assert _PROVIDER_ALIASES.get("aws") == "bedrock"
|
|
|
|
def test_aws_bedrock_alias(self):
|
|
from hermes_cli.models import _PROVIDER_ALIASES
|
|
assert _PROVIDER_ALIASES.get("aws-bedrock") == "bedrock"
|
|
|
|
def test_amazon_bedrock_alias(self):
|
|
from hermes_cli.models import _PROVIDER_ALIASES
|
|
assert _PROVIDER_ALIASES.get("amazon-bedrock") == "bedrock"
|
|
|
|
def test_amazon_alias(self):
|
|
from hermes_cli.models import _PROVIDER_ALIASES
|
|
assert _PROVIDER_ALIASES.get("amazon") == "bedrock"
|
|
|
|
|
|
class TestProviderLabels:
|
|
"""Verify Bedrock appears in provider labels."""
|
|
|
|
def test_bedrock_label(self):
|
|
from hermes_cli.models import _PROVIDER_LABELS
|
|
assert _PROVIDER_LABELS.get("bedrock") == "AWS Bedrock"
|
|
|
|
|
|
class TestModelCatalog:
|
|
"""Verify Bedrock has a static model fallback list."""
|
|
|
|
def test_bedrock_has_curated_models(self):
|
|
from hermes_cli.models import _PROVIDER_MODELS
|
|
models = _PROVIDER_MODELS.get("bedrock", [])
|
|
assert len(models) > 0
|
|
|
|
def test_bedrock_models_include_claude(self):
|
|
from hermes_cli.models import _PROVIDER_MODELS
|
|
models = _PROVIDER_MODELS.get("bedrock", [])
|
|
claude_models = [m for m in models if "anthropic.claude" in m]
|
|
assert len(claude_models) > 0
|
|
|
|
def test_bedrock_models_include_nova(self):
|
|
from hermes_cli.models import _PROVIDER_MODELS
|
|
models = _PROVIDER_MODELS.get("bedrock", [])
|
|
nova_models = [m for m in models if "amazon.nova" in m]
|
|
assert len(nova_models) > 0
|
|
|
|
|
|
class TestResolveProvider:
|
|
"""Verify resolve_provider() handles bedrock correctly."""
|
|
|
|
def test_explicit_bedrock_resolves(self, monkeypatch):
|
|
"""When user explicitly requests 'bedrock', it should resolve."""
|
|
from hermes_cli.auth import PROVIDER_REGISTRY
|
|
# bedrock is in the registry, so resolve_provider should return it
|
|
from hermes_cli.auth import resolve_provider
|
|
result = resolve_provider("bedrock")
|
|
assert result == "bedrock"
|
|
|
|
def test_aws_alias_resolves_to_bedrock(self):
|
|
from hermes_cli.auth import resolve_provider
|
|
result = resolve_provider("aws")
|
|
assert result == "bedrock"
|
|
|
|
def test_amazon_bedrock_alias_resolves(self):
|
|
from hermes_cli.auth import resolve_provider
|
|
result = resolve_provider("amazon-bedrock")
|
|
assert result == "bedrock"
|
|
|
|
def test_auto_detect_with_aws_credentials(self, monkeypatch):
|
|
"""When AWS credentials are present and no other provider is configured,
|
|
auto-detect should find bedrock."""
|
|
from hermes_cli.auth import resolve_provider
|
|
|
|
# Clear all other provider env vars
|
|
for var in ["OPENAI_API_KEY", "OPENROUTER_API_KEY", "ANTHROPIC_API_KEY",
|
|
"ANTHROPIC_TOKEN", "GOOGLE_API_KEY", "DEEPSEEK_API_KEY"]:
|
|
monkeypatch.delenv(var, raising=False)
|
|
|
|
# Set AWS credentials
|
|
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE")
|
|
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY")
|
|
|
|
# Mock the auth store to have no active provider
|
|
with patch("hermes_cli.auth._load_auth_store", return_value={}):
|
|
result = resolve_provider("auto")
|
|
assert result == "bedrock"
|
|
|
|
|
|
class TestRuntimeProvider:
|
|
"""Verify resolve_runtime_provider() handles bedrock correctly."""
|
|
|
|
def test_bedrock_runtime_resolution(self, monkeypatch):
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
|
|
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE")
|
|
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY")
|
|
monkeypatch.setenv("AWS_REGION", "eu-west-1")
|
|
|
|
# Mock resolve_provider to return bedrock
|
|
with patch("hermes_cli.runtime_provider.resolve_provider", return_value="bedrock"), \
|
|
patch("hermes_cli.runtime_provider._get_model_config", return_value={"provider": "bedrock"}):
|
|
result = resolve_runtime_provider(requested="bedrock")
|
|
|
|
assert result["provider"] == "bedrock"
|
|
assert result["api_mode"] == "bedrock_converse"
|
|
assert result["region"] == "eu-west-1"
|
|
assert "bedrock-runtime.eu-west-1.amazonaws.com" in result["base_url"]
|
|
assert result["api_key"] == "aws-sdk"
|
|
|
|
def test_bedrock_runtime_default_region(self, monkeypatch):
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
|
|
monkeypatch.setenv("AWS_PROFILE", "default")
|
|
monkeypatch.delenv("AWS_REGION", raising=False)
|
|
monkeypatch.delenv("AWS_DEFAULT_REGION", raising=False)
|
|
|
|
with patch("hermes_cli.runtime_provider.resolve_provider", return_value="bedrock"), \
|
|
patch("hermes_cli.runtime_provider._get_model_config", return_value={"provider": "bedrock"}):
|
|
result = resolve_runtime_provider(requested="bedrock")
|
|
|
|
assert result["region"] == "us-east-1"
|
|
|
|
def test_bedrock_runtime_no_credentials_raises_on_auto_detect(self, monkeypatch):
|
|
"""When bedrock is auto-detected (not explicitly requested) and no
|
|
credentials are found, runtime resolution should raise AuthError."""
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
from hermes_cli.auth import AuthError
|
|
|
|
# Clear all AWS env vars
|
|
for var in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_PROFILE",
|
|
"AWS_BEARER_TOKEN_BEDROCK", "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
|
|
"AWS_WEB_IDENTITY_TOKEN_FILE"]:
|
|
monkeypatch.delenv(var, raising=False)
|
|
|
|
# Mock both the provider resolution and boto3's credential chain
|
|
mock_session = MagicMock()
|
|
mock_session.get_credentials.return_value = None
|
|
with patch("hermes_cli.runtime_provider.resolve_provider", return_value="bedrock"), \
|
|
patch("hermes_cli.runtime_provider._get_model_config", return_value={"provider": "bedrock"}), \
|
|
patch("hermes_cli.runtime_provider.resolve_requested_provider", return_value="auto"), \
|
|
patch.dict("sys.modules", {"botocore": MagicMock(), "botocore.session": MagicMock()}):
|
|
import botocore.session as _bs
|
|
_bs.get_session = MagicMock(return_value=mock_session)
|
|
with pytest.raises(AuthError, match="No AWS credentials"):
|
|
resolve_runtime_provider(requested="auto")
|
|
|
|
def test_bedrock_runtime_explicit_skips_credential_check(self, monkeypatch):
|
|
"""When user explicitly requests bedrock, trust boto3's credential chain
|
|
even if env-var detection finds nothing (covers IMDS, SSO, etc.)."""
|
|
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
|
|
# No AWS env vars set — but explicit bedrock request should not raise
|
|
for var in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_PROFILE",
|
|
"AWS_BEARER_TOKEN_BEDROCK"]:
|
|
monkeypatch.delenv(var, raising=False)
|
|
|
|
with patch("hermes_cli.runtime_provider.resolve_provider", return_value="bedrock"), \
|
|
patch("hermes_cli.runtime_provider._get_model_config", return_value={"provider": "bedrock"}):
|
|
result = resolve_runtime_provider(requested="bedrock")
|
|
assert result["provider"] == "bedrock"
|
|
assert result["api_mode"] == "bedrock_converse"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# providers.py integration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestProvidersModule:
|
|
"""Verify bedrock is wired into hermes_cli/providers.py."""
|
|
|
|
def test_bedrock_alias_in_providers(self):
|
|
from hermes_cli.providers import ALIASES
|
|
assert ALIASES.get("bedrock") is None # "bedrock" IS the canonical name, not an alias
|
|
assert ALIASES.get("aws") == "bedrock"
|
|
assert ALIASES.get("aws-bedrock") == "bedrock"
|
|
|
|
def test_bedrock_transport_mapping(self):
|
|
from hermes_cli.providers import TRANSPORT_TO_API_MODE
|
|
assert TRANSPORT_TO_API_MODE.get("bedrock_converse") == "bedrock_converse"
|
|
|
|
def test_determine_api_mode_from_bedrock_url(self):
|
|
from hermes_cli.providers import determine_api_mode
|
|
assert determine_api_mode(
|
|
"unknown", "https://bedrock-runtime.us-east-1.amazonaws.com"
|
|
) == "bedrock_converse"
|
|
|
|
def test_label_override(self):
|
|
from hermes_cli.providers import _LABEL_OVERRIDES
|
|
assert _LABEL_OVERRIDES.get("bedrock") == "AWS Bedrock"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Error classifier integration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestErrorClassifierBedrock:
|
|
"""Verify Bedrock error patterns are in the global error classifier."""
|
|
|
|
def test_throttling_in_rate_limit_patterns(self):
|
|
from agent.error_classifier import _RATE_LIMIT_PATTERNS
|
|
assert "throttlingexception" in _RATE_LIMIT_PATTERNS
|
|
|
|
def test_context_overflow_patterns(self):
|
|
from agent.error_classifier import _CONTEXT_OVERFLOW_PATTERNS
|
|
assert "input is too long" in _CONTEXT_OVERFLOW_PATTERNS
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# pyproject.toml bedrock extra
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestPackaging:
|
|
"""Verify bedrock optional dependency is declared."""
|
|
|
|
def test_bedrock_extra_exists(self):
|
|
import configparser
|
|
from pathlib import Path
|
|
# Read pyproject.toml to verify [bedrock] extra
|
|
toml_path = Path(__file__).parent.parent.parent / "pyproject.toml"
|
|
content = toml_path.read_text()
|
|
assert 'bedrock = ["boto3' in content
|
|
|
|
def test_bedrock_in_all_extra(self):
|
|
from pathlib import Path
|
|
content = (Path(__file__).parent.parent.parent / "pyproject.toml").read_text()
|
|
assert '"hermes-agent[bedrock]"' in content
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Model ID dot preservation — regression for #11976
|
|
# ---------------------------------------------------------------------------
|
|
# AWS Bedrock inference-profile model IDs embed structural dots:
|
|
#
|
|
# global.anthropic.claude-opus-4-7
|
|
# us.anthropic.claude-sonnet-4-5-20250929-v1:0
|
|
# apac.anthropic.claude-haiku-4-5
|
|
#
|
|
# ``agent.anthropic_adapter.normalize_model_name`` converts dots to hyphens
|
|
# unless the caller opts in via ``preserve_dots=True``. Before this fix,
|
|
# ``AIAgent._anthropic_preserve_dots`` returned False for the ``bedrock``
|
|
# provider, so Claude-on-Bedrock requests went out with
|
|
# ``global-anthropic-claude-opus-4-7`` (all dots mangled to hyphens) and
|
|
# Bedrock rejected them with:
|
|
#
|
|
# HTTP 400: The provided model identifier is invalid.
|
|
#
|
|
# The fix adds ``bedrock`` to the preserve-dots provider allowlist and
|
|
# ``bedrock-runtime.`` to the base-URL heuristic, mirroring the shape of
|
|
# the opencode-go fix for #5211 (commit f77be22c), which extended this
|
|
# same allowlist.
|
|
|
|
|
|
class TestBedrockPreserveDotsFlag:
|
|
"""``AIAgent._anthropic_preserve_dots`` must return True on Bedrock so
|
|
inference-profile IDs survive the normalize step intact."""
|
|
|
|
def test_bedrock_provider_preserves_dots(self):
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="bedrock", base_url="")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_bedrock_runtime_us_east_1_url_preserves_dots(self):
|
|
"""Defense-in-depth: even without an explicit ``provider="bedrock"``,
|
|
a ``bedrock-runtime.us-east-1.amazonaws.com`` base URL must not
|
|
mangle dots."""
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(
|
|
provider="custom",
|
|
base_url="https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
)
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_bedrock_runtime_ap_northeast_2_url_preserves_dots(self):
|
|
"""Reporter-reported region (ap-northeast-2) exercises the same
|
|
base-URL heuristic."""
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(
|
|
provider="custom",
|
|
base_url="https://bedrock-runtime.ap-northeast-2.amazonaws.com",
|
|
)
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is True
|
|
|
|
def test_non_bedrock_aws_url_does_not_preserve_dots(self):
|
|
"""Unrelated AWS endpoints (e.g. ``s3.us-east-1.amazonaws.com``)
|
|
must not accidentally activate the dot-preservation heuristic —
|
|
the heuristic is scoped to the ``bedrock-runtime.`` substring
|
|
specifically."""
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(
|
|
provider="custom",
|
|
base_url="https://s3.us-east-1.amazonaws.com",
|
|
)
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is False
|
|
|
|
def test_anthropic_native_still_does_not_preserve_dots(self):
|
|
"""Canary: adding Bedrock to the allowlist must not weaken the
|
|
existing Anthropic native behaviour — ``claude-sonnet-4.6`` still
|
|
becomes ``claude-sonnet-4-6`` for the Anthropic API."""
|
|
from types import SimpleNamespace
|
|
agent = SimpleNamespace(provider="anthropic", base_url="https://api.anthropic.com")
|
|
from run_agent import AIAgent
|
|
assert AIAgent._anthropic_preserve_dots(agent) is False
|
|
|
|
|
|
class TestBedrockModelNameNormalization:
|
|
"""End-to-end: ``normalize_model_name`` + the preserve-dots flag
|
|
reproduce the exact production request shape for each Bedrock model
|
|
family, confirming the fix resolves the reporter's HTTP 400."""
|
|
|
|
def test_global_anthropic_inference_profile_preserved(self):
|
|
"""The reporter's exact model ID."""
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
assert normalize_model_name(
|
|
"global.anthropic.claude-opus-4-7", preserve_dots=True
|
|
) == "global.anthropic.claude-opus-4-7"
|
|
|
|
def test_us_anthropic_dated_inference_profile_preserved(self):
|
|
"""Regional + dated Sonnet inference profile."""
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
assert normalize_model_name(
|
|
"us.anthropic.claude-sonnet-4-5-20250929-v1:0",
|
|
preserve_dots=True,
|
|
) == "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
|
|
def test_apac_anthropic_haiku_inference_profile_preserved(self):
|
|
"""APAC inference profile — same structural-dot shape."""
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
assert normalize_model_name(
|
|
"apac.anthropic.claude-haiku-4-5", preserve_dots=True
|
|
) == "apac.anthropic.claude-haiku-4-5"
|
|
|
|
def test_preserve_false_mangles_as_documented(self):
|
|
"""Canary: with ``preserve_dots=False`` the function still
|
|
produces the broken all-hyphen form — this is the shape that
|
|
Bedrock rejected and that the fix avoids. Keeping this test
|
|
locks in the existing behaviour of ``normalize_model_name`` so a
|
|
future refactor doesn't accidentally decouple the knob from its
|
|
effect."""
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
assert normalize_model_name(
|
|
"global.anthropic.claude-opus-4-7", preserve_dots=False
|
|
) == "global-anthropic-claude-opus-4-7"
|
|
|
|
def test_bare_foundation_model_id_preserved(self):
|
|
"""Non-inference-profile Bedrock IDs
|
|
(e.g. ``anthropic.claude-3-5-sonnet-20241022-v2:0``) use dots as
|
|
vendor separators and must also survive intact under
|
|
``preserve_dots=True``."""
|
|
from agent.anthropic_adapter import normalize_model_name
|
|
assert normalize_model_name(
|
|
"anthropic.claude-3-5-sonnet-20241022-v2:0",
|
|
preserve_dots=True,
|
|
) == "anthropic.claude-3-5-sonnet-20241022-v2:0"
|
|
|
|
|
|
class TestBedrockBuildAnthropicKwargsEndToEnd:
|
|
"""Integration: calling ``build_anthropic_kwargs`` with a Bedrock-
|
|
shaped model ID and ``preserve_dots=True`` produces the unmangled
|
|
model string in the outgoing kwargs — the exact body sent to the
|
|
``bedrock-runtime.`` endpoint. This is the integration-level
|
|
regression for the reporter's HTTP 400."""
|
|
|
|
def test_bedrock_inference_profile_survives_build_kwargs(self):
|
|
from agent.anthropic_adapter import build_anthropic_kwargs
|
|
kwargs = build_anthropic_kwargs(
|
|
model="global.anthropic.claude-opus-4-7",
|
|
messages=[{"role": "user", "content": "hi"}],
|
|
tools=None,
|
|
max_tokens=1024,
|
|
reasoning_config=None,
|
|
preserve_dots=True,
|
|
)
|
|
assert kwargs["model"] == "global.anthropic.claude-opus-4-7", (
|
|
"Bedrock inference-profile ID was mangled in build_anthropic_kwargs: "
|
|
f"{kwargs['model']!r}"
|
|
)
|
|
|
|
def test_bedrock_model_mangled_without_preserve_dots(self):
|
|
"""Inverse canary: without the flag, ``build_anthropic_kwargs``
|
|
still produces the broken form — so the fix in
|
|
``_anthropic_preserve_dots`` is the load-bearing piece that
|
|
wires ``preserve_dots=True`` through to this builder for the
|
|
Bedrock case."""
|
|
from agent.anthropic_adapter import build_anthropic_kwargs
|
|
kwargs = build_anthropic_kwargs(
|
|
model="global.anthropic.claude-opus-4-7",
|
|
messages=[{"role": "user", "content": "hi"}],
|
|
tools=None,
|
|
max_tokens=1024,
|
|
reasoning_config=None,
|
|
preserve_dots=False,
|
|
)
|
|
assert kwargs["model"] == "global-anthropic-claude-opus-4-7"
|