From e7575d7b9683efb5b4d995e255c8fa85e7d0b387 Mon Sep 17 00:00:00 2001 From: PR Bot Date: Thu, 26 Mar 2026 18:21:45 +0800 Subject: [PATCH] Add MiniMax as first-class LLM provider Add MiniMax AI model support via OpenAI-compatible API integration: - MiniMaxModels.kt: Model constants for MiniMax-M2.7 and MiniMax-M2.7-highspeed - embabel-agent-minimax-autoconfigure: Spring Boot auto-configuration module with MiniMaxModelsConfig extending OpenAiCompatibleModelFactory, MiniMaxOptionsConverter with temperature clamping to (0.0, 1.0], per-token pricing, and retry configuration - embabel-agent-starter-minimax: Spring Boot starter for easy dependency inclusion - MiniMaxOptionsConverterTest: Unit tests for options conversion and temperature clamping behavior - BOM and parent POM registration for the new modules - README: Document MINIMAX_API_KEY environment variable Signed-off-by: octo-patch --- README.md | 1 + .../embabel/agent/api/models/MiniMaxModels.kt | 34 ++++ .../pom.xml | 47 +++++ .../AgentMiniMaxAutoConfiguration.java | 46 +++++ .../models/minimax/MiniMaxModelsConfig.kt | 179 ++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 16 ++ .../minimax/MiniMaxOptionsConverterTest.kt | 75 ++++++++ embabel-agent-autoconfigure/pom.xml | 1 + embabel-agent-dependencies/pom.xml | 12 ++ .../embabel-agent-starter-minimax/pom.xml | 34 ++++ embabel-agent-starters/pom.xml | 1 + 11 files changed, 446 insertions(+) create mode 100644 embabel-agent-api/src/main/kotlin/com/embabel/agent/api/models/MiniMaxModels.kt create mode 100644 embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/pom.xml create mode 100644 embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/java/com/embabel/agent/autoconfigure/models/minimax/AgentMiniMaxAutoConfiguration.java create mode 100644 embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/minimax/MiniMaxModelsConfig.kt create mode 100644 embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/test/kotlin/com/embabel/agent/config/models/minimax/MiniMaxOptionsConverterTest.kt create mode 100644 embabel-agent-starters/embabel-agent-starter-minimax/pom.xml diff --git a/README.md b/README.md index f6b960f86..62a06d188 100644 --- a/README.md +++ b/README.md @@ -610,6 +610,7 @@ Required: Optional: - `ANTHROPIC_API_KEY`: For the Anthropic API. Necessary for the coding agent. +- `MINIMAX_API_KEY`: For the [MiniMax](https://www.minimax.io) API. Supports MiniMax-M2.7 and MiniMax-M2.7-highspeed models. > We strongly recommend providing both an OpenAI and Anthropic key, as some examples require both. And it's important to > try to find the best LLM for a given task, rather than automatically choose a familiar provider. diff --git a/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/models/MiniMaxModels.kt b/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/models/MiniMaxModels.kt new file mode 100644 index 000000000..ce4306f7b --- /dev/null +++ b/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/models/MiniMaxModels.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024-2026 Embabel Pty Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.embabel.agent.api.models + +/** + * Provides constants for MiniMax AI model identifiers. + * MiniMax offers large language models with up to 1M token context windows + * via an OpenAI-compatible API. + * + * @see MiniMax AI + */ +class MiniMaxModels { + + companion object { + + const val MINIMAX_M2_7 = "MiniMax-M2.7" + const val MINIMAX_M2_7_HIGHSPEED = "MiniMax-M2.7-highspeed" + + const val PROVIDER = "MiniMax" + } +} diff --git a/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/pom.xml b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/pom.xml new file mode 100644 index 000000000..00562a252 --- /dev/null +++ b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + com.embabel.agent + embabel-agent-autoconfigure + 0.3.5-SNAPSHOT + ../../pom.xml + + + embabel-agent-minimax-autoconfigure + jar + Embabel Agent Autoconfiguration Models MiniMax + MiniMax Models Auto-Configuration for Embabel Agent API + https://github.com/embabel/embabel-agent + + + https://github.com/embabel/embabel-agent + scm:git:https://github.com/embabel/embabel-agent.git + scm:git:https://github.com/embabel/embabel-agent.git + HEAD + + + + + com.embabel.agent + embabel-agent-api + + + + com.embabel.agent + embabel-agent-openai + + + + org.springframework.ai + spring-ai-openai + + + + com.embabel.agent + embabel-agent-test-internal + test + + + + diff --git a/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/java/com/embabel/agent/autoconfigure/models/minimax/AgentMiniMaxAutoConfiguration.java b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/java/com/embabel/agent/autoconfigure/models/minimax/AgentMiniMaxAutoConfiguration.java new file mode 100644 index 000000000..7e9944ce4 --- /dev/null +++ b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/java/com/embabel/agent/autoconfigure/models/minimax/AgentMiniMaxAutoConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024-2026 Embabel Pty Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.embabel.agent.autoconfigure.models.minimax; + +import com.embabel.agent.config.models.minimax.MiniMaxModelsConfig; +import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.context.annotation.Import; + +/** + * Autoconfiguration for MiniMax AI models in the Embabel Agent system. + *

+ * This class serves as a Spring Boot autoconfiguration entry point that: + * - Imports the {@link MiniMaxModelsConfig} configuration to register MiniMax model beans + *

+ * The configuration is automatically activated when the MiniMax + * dependencies are present on the classpath and the MINIMAX_API_KEY + * environment variable is set. + */ +@AutoConfiguration +@AutoConfigureBefore(name = {"com.embabel.agent.autoconfigure.platform.AgentPlatformAutoConfiguration"}) +@Import(MiniMaxModelsConfig.class) +public class AgentMiniMaxAutoConfiguration { + private static final Logger logger = LoggerFactory.getLogger(AgentMiniMaxAutoConfiguration.class); + + @PostConstruct + public void logEvent() { + logger.info("AgentMiniMaxAutoConfiguration about to proceed..."); + } +} diff --git a/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/minimax/MiniMaxModelsConfig.kt b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/minimax/MiniMaxModelsConfig.kt new file mode 100644 index 000000000..3c1f9499a --- /dev/null +++ b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/kotlin/com/embabel/agent/config/models/minimax/MiniMaxModelsConfig.kt @@ -0,0 +1,179 @@ +/* + * Copyright 2024-2026 Embabel Pty Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.embabel.agent.config.models.minimax + +import com.embabel.agent.api.models.MiniMaxModels +import com.embabel.agent.openai.OpenAiCompatibleModelFactory +import com.embabel.agent.spi.LlmService +import com.embabel.agent.spi.common.RetryProperties +import com.embabel.agent.spi.support.springai.SpringAiLlmService +import com.embabel.common.ai.model.LlmOptions +import com.embabel.common.ai.model.OptionsConverter +import com.embabel.common.ai.model.PerTokenPricingModel +import com.embabel.common.util.ExcludeFromJacocoGeneratedReport +import com.embabel.common.util.loggerFor +import io.micrometer.observation.ObservationRegistry +import org.springframework.ai.openai.OpenAiChatOptions +import org.springframework.beans.factory.ObjectProvider +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.client.ClientHttpRequestFactory +import java.time.LocalDate + +/** + * Configuration properties for MiniMax models. + * These properties are bound from the Spring configuration with the prefix + * "embabel.agent.platform.models.minimax" and control retry behavior + * when calling MiniMax APIs. + */ +@ConfigurationProperties(prefix = "embabel.agent.platform.models.minimax") +class MiniMaxProperties : RetryProperties { + /** + * Base URL for MiniMax API requests. + */ + var baseUrl: String = "https://api.minimax.io/v1" + + /** + * API key for authenticating with MiniMax services. + */ + var apiKey: String? = null + + /** + * Maximum number of attempts. + */ + override var maxAttempts: Int = 4 + + /** + * Initial backoff interval (in milliseconds). + */ + override var backoffMillis: Long = 1500L + + /** + * Backoff interval multiplier. + */ + override var backoffMultiplier: Double = 2.0 + + /** + * Maximum backoff interval (in milliseconds). + */ + override var backoffMaxInterval: Long = 60000L +} + +/** + * Configuration class for MiniMax models. + * This class provides beans for MiniMax models (M2.7, M2.7-highspeed) + * via the OpenAI-compatible API provided by MiniMax. + * + * MiniMax models require temperature values in the range (0.0, 1.0]. + * The [MiniMaxOptionsConverter] handles clamping temperature to this range. + * + * To use, set the following environment variables: + * ``` + * MINIMAX_API_KEY=your-api-key + * ``` + * + * @see MiniMax AI + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(MiniMaxProperties::class) +@ExcludeFromJacocoGeneratedReport(reason = "MiniMax configuration can't be unit tested") +class MiniMaxModelsConfig( + @param:Value("\${MINIMAX_BASE_URL:#{null}}") + private val envBaseUrl: String?, + @param:Value("\${MINIMAX_API_KEY:#{null}}") + private val envApiKey: String?, + observationRegistry: ObjectProvider, + private val properties: MiniMaxProperties, + requestFactory: ObjectProvider, +) : OpenAiCompatibleModelFactory( + baseUrl = envBaseUrl ?: properties.baseUrl, + apiKey = envApiKey?.trim()?.takeIf { it.isNotEmpty() } + ?: properties.apiKey?.trim()?.takeIf { it.isNotEmpty() } + ?: error("MiniMax API key required: set MINIMAX_API_KEY env var or embabel.agent.platform.models.minimax.api-key"), + completionsPath = null, + embeddingsPath = null, + observationRegistry = observationRegistry.getIfUnique { ObservationRegistry.NOOP }, + requestFactory = requestFactory, +) { + + init { + logger.info("MiniMax models are available: {}", properties) + } + + @Bean + fun miniMaxM27(): LlmService<*> { + return openAiCompatibleLlm( + model = MiniMaxModels.MINIMAX_M2_7, + provider = MiniMaxModels.PROVIDER, + knowledgeCutoffDate = LocalDate.of(2025, 6, 1), + optionsConverter = MiniMaxOptionsConverter, + pricingModel = PerTokenPricingModel( + usdPer1mInputTokens = 1.10, + usdPer1mOutputTokens = 4.40, + ), + retryTemplate = properties.retryTemplate(MiniMaxModels.MINIMAX_M2_7), + ) + } + + @Bean + fun miniMaxM27Highspeed(): LlmService<*> { + return openAiCompatibleLlm( + model = MiniMaxModels.MINIMAX_M2_7_HIGHSPEED, + provider = MiniMaxModels.PROVIDER, + knowledgeCutoffDate = LocalDate.of(2025, 6, 1), + optionsConverter = MiniMaxOptionsConverter, + pricingModel = PerTokenPricingModel( + usdPer1mInputTokens = 0.55, + usdPer1mOutputTokens = 2.20, + ), + retryTemplate = properties.retryTemplate(MiniMaxModels.MINIMAX_M2_7_HIGHSPEED), + ) + } +} + +/** + * Options converter for MiniMax models. + * MiniMax requires temperature to be in the range (0.0, 1.0]. + * Values outside this range are clamped accordingly. + */ +object MiniMaxOptionsConverter : OptionsConverter { + + private const val MIN_TEMPERATURE = 0.01 + private const val MAX_TEMPERATURE = 1.0 + + override fun convertOptions(options: LlmOptions): OpenAiChatOptions { + val temperature = options.temperature?.let { temp -> + temp.coerceIn(MIN_TEMPERATURE, MAX_TEMPERATURE).also { clamped -> + if (clamped != temp) { + loggerFor().debug( + "MiniMax temperature clamped from {} to {} (valid range: ({}, {}])", + temp, clamped, 0.0, MAX_TEMPERATURE + ) + } + } + } + return OpenAiChatOptions.builder() + .temperature(temperature) + .topP(options.topP) + .maxTokens(options.maxTokens) + .presencePenalty(options.presencePenalty) + .frequencyPenalty(options.frequencyPenalty) + .build() + } +} diff --git a/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..0ffc9936b --- /dev/null +++ b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,16 @@ +# +# Copyright 2025-2025 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +com.embabel.agent.autoconfigure.models.minimax.AgentMiniMaxAutoConfiguration \ No newline at end of file diff --git a/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/test/kotlin/com/embabel/agent/config/models/minimax/MiniMaxOptionsConverterTest.kt b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/test/kotlin/com/embabel/agent/config/models/minimax/MiniMaxOptionsConverterTest.kt new file mode 100644 index 000000000..84cb96ff3 --- /dev/null +++ b/embabel-agent-autoconfigure/models/embabel-agent-minimax-autoconfigure/src/test/kotlin/com/embabel/agent/config/models/minimax/MiniMaxOptionsConverterTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2024-2026 Embabel Pty Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.embabel.agent.config.models.minimax + +import com.embabel.agent.test.models.OptionsConverterTestSupport +import com.embabel.common.ai.model.LlmOptions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.springframework.ai.openai.OpenAiChatOptions + +class MiniMaxOptionsConverterTest : OptionsConverterTestSupport( + optionsConverter = MiniMaxOptionsConverter +) { + @Test + fun `should set override maxTokens default`() { + val options = optionsConverter.convertOptions(LlmOptions().withMaxTokens(200)) + assertEquals(200, options.maxTokens) + } + + @Test + fun `should clamp temperature below minimum to minimum`() { + val options = optionsConverter.convertOptions(LlmOptions().withTemperature(0.0)) + assertTrue(options.temperature >= 0.01, "Temperature should be clamped to at least 0.01") + } + + @Test + fun `should clamp temperature above maximum to maximum`() { + val options = optionsConverter.convertOptions(LlmOptions().withTemperature(2.0)) + assertEquals(1.0, options.temperature) + } + + @Test + fun `should preserve valid temperature`() { + val options = optionsConverter.convertOptions(LlmOptions().withTemperature(0.7)) + assertEquals(0.7, options.temperature) + } + + @Test + fun `should handle null temperature`() { + val options = optionsConverter.convertOptions(LlmOptions()) + assertEquals(null, options.temperature) + } + + @Test + fun `should preserve topP`() { + val options = optionsConverter.convertOptions(LlmOptions().withTopP(0.9)) + assertEquals(0.9, options.topP) + } + + @Test + fun `should preserve presencePenalty`() { + val options = optionsConverter.convertOptions(LlmOptions().withPresencePenalty(0.5)) + assertEquals(0.5, options.presencePenalty) + } + + @Test + fun `should preserve frequencyPenalty`() { + val options = optionsConverter.convertOptions(LlmOptions().withFrequencyPenalty(0.3)) + assertEquals(0.3, options.frequencyPenalty) + } +} diff --git a/embabel-agent-autoconfigure/pom.xml b/embabel-agent-autoconfigure/pom.xml index 698f096eb..3ac8cae18 100644 --- a/embabel-agent-autoconfigure/pom.xml +++ b/embabel-agent-autoconfigure/pom.xml @@ -37,6 +37,7 @@ models/embabel-agent-deepseek-autoconfigure models/embabel-agent-gemini-autoconfigure models/embabel-agent-google-genai-autoconfigure + models/embabel-agent-minimax-autoconfigure models/embabel-agent-mistral-ai-autoconfigure models/embabel-agent-onnx-autoconfigure diff --git a/embabel-agent-dependencies/pom.xml b/embabel-agent-dependencies/pom.xml index 645c340c8..8ae4b67b7 100644 --- a/embabel-agent-dependencies/pom.xml +++ b/embabel-agent-dependencies/pom.xml @@ -168,6 +168,12 @@ ${project.version} + + com.embabel.agent + embabel-agent-minimax-autoconfigure + ${project.version} + + com.embabel.agent embabel-agent-mistral-ai-autoconfigure @@ -288,6 +294,12 @@ ${project.version} + + com.embabel.agent + embabel-agent-starter-minimax + ${project.version} + + com.embabel.agent embabel-agent-starter-mistral-ai diff --git a/embabel-agent-starters/embabel-agent-starter-minimax/pom.xml b/embabel-agent-starters/embabel-agent-starter-minimax/pom.xml new file mode 100644 index 000000000..155e0d360 --- /dev/null +++ b/embabel-agent-starters/embabel-agent-starter-minimax/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + com.embabel.agent + embabel-agent-starters + 0.3.5-SNAPSHOT + + + embabel-agent-starter-minimax + jar + Embabel Agent MiniMax Starter + Embabel Agent MiniMax Starter + https://github.com/embabel/embabel-agent + + + https://github.com/embabel/embabel-agent + scm:git:https://github.com/embabel/embabel-agent.git + scm:git:https://github.com/embabel/embabel-agent.git + HEAD + + + + + com.embabel.agent + embabel-agent-platform-autoconfigure + + + com.embabel.agent + embabel-agent-minimax-autoconfigure + + + + diff --git a/embabel-agent-starters/pom.xml b/embabel-agent-starters/pom.xml index d7307dc9f..58fd6cde6 100644 --- a/embabel-agent-starters/pom.xml +++ b/embabel-agent-starters/pom.xml @@ -36,6 +36,7 @@ embabel-agent-starter-deepseek embabel-agent-starter-gemini embabel-agent-starter-google-genai + embabel-agent-starter-minimax embabel-agent-starter-mistral-ai embabel-agent-starter-a2a embabel-agent-starter-webmvc