Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion agent/agent_runtime_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,7 @@ def switch_model(agent, new_model, new_provider, api_key='', base_url='', api_mo
"_anthropic_base_url",
"_is_anthropic_oauth",
"_config_context_length",
"_bedrock_region",
)
}
# _client_kwargs is a dict — snapshot a shallow copy so mutating the
Expand Down Expand Up @@ -1443,7 +1444,28 @@ def switch_model(agent, new_model, new_provider, api_key='', base_url='', api_mo
agent.api_key = api_key

# ── Build new client ──
if api_mode == "anthropic_messages":
if new_provider == "bedrock" and api_mode in {"anthropic_messages", "bedrock_converse"}:
_br_match = re.search(
r"bedrock-runtime\.([a-z0-9-]+)\.",
(base_url or agent.base_url or ""),
)
_br_region = _br_match.group(1) if _br_match else "us-east-1"
agent._bedrock_region = _br_region
agent.api_key = "aws-sdk"
agent.client = None
agent._client_kwargs = {}
if api_mode == "anthropic_messages":
from agent.anthropic_adapter import build_anthropic_bedrock_client
agent._anthropic_client = build_anthropic_bedrock_client(_br_region)
agent._anthropic_api_key = "aws-sdk"
agent._anthropic_base_url = base_url or agent.base_url
agent._is_anthropic_oauth = False
else:
agent._anthropic_client = None
agent._anthropic_api_key = None
agent._anthropic_base_url = None
agent._is_anthropic_oauth = False
elif api_mode == "anthropic_messages":
from agent.anthropic_adapter import (
build_anthropic_client,
resolve_anthropic_token,
Expand Down
4 changes: 3 additions & 1 deletion hermes_cli/runtime_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,9 @@ def resolve_runtime_provider(
# Dual-path routing: Claude models use AnthropicBedrock SDK for full
# feature parity (prompt caching, thinking budgets, adaptive thinking).
# Non-Claude models use the Converse API for multi-model support.
_current_model = str(model_cfg.get("default") or "").strip()
# Prefer target_model for in-session /model switches; using the stale
# config default misroutes Claude-on-Bedrock switches to Converse.
_current_model = str(target_model or model_cfg.get("default") or "").strip()
if is_anthropic_bedrock_model(_current_model):
# Claude on Bedrock → AnthropicBedrock SDK → anthropic_messages path
runtime = {
Expand Down
58 changes: 58 additions & 0 deletions tests/hermes_cli/test_runtime_provider_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -2705,3 +2705,61 @@ def test_host_derived_key_helper_basic_cases():
for k in ("DEEPSEEK_API_KEY", "GROQ_API_KEY", "MISTRAL_API_KEY",
"OPENAI_API_KEY", "OPENROUTER_API_KEY"):
_os.environ.pop(k, None)


def test_bedrock_runtime_provider_uses_target_model_for_switch(monkeypatch):
"""Bedrock /model switches should route from the target model, not stale config."""
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "bedrock")
monkeypatch.setattr(
rp,
"_get_model_config",
lambda: {"provider": "openrouter", "default": "openai/gpt-5.5"},
)
monkeypatch.setattr(
rp,
"load_config",
lambda: {"bedrock": {"region": "ap-northeast-1"}},
)
monkeypatch.setattr(
"agent.bedrock_adapter.resolve_aws_auth_env_var",
lambda: "aws-sdk-default-chain",
)

resolved = rp.resolve_runtime_provider(
requested="bedrock",
target_model="jp.anthropic.claude-opus-4-8",
)

assert resolved["provider"] == "bedrock"
assert resolved["api_mode"] == "anthropic_messages"
assert resolved["bedrock_anthropic"] is True
assert resolved["region"] == "ap-northeast-1"
assert resolved["base_url"] == "https://bedrock-runtime.ap-northeast-1.amazonaws.com"


def test_bedrock_runtime_provider_keeps_converse_for_non_anthropic_target(monkeypatch):
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "bedrock")
monkeypatch.setattr(
rp,
"_get_model_config",
lambda: {"provider": "anthropic", "default": "anthropic/claude-sonnet-4"},
)
monkeypatch.setattr(
rp,
"load_config",
lambda: {"bedrock": {"region": "eu-west-1"}},
)
monkeypatch.setattr(
"agent.bedrock_adapter.resolve_aws_auth_env_var",
lambda: "aws-sdk-default-chain",
)

resolved = rp.resolve_runtime_provider(
requested="bedrock",
target_model="amazon.nova-pro-v1:0",
)

assert resolved["provider"] == "bedrock"
assert resolved["api_mode"] == "bedrock_converse"
assert "bedrock_anthropic" not in resolved
assert resolved["region"] == "eu-west-1"
47 changes: 47 additions & 0 deletions tests/run_agent/test_switch_model_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,50 @@ def test_switch_model_without_config_context_length():
mock_ctx_len.assert_called_once()
call_kwargs = mock_ctx_len.call_args.kwargs
assert call_kwargs.get("config_context_length") is None


def test_switch_model_builds_bedrock_anthropic_client_with_resolved_region():
agent = _make_agent_with_compressor(config_context_length=None)
built = object()

with patch("agent.anthropic_adapter.build_anthropic_bedrock_client", return_value=built) as mock_build:
with patch("agent.model_metadata.get_model_context_length", return_value=200_000):
agent.switch_model(
"jp.anthropic.claude-opus-4-8",
"bedrock",
api_key="aws-sdk",
base_url="https://bedrock-runtime.ap-northeast-1.amazonaws.com",
api_mode="anthropic_messages",
)

mock_build.assert_called_once_with("ap-northeast-1")
assert agent.provider == "bedrock"
assert agent.api_mode == "anthropic_messages"
assert agent.api_key == "aws-sdk"
assert agent._bedrock_region == "ap-northeast-1"
assert agent._anthropic_client is built
assert agent._anthropic_api_key == "aws-sdk"
assert agent.client is None
assert agent._client_kwargs == {}


def test_switch_model_builds_bedrock_converse_without_openai_client():
agent = _make_agent_with_compressor(config_context_length=None)
agent._create_openai_client = MagicMock(side_effect=AssertionError("bedrock should not build OpenAI client"))

with patch("agent.model_metadata.get_model_context_length", return_value=128_000):
agent.switch_model(
"amazon.nova-pro-v1:0",
"bedrock",
api_key="aws-sdk",
base_url="https://bedrock-runtime.eu-west-1.amazonaws.com",
api_mode="bedrock_converse",
)

assert agent.provider == "bedrock"
assert agent.api_mode == "bedrock_converse"
assert agent.api_key == "aws-sdk"
assert agent._bedrock_region == "eu-west-1"
assert agent.client is None
assert agent._anthropic_client is None
assert agent._client_kwargs == {}
Loading