fix(bedrock): price bedrock/mantle/<model> deployments from the base model row (#42402)

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-09-21 20:59:36 -07:00 • committed by GitHub
parent 571e5797d0
commit 418561991b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 51 additions and 1 deletions

View file

@ -787,7 +787,7 @@ def is_bedrock_application_inference_profile_arn(model: str) -> bool:
def strip_bedrock_routing_prefix(model: str) -> str:
"""Strip LiteLLM routing prefixes from model name."""
for prefix in ["bedrock/", "converse/", "invoke/", "openai/", "nova-2/", "nova/"]:
for prefix in ["bedrock/", "converse/", "invoke/", "openai/", "mantle/", "nova-2/", "nova/"]:
if model.startswith(prefix):
model = model.split("/", 1)[1]
return model
@ -850,6 +850,7 @@ def get_bedrock_base_model(model: str) -> str:
Handle model names like:
- "us.meta.llama3-2-11b-instruct-v1:0" -> "meta.llama3-2-11b-instruct-v1"
- "bedrock/converse/model" -> "model"
- "bedrock/mantle/anthropic.claude-sonnet-5" -> "anthropic.claude-sonnet-5"
- "anthropic.claude-3-5-sonnet-20241022-v2:0:51k" -> "anthropic.claude-3-5-sonnet-20241022-v2:0"
- "bedrock/nova-2/arn:aws:..." -> "amazon.nova-2-custom"
- "bedrock/nova/arn:aws:..." -> "amazon.nova-custom"

View file

@ -279,6 +279,16 @@ def test_context_window_suffix_stripped_for_cost_lookup():
)
def test_legacy_mantle_route_prefix_stripped_for_cost_lookup():
"""The mantle/ route token is a routing prefix like openai/, so a bedrock/mantle/<model>
deployment must resolve the bare Bedrock model for cost lookup while still routing to Mantle."""
from litellm.llms.bedrock.common_utils import get_bedrock_base_model, strip_bedrock_routing_prefix
assert strip_bedrock_routing_prefix("mantle/anthropic.claude-sonnet-5") == "anthropic.claude-sonnet-5"
assert get_bedrock_base_model("bedrock/mantle/anthropic.claude-sonnet-5") == "anthropic.claude-sonnet-5"
assert BedrockModelInfo.get_bedrock_route("bedrock/mantle/anthropic.claude-sonnet-5") == "mantle"
def test_output_config_effort_normalization_uses_model_info_ceiling(monkeypatch):
import litellm.llms.bedrock.common_utils as mod

View file

@ -3574,6 +3574,45 @@ def test_completion_cost_mantle_native_messages_prices_haiku_from_the_mantle_row
) == pytest.approx(expected), model
def test_completion_cost_legacy_mantle_route_prices_after_router_registration(local_model_cost_map):
"""The proxy registers every deployment under its provider-prefixed key at boot. A
bedrock/mantle/<model> deployment must resolve to the bare Bedrock row there, otherwise the boot
entry is a cost-less capability rule that shadows the priced row and every call on the deployment,
/v1/chat/completions and /v1/messages alike, bills $0."""
from litellm import Router
Router(
model_list=[
{
"model_name": "claude-sonnet-5",
"litellm_params": {
"model": "bedrock/mantle/anthropic.claude-sonnet-5",
"aws_region_name": "us-east-1",
},
}
]
)
assert "bedrock/mantle/anthropic.claude-sonnet-5" not in litellm.model_cost
response = litellm.ModelResponse(
id="msg_x",
choices=[{"index": 0, "message": {"role": "assistant", "content": "hi"}, "finish_reason": "stop"}],
model="claude-sonnet-5",
usage={"prompt_tokens": 16, "completion_tokens": 4, "total_tokens": 20},
)
row = litellm.model_cost["anthropic.claude-sonnet-5"]
expected = 16 * row["input_cost_per_token"] + 4 * row["output_cost_per_token"]
assert expected > 0
for call_type in ("completion", "anthropic_messages"):
assert litellm.completion_cost(
completion_response=response,
model="mantle/anthropic.claude-sonnet-5",
custom_llm_provider="bedrock",
call_type=call_type,
) == pytest.approx(expected), call_type
def test_select_model_name_keeps_base_model_free_of_region(_local_model_cost_map):
"""An explicit base_model keeps pricing on that model's own key even when the request carries a
region with different regional rates, so the private provider model never widens region pricing."""