diff --git a/letta/llm_api/anthropic_client.py b/letta/llm_api/anthropic_client.py index 8acd65dd..7f133afb 100644 --- a/letta/llm_api/anthropic_client.py +++ b/letta/llm_api/anthropic_client.py @@ -356,9 +356,17 @@ class AnthropicClient(LLMClientBase): ) -> Union[anthropic.AsyncAnthropic, anthropic.Anthropic]: api_key, _, _ = self.get_byok_overrides(llm_config) + # For MiniMax provider, use minimax_api_key from settings + if not api_key and llm_config.provider_name == "minimax": + api_key = model_settings.minimax_api_key + # For claude-pro-max provider, use OAuth Bearer token instead of api_key is_oauth_provider = llm_config.provider_name == "claude-pro-max" + # Only use custom base_url for MiniMax (Anthropic-compatible API) + # The Anthropic SDK adds /v1/messages internally, so we only override for non-Anthropic providers + base_url = llm_config.model_endpoint if llm_config.provider_name == "minimax" else None + if async_client: if api_key: if is_oauth_provider: @@ -370,8 +378,8 @@ class AnthropicClient(LLMClientBase): "anthropic-beta": "oauth-2025-04-20", }, ) - return anthropic.AsyncAnthropic(api_key=api_key, max_retries=model_settings.anthropic_max_retries) - return anthropic.AsyncAnthropic(max_retries=model_settings.anthropic_max_retries) + return anthropic.AsyncAnthropic(api_key=api_key, base_url=base_url, max_retries=model_settings.anthropic_max_retries) + return anthropic.AsyncAnthropic(base_url=base_url, max_retries=model_settings.anthropic_max_retries) if api_key: if is_oauth_provider: @@ -383,8 +391,8 @@ class AnthropicClient(LLMClientBase): "anthropic-beta": "oauth-2025-04-20", }, ) - return anthropic.Anthropic(api_key=api_key, max_retries=model_settings.anthropic_max_retries) - return anthropic.Anthropic(max_retries=model_settings.anthropic_max_retries) + return anthropic.Anthropic(api_key=api_key, base_url=base_url, max_retries=model_settings.anthropic_max_retries) + return anthropic.Anthropic(base_url=base_url, max_retries=model_settings.anthropic_max_retries) @trace_method async def _get_anthropic_client_async( @@ -392,9 +400,17 @@ class AnthropicClient(LLMClientBase): ) -> Union[anthropic.AsyncAnthropic, anthropic.Anthropic]: api_key, _, _ = await self.get_byok_overrides_async(llm_config) + # For MiniMax provider, use minimax_api_key from settings + if not api_key and llm_config.provider_name == "minimax": + api_key = model_settings.minimax_api_key + # For claude-pro-max provider, use OAuth Bearer token instead of api_key is_oauth_provider = llm_config.provider_name == "claude-pro-max" + # Only use custom base_url for MiniMax (Anthropic-compatible API) + # The Anthropic SDK adds /v1/messages internally, so we only override for non-Anthropic providers + base_url = llm_config.model_endpoint if llm_config.provider_name == "minimax" else None + if async_client: if api_key: if is_oauth_provider: @@ -406,8 +422,8 @@ class AnthropicClient(LLMClientBase): "anthropic-beta": "oauth-2025-04-20", }, ) - return anthropic.AsyncAnthropic(api_key=api_key, max_retries=model_settings.anthropic_max_retries) - return anthropic.AsyncAnthropic(max_retries=model_settings.anthropic_max_retries) + return anthropic.AsyncAnthropic(api_key=api_key, base_url=base_url, max_retries=model_settings.anthropic_max_retries) + return anthropic.AsyncAnthropic(base_url=base_url, max_retries=model_settings.anthropic_max_retries) if api_key: if is_oauth_provider: @@ -419,8 +435,8 @@ class AnthropicClient(LLMClientBase): "anthropic-beta": "oauth-2025-04-20", }, ) - return anthropic.Anthropic(api_key=api_key, max_retries=model_settings.anthropic_max_retries) - return anthropic.Anthropic(max_retries=model_settings.anthropic_max_retries) + return anthropic.Anthropic(api_key=api_key, base_url=base_url, max_retries=model_settings.anthropic_max_retries) + return anthropic.Anthropic(base_url=base_url, max_retries=model_settings.anthropic_max_retries) @trace_method def build_request_data( diff --git a/letta/schemas/providers/base.py b/letta/schemas/providers/base.py index 6b5b722d..f24794d6 100644 --- a/letta/schemas/providers/base.py +++ b/letta/schemas/providers/base.py @@ -192,6 +192,7 @@ class Provider(ProviderBase): GroqProvider, LettaProvider, LMStudioOpenAIProvider, + MiniMaxProvider, MistralProvider, OllamaProvider, OpenAIProvider, @@ -244,6 +245,8 @@ class Provider(ProviderBase): return LMStudioOpenAIProvider(**self.model_dump(exclude_none=True)) case ProviderType.bedrock: return BedrockProvider(**self.model_dump(exclude_none=True)) + case ProviderType.minimax: + return MiniMaxProvider(**self.model_dump(exclude_none=True)) case _: raise ValueError(f"Unknown provider type: {self.provider_type}") diff --git a/letta/schemas/providers/minimax.py b/letta/schemas/providers/minimax.py index 616cf11d..70777113 100644 --- a/letta/schemas/providers/minimax.py +++ b/letta/schemas/providers/minimax.py @@ -73,7 +73,7 @@ class MiniMaxProvider(Provider): configs.append( LLMConfig( model=model["name"], - model_endpoint_type="minimax", + model_endpoint_type="anthropic", model_endpoint=self.base_url, context_window=model["context_window"], handle=self.get_handle(model["name"]), diff --git a/letta/server/server.py b/letta/server/server.py index bfdb21a8..7cb815a8 100644 --- a/letta/server/server.py +++ b/letta/server/server.py @@ -67,6 +67,7 @@ from letta.schemas.providers import ( GroqProvider, LettaProvider, LMStudioOpenAIProvider, + MiniMaxProvider, OllamaProvider, OpenAIProvider, OpenRouterProvider, @@ -342,6 +343,13 @@ class SyncServer(object): api_key_enc=Secret.from_plaintext(model_settings.xai_api_key), ) ) + if model_settings.minimax_api_key: + self._enabled_providers.append( + MiniMaxProvider( + name="minimax", + api_key_enc=Secret.from_plaintext(model_settings.minimax_api_key), + ) + ) if model_settings.zai_api_key: self._enabled_providers.append( ZAIProvider( diff --git a/tests/test_providers.py b/tests/test_providers.py index 21600682..3b013a35 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -154,8 +154,8 @@ async def test_minimax(): assert model.context_window == 200000 # All MiniMax models have 128K max output assert model.max_tokens == 128000 - # All use minimax endpoint type - assert model.model_endpoint_type == "minimax" + # MiniMax uses Anthropic-compatible API endpoint + assert model.model_endpoint_type == "anthropic" @pytest.mark.skipif(model_settings.azure_api_key is None, reason="Only run if AZURE_API_KEY is set.")