mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-26 01:12:21 +00:00
feat(bedrock_mantle): serve /v1/messages for Claude models on Mantle's native Anthropic Messages API
This commit is contained in:
parent
252a0f1eac
commit
f2b6c0da81
6 changed files with 478 additions and 1 deletions
|
|
@ -501,6 +501,7 @@ def anthropic_messages_handler(
|
|||
api_base=litellm_params.api_base,
|
||||
api_key=litellm_params.api_key,
|
||||
)
|
||||
resolved_api_base: Final = dynamic_api_base if dynamic_api_base is not None else api_base
|
||||
|
||||
# Store agentic loop params in logging object for agentic hooks
|
||||
# This provides original request context needed for follow-up calls
|
||||
|
|
@ -662,7 +663,7 @@ def anthropic_messages_handler(
|
|||
litellm_params=litellm_params,
|
||||
logging_obj=litellm_logging_obj,
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
api_base=resolved_api_base,
|
||||
stream=stream,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
|
|
|||
0
litellm/llms/bedrock_mantle/messages/__init__.py
Normal file
0
litellm/llms/bedrock_mantle/messages/__init__.py
Normal file
101
litellm/llms/bedrock_mantle/messages/transformation.py
Normal file
101
litellm/llms/bedrock_mantle/messages/transformation.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
from collections.abc import Mapping
|
||||
from typing import Final
|
||||
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
|
||||
DEFAULT_ANTHROPIC_API_VERSION,
|
||||
)
|
||||
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
||||
from litellm.llms.bedrock.common_utils import MANTLE_MESSAGES_PATH
|
||||
from litellm.llms.bedrock.messages.mantle_transformation import AmazonMantleMessagesConfig
|
||||
from litellm.llms.bedrock_mantle.common_utils import (
|
||||
MANTLE_HOST_RE,
|
||||
BedrockMantleAuthMixin,
|
||||
resolve_mantle_region,
|
||||
)
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
_BASE_SUFFIXES_TO_STRIP: Final = (
|
||||
MANTLE_MESSAGES_PATH,
|
||||
"/v1/messages",
|
||||
"/messages",
|
||||
"/anthropic/v1",
|
||||
"/openai/v1",
|
||||
"/v1",
|
||||
)
|
||||
|
||||
|
||||
def build_mantle_native_messages_url(api_base: str | None, litellm_params: Mapping[str, object]) -> str:
|
||||
region: Final = resolve_mantle_region({**litellm_params, "api_base": api_base})
|
||||
configured: Final = (
|
||||
api_base or get_secret_str("BEDROCK_MANTLE_API_BASE") or f"https://bedrock-mantle.{region}.api.aws"
|
||||
).rstrip("/")
|
||||
stripped: Final = next(
|
||||
(configured[: -len(suffix)] for suffix in _BASE_SUFFIXES_TO_STRIP if configured.endswith(suffix)),
|
||||
configured,
|
||||
)
|
||||
host: Final = f"https://bedrock-mantle.{region}.api.aws" if MANTLE_HOST_RE.match(stripped) else stripped
|
||||
return f"{host}{MANTLE_MESSAGES_PATH}"
|
||||
|
||||
|
||||
class BedrockMantleAnthropicMessagesConfig(BedrockMantleAuthMixin, AmazonMantleMessagesConfig):
|
||||
def __init__(self, aws_signer: BaseAWSLLM | None = None) -> None:
|
||||
AmazonMantleMessagesConfig.__init__(self)
|
||||
self._aws_signer = aws_signer or self
|
||||
|
||||
@property
|
||||
def custom_llm_provider(self) -> str | None:
|
||||
return "bedrock_mantle"
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: str | None,
|
||||
api_key: str | None,
|
||||
model: str,
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
stream: bool | None = None,
|
||||
) -> str:
|
||||
return build_mantle_native_messages_url(api_base=api_base, litellm_params=litellm_params)
|
||||
|
||||
def validate_anthropic_messages_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
messages: list[dict],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
api_key: str | None = None,
|
||||
api_base: str | None = None,
|
||||
) -> tuple[dict, str | None]:
|
||||
merged_headers, resolved_api_base = super().validate_anthropic_messages_environment(
|
||||
headers=headers,
|
||||
model=model,
|
||||
messages=messages,
|
||||
optional_params=optional_params,
|
||||
litellm_params=litellm_params,
|
||||
api_key=api_key,
|
||||
api_base=api_base,
|
||||
)
|
||||
if any(name.lower() == "anthropic-version" for name in merged_headers):
|
||||
return merged_headers, resolved_api_base
|
||||
return {**merged_headers, "anthropic-version": DEFAULT_ANTHROPIC_API_VERSION}, resolved_api_base
|
||||
|
||||
def transform_anthropic_messages_request(
|
||||
self,
|
||||
model: str,
|
||||
messages: list[dict],
|
||||
anthropic_messages_optional_request_params: dict,
|
||||
litellm_params: GenericLiteLLMParams,
|
||||
headers: dict,
|
||||
) -> dict:
|
||||
request: Final = super().transform_anthropic_messages_request(
|
||||
model=model,
|
||||
messages=messages,
|
||||
anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,
|
||||
litellm_params=litellm_params,
|
||||
headers=headers,
|
||||
)
|
||||
if "anthropic_version" in anthropic_messages_optional_request_params:
|
||||
return request
|
||||
return {key: value for key, value in request.items() if key != "anthropic_version"}
|
||||
|
|
@ -8681,6 +8681,13 @@ class ProviderConfigManager:
|
|||
from litellm.llms.bedrock.common_utils import BedrockModelInfo
|
||||
|
||||
return BedrockModelInfo.get_bedrock_provider_config_for_messages_api(model)
|
||||
elif litellm.LlmProviders.BEDROCK_MANTLE == provider:
|
||||
if "claude" in model_lower:
|
||||
from litellm.llms.bedrock_mantle.messages.transformation import (
|
||||
BedrockMantleAnthropicMessagesConfig,
|
||||
)
|
||||
|
||||
return BedrockMantleAnthropicMessagesConfig()
|
||||
elif litellm.LlmProviders.VERTEX_AI == provider:
|
||||
if "claude" in model_lower:
|
||||
from litellm.llms.vertex_ai.vertex_ai_partner_models.anthropic.experimental_pass_through.transformation import (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,346 @@
|
|||
"""
|
||||
Unit tests for the bedrock_mantle native Anthropic Messages route.
|
||||
|
||||
Mantle serves its Claude models only on `/anthropic/v1/messages` (the OpenAI
|
||||
paths reject them), so `bedrock_mantle/anthropic.claude-*` requests on
|
||||
/v1/messages must hit that endpoint directly instead of the chat-completions
|
||||
bridge. These tests lock the dispatcher gate, the URL derivation from the
|
||||
OpenAI-surface base that get_llm_provider pre-fills, the version header, the
|
||||
Bearer/SigV4 auth chain, and the wire request through the public entrypoint.
|
||||
"""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
import litellm
|
||||
from litellm.caching.llm_caching_handler import LLMClientCache
|
||||
from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM
|
||||
from litellm.llms.bedrock_mantle.messages.transformation import (
|
||||
BedrockMantleAnthropicMessagesConfig,
|
||||
build_mantle_native_messages_url,
|
||||
)
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
MESSAGES_PATH = "/anthropic/v1/messages"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _httpx_transport_with_fresh_clients(monkeypatch):
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
monkeypatch.setattr(litellm, "in_memory_llm_clients_cache", LLMClientCache())
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_ambient_mantle_env(monkeypatch):
|
||||
monkeypatch.delenv("AWS_BEARER_TOKEN_BEDROCK", raising=False)
|
||||
monkeypatch.delenv("BEDROCK_MANTLE_API_KEY", raising=False)
|
||||
monkeypatch.delenv("BEDROCK_MANTLE_API_BASE", raising=False)
|
||||
monkeypatch.delenv("BEDROCK_MANTLE_REGION", raising=False)
|
||||
monkeypatch.delenv("AWS_REGION_NAME", raising=False)
|
||||
monkeypatch.delenv("AWS_REGION", raising=False)
|
||||
|
||||
|
||||
def _anthropic_response() -> httpx.Response:
|
||||
return httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
"id": "msg_test",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "anthropic.claude-sonnet-5",
|
||||
"content": [{"type": "text", "text": "pong"}],
|
||||
"stop_reason": "end_turn",
|
||||
"stop_sequence": None,
|
||||
"usage": {"input_tokens": 3, "output_tokens": 1},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
_SSE_EVENTS = (
|
||||
(
|
||||
"message_start",
|
||||
{
|
||||
"type": "message_start",
|
||||
"message": {
|
||||
"id": "msg_stream",
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"model": "anthropic.claude-sonnet-5",
|
||||
"content": [],
|
||||
"stop_reason": None,
|
||||
"stop_sequence": None,
|
||||
"usage": {"input_tokens": 3, "output_tokens": 1},
|
||||
},
|
||||
},
|
||||
),
|
||||
("content_block_start", {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}),
|
||||
("content_block_delta", {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "pong"}}),
|
||||
("content_block_stop", {"type": "content_block_stop", "index": 0}),
|
||||
("message_delta", {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"output_tokens": 1}}),
|
||||
("message_stop", {"type": "message_stop"}),
|
||||
)
|
||||
|
||||
|
||||
def _sse_response() -> httpx.Response:
|
||||
body = "".join(f"event: {event}\ndata: {json.dumps(payload)}\n\n" for event, payload in _SSE_EVENTS).encode()
|
||||
return httpx.Response(status_code=200, content=body, headers={"content-type": "text/event-stream"})
|
||||
|
||||
|
||||
def _mantle_messages_route(region: str) -> respx.Route:
|
||||
return respx.post(f"https://bedrock-mantle.{region}.api.aws{MESSAGES_PATH}")
|
||||
|
||||
|
||||
def _sent_body(route: respx.Route) -> dict:
|
||||
return json.loads(route.calls.last.request.content)
|
||||
|
||||
|
||||
class TestDispatch:
|
||||
def test_claude_models_get_the_native_messages_config(self):
|
||||
config = ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model="anthropic.claude-sonnet-5", provider=litellm.LlmProviders.BEDROCK_MANTLE
|
||||
)
|
||||
assert isinstance(config, BedrockMantleAnthropicMessagesConfig)
|
||||
assert config.custom_llm_provider == "bedrock_mantle"
|
||||
|
||||
@pytest.mark.parametrize("model", ["openai.gpt-5.6-sol", "openai.gpt-oss-120b-1:0", "google.gemma-4-31b"])
|
||||
def test_non_claude_models_keep_the_bridge(self, model):
|
||||
assert (
|
||||
ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model=model, provider=litellm.LlmProviders.BEDROCK_MANTLE
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
class TestURL:
|
||||
@pytest.mark.parametrize(
|
||||
"api_base",
|
||||
[
|
||||
"https://bedrock-mantle.us-east-1.api.aws/v1",
|
||||
"https://bedrock-mantle.us-east-1.api.aws/openai/v1",
|
||||
"https://bedrock-mantle.us-east-1.api.aws/openai/v1/",
|
||||
"https://bedrock-mantle.us-east-1.api.aws",
|
||||
"https://bedrock-mantle.us-east-1.api.aws/anthropic/v1/messages",
|
||||
],
|
||||
)
|
||||
def test_prefilled_openai_base_becomes_the_messages_endpoint(self, api_base):
|
||||
url = build_mantle_native_messages_url(api_base, {"aws_region_name": "us-east-1"})
|
||||
assert url == f"https://bedrock-mantle.us-east-1.api.aws{MESSAGES_PATH}"
|
||||
|
||||
def test_aws_region_name_wins_over_the_prefilled_host_region(self):
|
||||
url = build_mantle_native_messages_url(
|
||||
"https://bedrock-mantle.us-east-1.api.aws/v1", {"aws_region_name": "us-east-2"}
|
||||
)
|
||||
assert url == f"https://bedrock-mantle.us-east-2.api.aws{MESSAGES_PATH}"
|
||||
|
||||
def test_host_region_is_used_when_no_region_param(self):
|
||||
url = build_mantle_native_messages_url("https://bedrock-mantle.eu-west-1.api.aws/v1", {})
|
||||
assert url == f"https://bedrock-mantle.eu-west-1.api.aws{MESSAGES_PATH}"
|
||||
|
||||
def test_custom_host_is_preserved(self):
|
||||
url = build_mantle_native_messages_url("https://vpce-abc.bedrock-mantle.example.com/v1", {})
|
||||
assert url == f"https://vpce-abc.bedrock-mantle.example.com{MESSAGES_PATH}"
|
||||
|
||||
def test_env_base_is_used_without_api_base(self, monkeypatch):
|
||||
monkeypatch.setenv("BEDROCK_MANTLE_API_BASE", "https://mantle-proxy.internal/openai/v1")
|
||||
assert build_mantle_native_messages_url(None, {}) == f"https://mantle-proxy.internal{MESSAGES_PATH}"
|
||||
|
||||
def test_default_host_comes_from_mantle_region_env(self, monkeypatch):
|
||||
monkeypatch.setenv("BEDROCK_MANTLE_REGION", "ap-northeast-1")
|
||||
assert build_mantle_native_messages_url(None, {}) == f"https://bedrock-mantle.ap-northeast-1.api.aws{MESSAGES_PATH}"
|
||||
|
||||
def test_config_get_complete_url_reads_litellm_params(self):
|
||||
config = BedrockMantleAnthropicMessagesConfig()
|
||||
url = config.get_complete_url(
|
||||
api_base="https://bedrock-mantle.us-east-1.api.aws/v1",
|
||||
api_key=None,
|
||||
model="anthropic.claude-sonnet-5",
|
||||
optional_params={},
|
||||
litellm_params={"aws_region_name": "us-west-2"},
|
||||
)
|
||||
assert url == f"https://bedrock-mantle.us-west-2.api.aws{MESSAGES_PATH}"
|
||||
|
||||
|
||||
class TestEnvironment:
|
||||
def _validate(self, headers: dict, litellm_params: dict) -> dict:
|
||||
config = BedrockMantleAnthropicMessagesConfig()
|
||||
merged, _ = config.validate_anthropic_messages_environment(
|
||||
headers=headers,
|
||||
model="anthropic.claude-sonnet-5",
|
||||
messages=[],
|
||||
optional_params={},
|
||||
litellm_params=litellm_params,
|
||||
)
|
||||
return merged
|
||||
|
||||
def test_adds_the_anthropic_version_header(self):
|
||||
assert self._validate({}, {})["anthropic-version"] == "2023-06-01"
|
||||
|
||||
def test_keeps_a_caller_supplied_version_header(self):
|
||||
merged = self._validate({"Anthropic-Version": "2024-01-01"}, {})
|
||||
assert merged["Anthropic-Version"] == "2024-01-01"
|
||||
assert "anthropic-version" not in merged
|
||||
|
||||
def test_project_id_becomes_the_workspace_header(self):
|
||||
assert self._validate({}, {"aws_bedrock_project_id": "proj_123"})["anthropic-workspace"] == "proj_123"
|
||||
|
||||
|
||||
class TestRequestBody:
|
||||
def test_body_carries_model_and_stream_but_not_the_invoke_version(self):
|
||||
config = BedrockMantleAnthropicMessagesConfig()
|
||||
body = config.transform_anthropic_messages_request(
|
||||
model="anthropic.claude-sonnet-5",
|
||||
messages=[{"role": "user", "content": "ping"}],
|
||||
anthropic_messages_optional_request_params={"max_tokens": 8, "stream": True},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
assert body["model"] == "anthropic.claude-sonnet-5"
|
||||
assert body["stream"] is True
|
||||
assert body["max_tokens"] == 8
|
||||
assert "anthropic_version" not in body
|
||||
|
||||
def test_body_omits_stream_when_not_streaming(self):
|
||||
config = BedrockMantleAnthropicMessagesConfig()
|
||||
body = config.transform_anthropic_messages_request(
|
||||
model="anthropic.claude-sonnet-5",
|
||||
messages=[{"role": "user", "content": "ping"}],
|
||||
anthropic_messages_optional_request_params={"max_tokens": 8},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
assert "stream" not in body
|
||||
|
||||
|
||||
class TestAuth:
|
||||
def test_bearer_from_api_key_skips_aws_credentials(self):
|
||||
signer = BaseAWSLLM()
|
||||
signer.get_credentials = MagicMock(side_effect=AssertionError("must not resolve AWS credentials"))
|
||||
config = BedrockMantleAnthropicMessagesConfig(aws_signer=signer)
|
||||
headers, signed = config.sign_request(
|
||||
headers={"anthropic-version": "2023-06-01"},
|
||||
optional_params={},
|
||||
request_data={"model": "anthropic.claude-sonnet-5"},
|
||||
api_base=f"https://bedrock-mantle.us-east-1.api.aws{MESSAGES_PATH}",
|
||||
api_key="arg-bearer",
|
||||
)
|
||||
assert headers["Authorization"] == "Bearer arg-bearer"
|
||||
assert headers["anthropic-version"] == "2023-06-01"
|
||||
assert signed == b'{"model": "anthropic.claude-sonnet-5"}'
|
||||
|
||||
def test_bearer_from_mantle_env_key(self, monkeypatch):
|
||||
monkeypatch.setenv("BEDROCK_MANTLE_API_KEY", "env-bearer")
|
||||
config = BedrockMantleAnthropicMessagesConfig()
|
||||
headers, _ = config.sign_request(
|
||||
headers={},
|
||||
optional_params={},
|
||||
request_data={},
|
||||
api_base=f"https://bedrock-mantle.us-east-1.api.aws{MESSAGES_PATH}",
|
||||
api_key=None,
|
||||
)
|
||||
assert headers["Authorization"] == "Bearer env-bearer"
|
||||
|
||||
def test_sigv4_scope_is_pinned_to_the_url_host_region(self):
|
||||
config = BedrockMantleAnthropicMessagesConfig()
|
||||
headers, signed = config.sign_request(
|
||||
headers={"anthropic-version": "2023-06-01"},
|
||||
optional_params={
|
||||
"aws_access_key_id": "AKIAEXAMPLE",
|
||||
"aws_secret_access_key": "c2VjcmV0LXRlc3Qtc2VjcmV0LXRlc3Qtc2VjcmV0",
|
||||
"aws_region_name": "us-east-1",
|
||||
},
|
||||
request_data={"model": "anthropic.claude-sonnet-5"},
|
||||
api_base=f"https://bedrock-mantle.us-west-2.api.aws{MESSAGES_PATH}",
|
||||
api_key=None,
|
||||
)
|
||||
assert headers["Authorization"].startswith("AWS4-HMAC-SHA256")
|
||||
assert "/us-west-2/bedrock/aws4_request" in headers["Authorization"]
|
||||
assert signed == b'{"model": "anthropic.claude-sonnet-5"}'
|
||||
|
||||
|
||||
class TestWireRequest:
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_claude_request_hits_the_native_messages_endpoint(self):
|
||||
route = _mantle_messages_route("us-east-1").mock(return_value=_anthropic_response())
|
||||
|
||||
response = await litellm.anthropic_messages(
|
||||
model="bedrock_mantle/anthropic.claude-sonnet-5",
|
||||
messages=[{"role": "user", "content": "ping"}],
|
||||
max_tokens=8,
|
||||
api_key="test-bearer",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
|
||||
assert response["content"][0]["text"] == "pong"
|
||||
assert route.call_count == 1
|
||||
sent = route.calls.last.request
|
||||
assert sent.headers["authorization"] == "Bearer test-bearer"
|
||||
assert sent.headers["anthropic-version"] == "2023-06-01"
|
||||
assert "x-api-key" not in sent.headers
|
||||
body = _sent_body(route)
|
||||
assert body["model"] == "anthropic.claude-sonnet-5"
|
||||
assert body["messages"] == [{"role": "user", "content": "ping"}]
|
||||
assert "anthropic_version" not in body
|
||||
assert "stream" not in body
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_region_prefix_selects_the_host_and_is_not_sent_as_model(self):
|
||||
route = _mantle_messages_route("us-east-2").mock(return_value=_anthropic_response())
|
||||
|
||||
await litellm.anthropic_messages(
|
||||
model="bedrock_mantle/us-east-2/anthropic.claude-haiku-4-5",
|
||||
messages=[{"role": "user", "content": "ping"}],
|
||||
max_tokens=8,
|
||||
api_key="test-bearer",
|
||||
)
|
||||
|
||||
assert route.call_count == 1
|
||||
assert _sent_body(route)["model"] == "anthropic.claude-haiku-4-5"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_streaming_sends_stream_and_passes_the_sse_through(self):
|
||||
route = _mantle_messages_route("us-east-1").mock(return_value=_sse_response())
|
||||
|
||||
response = await litellm.anthropic_messages(
|
||||
model="bedrock_mantle/anthropic.claude-sonnet-5",
|
||||
messages=[{"role": "user", "content": "ping"}],
|
||||
max_tokens=8,
|
||||
stream=True,
|
||||
api_key="test-bearer",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
raw = b"".join([chunk async for chunk in response])
|
||||
|
||||
assert route.call_count == 1
|
||||
assert _sent_body(route)["stream"] is True
|
||||
text = raw.decode()
|
||||
assert "event: message_start" in text
|
||||
assert '"text": "pong"' in text
|
||||
assert "event: message_stop" in text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_sigv4_request_signs_against_the_messages_url(self):
|
||||
route = _mantle_messages_route("us-east-1").mock(return_value=_anthropic_response())
|
||||
|
||||
await litellm.anthropic_messages(
|
||||
model="bedrock_mantle/anthropic.claude-sonnet-5",
|
||||
messages=[{"role": "user", "content": "ping"}],
|
||||
max_tokens=8,
|
||||
aws_access_key_id="AKIAEXAMPLE",
|
||||
aws_secret_access_key="c2VjcmV0LXRlc3Qtc2VjcmV0LXRlc3Qtc2VjcmV0",
|
||||
aws_region_name="us-east-1",
|
||||
)
|
||||
|
||||
assert route.call_count == 1
|
||||
authorization = route.calls.last.request.headers["authorization"]
|
||||
assert authorization.startswith("AWS4-HMAC-SHA256")
|
||||
assert "/us-east-1/bedrock/aws4_request" in authorization
|
||||
|
|
@ -3640,6 +3640,28 @@ class TestGetOptionalParamsTencent:
|
|||
assert isinstance(config, TencentAnthropicMessagesConfig)
|
||||
assert config.custom_llm_provider == "tencent"
|
||||
|
||||
def test_bedrock_mantle_claude_messages_config_routing(self):
|
||||
import litellm
|
||||
from litellm.llms.bedrock_mantle.messages.transformation import (
|
||||
BedrockMantleAnthropicMessagesConfig,
|
||||
)
|
||||
|
||||
config = ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model="anthropic.claude-sonnet-5",
|
||||
provider=litellm.LlmProviders.BEDROCK_MANTLE,
|
||||
)
|
||||
assert isinstance(config, BedrockMantleAnthropicMessagesConfig)
|
||||
assert config.custom_llm_provider == "bedrock_mantle"
|
||||
|
||||
def test_bedrock_mantle_openai_models_keep_the_messages_bridge(self):
|
||||
import litellm
|
||||
|
||||
config = ProviderConfigManager.get_provider_anthropic_messages_config(
|
||||
model="openai.gpt-5.6-sol",
|
||||
provider=litellm.LlmProviders.BEDROCK_MANTLE,
|
||||
)
|
||||
assert config is None
|
||||
|
||||
|
||||
class TestValidateEnvironmentTencent:
|
||||
"""Tests that validate_environment resolves TENCENT_API_KEY for the tencent provider."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue