diff --git a/litellm/__init__.py b/litellm/__init__.py index 56d516536e8..83746b184f5 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1680,6 +1680,9 @@ if TYPE_CHECKING: from .llms.bedrock_mantle.chat.transformation import ( BedrockMantleChatConfig as BedrockMantleChatConfig, ) + from .llms.bedrock_mantle.responses.transformation import ( + BedrockMantleResponsesAPIConfig as BedrockMantleResponsesAPIConfig, + ) from .llms.a2a.chat.transformation import A2AConfig as A2AConfig from .llms.voyage.embedding.transformation import ( VoyageEmbeddingConfig as VoyageEmbeddingConfig, diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index 17eb6609292..6d8040b5d0d 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -219,6 +219,7 @@ LLM_CONFIG_NAMES = ( "OpenAITextCompletionConfig", "GroqChatConfig", "BedrockMantleChatConfig", + "BedrockMantleResponsesAPIConfig", "A2AConfig", "GenAIHubOrchestrationConfig", "VoyageEmbeddingConfig", @@ -886,6 +887,10 @@ _LLM_CONFIGS_IMPORT_MAP = { ".llms.bedrock_mantle.chat.transformation", "BedrockMantleChatConfig", ), + "BedrockMantleResponsesAPIConfig": ( + ".llms.bedrock_mantle.responses.transformation", + "BedrockMantleResponsesAPIConfig", + ), "A2AConfig": (".llms.a2a.chat.transformation", "A2AConfig"), "GenAIHubOrchestrationConfig": ( ".llms.sap.chat.transformation", diff --git a/litellm/llms/bedrock_mantle/chat/transformation.py b/litellm/llms/bedrock_mantle/chat/transformation.py index 81a56030a5c..16ac22f0eba 100644 --- a/litellm/llms/bedrock_mantle/chat/transformation.py +++ b/litellm/llms/bedrock_mantle/chat/transformation.py @@ -3,7 +3,7 @@ Amazon Bedrock Mantle - OpenAI-compatible inference engine in Amazon Bedrock. API docs: https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-mantle.html -Base URL: https://bedrock-mantle.{region}.api.aws/v1 +Base URL: https://bedrock-mantle.{region}.api.aws/openai/v1 Auth: AWS Bedrock API key as Bearer token (set via BEDROCK_MANTLE_API_KEY env var) or region-aware key via BEDROCK_MANTLE_{REGION}_API_KEY. """ @@ -43,7 +43,7 @@ class BedrockMantleChatConfig(OpenAILikeChatConfig): api_base = ( api_base or get_secret_str("BEDROCK_MANTLE_API_BASE") - or f"https://bedrock-mantle.{region}.api.aws/v1" + or f"https://bedrock-mantle.{region}.api.aws/openai/v1" ) dynamic_api_key = api_key or get_secret_str("BEDROCK_MANTLE_API_KEY") return api_base, dynamic_api_key diff --git a/litellm/llms/bedrock_mantle/responses/transformation.py b/litellm/llms/bedrock_mantle/responses/transformation.py new file mode 100644 index 00000000000..326233fd24b --- /dev/null +++ b/litellm/llms/bedrock_mantle/responses/transformation.py @@ -0,0 +1,61 @@ +""" +Amazon Bedrock Mantle - OpenAI-compatible Responses API. + +Routes /v1/responses to the provider's native Responses endpoint instead of +the chat-completions translation fallback. +""" + +from typing import Optional + +from litellm.llms.bedrock_mantle.chat.transformation import BedrockMantleChatConfig +from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig +from litellm.types.router import GenericLiteLLMParams +from litellm.types.utils import LlmProviders + + +class BedrockMantleResponsesAPIConfig(OpenAIResponsesAPIConfig): + @property + def custom_llm_provider(self) -> LlmProviders: + return LlmProviders.BEDROCK_MANTLE + + def validate_environment( + self, + headers: dict, + model: str, + litellm_params: Optional[GenericLiteLLMParams], + ) -> dict: + litellm_params = litellm_params or GenericLiteLLMParams() + _, api_key = BedrockMantleChatConfig()._get_openai_compatible_provider_info( + api_base=litellm_params.api_base, + api_key=litellm_params.api_key, + ) + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + return headers + + def get_complete_url( + self, + api_base: Optional[str], + litellm_params: dict, + ) -> str: + ( + resolved_api_base, + _, + ) = BedrockMantleChatConfig()._get_openai_compatible_provider_info( + api_base=api_base or litellm_params.get("api_base"), + api_key=litellm_params.get("api_key"), + ) + if not resolved_api_base: + raise ValueError( + "api_base is required for bedrock_mantle Responses API. " + "Set BEDROCK_MANTLE_API_BASE or BEDROCK_MANTLE_REGION." + ) + api_base = resolved_api_base.rstrip("/") + if api_base.endswith("/responses"): + return api_base + if api_base.endswith("/v1"): + return f"{api_base}/responses" + return f"{api_base}/v1/responses" + + def supports_native_websocket(self) -> bool: + return False diff --git a/litellm/utils.py b/litellm/utils.py index a3a26c338b5..a53a7b9fecd 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -8865,6 +8865,8 @@ class ProviderConfigManager: return litellm.OpenRouterResponsesAPIConfig() elif litellm.LlmProviders.HOSTED_VLLM == provider: return litellm.HostedVLLMResponsesAPIConfig() + elif litellm.LlmProviders.BEDROCK_MANTLE == provider: + return litellm.BedrockMantleResponsesAPIConfig() return None @staticmethod diff --git a/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py b/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py index 1725aa85d10..729dd2d6514 100644 --- a/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py +++ b/tests/test_litellm/llms/bedrock_mantle/test_bedrock_mantle_transformation.py @@ -14,7 +14,11 @@ import pytest import litellm from litellm.llms.bedrock_mantle.chat.transformation import BedrockMantleChatConfig +from litellm.llms.bedrock_mantle.responses.transformation import ( + BedrockMantleResponsesAPIConfig, +) from litellm.types.utils import LlmProviders +from litellm.utils import ProviderConfigManager class TestBedrockMantleProviderRegistration: @@ -50,7 +54,7 @@ class TestBedrockMantleConfig: monkeypatch.delenv("BEDROCK_MANTLE_API_BASE", raising=False) cfg = BedrockMantleChatConfig() api_base, _ = cfg._get_openai_compatible_provider_info(None, None) - assert api_base == "https://bedrock-mantle.eu-west-1.api.aws/v1" + assert api_base == "https://bedrock-mantle.eu-west-1.api.aws/openai/v1" def test_default_api_base_uses_aws_region(self, monkeypatch): monkeypatch.delenv("BEDROCK_MANTLE_REGION", raising=False) @@ -58,7 +62,7 @@ class TestBedrockMantleConfig: monkeypatch.setenv("AWS_REGION", "ap-northeast-1") cfg = BedrockMantleChatConfig() api_base, _ = cfg._get_openai_compatible_provider_info(None, None) - assert api_base == "https://bedrock-mantle.ap-northeast-1.api.aws/v1" + assert api_base == "https://bedrock-mantle.ap-northeast-1.api.aws/openai/v1" def test_default_api_base_fallback_to_us_east_1(self, monkeypatch): monkeypatch.delenv("BEDROCK_MANTLE_REGION", raising=False) @@ -66,10 +70,10 @@ class TestBedrockMantleConfig: monkeypatch.delenv("AWS_REGION", raising=False) cfg = BedrockMantleChatConfig() api_base, _ = cfg._get_openai_compatible_provider_info(None, None) - assert api_base == "https://bedrock-mantle.us-east-1.api.aws/v1" + assert api_base == "https://bedrock-mantle.us-east-1.api.aws/openai/v1" def test_custom_api_base_overrides_default(self, monkeypatch): - custom_base = "https://bedrock-mantle.us-west-2.api.aws/v1" + custom_base = "https://bedrock-mantle.us-west-2.api.aws/openai/v1" cfg = BedrockMantleChatConfig() api_base, _ = cfg._get_openai_compatible_provider_info(custom_base, None) assert api_base == custom_base @@ -96,6 +100,21 @@ class TestBedrockMantleConfig: assert "max_tokens" in params +class TestBedrockMantleResponsesConfig: + def test_responses_complete_url(self, monkeypatch): + monkeypatch.setenv("BEDROCK_MANTLE_REGION", "us-east-1") + cfg = BedrockMantleResponsesAPIConfig() + url = cfg.get_complete_url(api_base=None, litellm_params={}) + assert url == "https://bedrock-mantle.us-east-1.api.aws/openai/v1/responses" + + def test_provider_config_manager_registers_responses(self): + cfg = ProviderConfigManager.get_provider_responses_api_config( + provider="bedrock_mantle", + model="gpt-5.5", + ) + assert isinstance(cfg, BedrockMantleResponsesAPIConfig) + + class TestBedrockMantleProviderResolution: def test_get_llm_provider_resolves_correctly(self): model, provider, _, _ = litellm.get_llm_provider(