diff --git a/llmstack/processors/providers/minimax/__init__.py b/llmstack/processors/providers/minimax/__init__.py new file mode 100644 index 00000000000..e8f778bba8c --- /dev/null +++ b/llmstack/processors/providers/minimax/__init__.py @@ -0,0 +1,25 @@ +from typing import Optional + +from pydantic import Field + +from llmstack.processors.providers.config import ProviderConfig + + +class MiniMaxProviderConfig(ProviderConfig): + provider_slug: str = "minimax" + api_key: str = Field( + title="API Key", + description="Your MiniMax API Key", + default="", + json_schema_extra={"widget": "password", "advanced_parameter": False}, + ) + base_url: Optional[str] = Field( + title="Base URL", + description="Base URL for the MiniMax OpenAI-compatible API.", + default="https://api.minimax.io/v1", + ) + organization: Optional[str] = Field( + title="Organization ID", + description="Organization ID to use in MiniMax API requests", + default=None, + ) diff --git a/llmstack/processors/providers/minimax/chat_completions.py b/llmstack/processors/providers/minimax/chat_completions.py new file mode 100644 index 00000000000..89b3dccc23d --- /dev/null +++ b/llmstack/processors/providers/minimax/chat_completions.py @@ -0,0 +1,167 @@ +import logging +from typing import List, Optional + +from asgiref.sync import async_to_sync +from pydantic import Field + +from llmstack.apps.schemas import OutputTemplate +from llmstack.common.blocks.base.schema import StrEnum +from llmstack.processors.providers.api_processor_interface import ( + ApiProcessorInterface, + ApiProcessorSchema, +) + +logger = logging.getLogger(__name__) + + +class MessagesModel(StrEnum): + MINIMAX_M3 = "MiniMax-M3" + + def model_name(self): + return self.value + + +class Role(StrEnum): + USER = "user" + SYSTEM = "system" + ASSISTANT = "assistant" + + +class ChatMessage(ApiProcessorSchema): + role: Role = Field( + default=Role.USER, + description="The role of the message. Can be 'system', 'user', or 'assistant'.", + ) + message: str = Field( + default="", + description="The message text.", + json_schema_extra={"widget": "textarea"}, + ) + + +class MessagesInput(ApiProcessorSchema): + messages: List[ChatMessage] = Field( + default=[ + ChatMessage(), + ], + description="A list of messages, each with a role and message text.", + ) + + +class MessagesOutput(ApiProcessorSchema): + result: str = Field(description="The response message.") + + +class MessagesConfiguration(ApiProcessorSchema): + system_prompt: str = Field( + default="", + description="A system prompt is a way of providing context and instructions to the model.", + json_schema_extra={"widget": "textarea", "advanced_parameter": False}, + ) + + model: MessagesModel = Field( + default=MessagesModel.MINIMAX_M3, + description="The MiniMax model that will generate the responses.", + json_schema_extra={"advanced_parameter": False}, + ) + max_tokens: int = Field( + ge=1, + default=256, + description="The maximum number of tokens to generate before stopping.", + json_schema_extra={"advanced_parameter": False}, + ) + temperature: float = Field( + default=0.5, + description="Amount of randomness injected into the response.", + multiple_of=0.1, + ge=0.0, + le=1.0, + json_schema_extra={"advanced_parameter": False}, + ) + retain_history: Optional[bool] = Field( + default=False, + description="Retain and use the chat history. (Only works in apps)", + ) + seed: Optional[int] = Field( + default=None, + description="The seed to use for random sampling. If set, different calls will generate deterministic results.", + ) + + +class MessagesProcessor(ApiProcessorInterface[MessagesInput, MessagesOutput, MessagesConfiguration]): + @staticmethod + def name() -> str: + return "Chat Completions" + + @staticmethod + def slug() -> str: + return "chat_completions" + + @staticmethod + def description() -> str: + return "MiniMax Chat Completions" + + @staticmethod + def provider_slug() -> str: + return "minimax" + + @classmethod + def get_output_template(cls) -> OutputTemplate: + return OutputTemplate( + markdown="""{{ result }}""", + jsonpath="$.result", + ) + + def process_session_data(self, session_data): + self._chat_history = session_data["chat_history"] if "chat_history" in session_data else [] + + def session_data_to_persist(self) -> dict: + if self._config.retain_history: + return {"chat_history": self._chat_history} + return {} + + def process(self) -> MessagesOutput: + from llmstack.common.utils.sslr import LLM + + minimax_provider_config = self.get_provider_config(model_slug=self._config.model.model_name()) + client = LLM( + provider="openai", + openai_api_key=minimax_provider_config.api_key, + openai_base_url=minimax_provider_config.base_url, + organization=minimax_provider_config.organization, + ) + messages = [] + + if self._config.system_prompt: + messages.append({"role": "system", "content": self._config.system_prompt}) + + if self._chat_history: + for message in self._chat_history: + messages.append({"role": message["role"], "content": message["message"]}) + + for message in self._input.messages: + messages.append({"role": str(message.role), "content": str(message.message)}) + + response = client.chat.completions.create( + messages=messages, + model=self._config.model.model_name(), + max_tokens=self._config.max_tokens, + stream=True, + temperature=self._config.temperature, + seed=self._config.seed, + ) + + for result in response: + choice = result.choices[0] + if choice.delta.content: + async_to_sync(self._output_stream.write)(MessagesOutput(result=choice.delta.content)) + + output = self._output_stream.finalize() + + if self._config.retain_history: + for message in self._input.messages: + self._chat_history.append({"role": str(message.role), "message": str(message.message)}) + + self._chat_history.append({"role": "assistant", "message": output.message}) + + return output diff --git a/llmstack/server/settings.py b/llmstack/server/settings.py index b720d01ecf4..ef8db3c500d 100644 --- a/llmstack/server/settings.py +++ b/llmstack/server/settings.py @@ -574,6 +574,12 @@ "slug": "mistral", "config_schema": "llmstack.processors.providers.mistral.MistralProviderConfig", }, + { + "name": "MiniMax", + "processor_packages": ["llmstack.processors.providers.minimax"], + "slug": "minimax", + "config_schema": "llmstack.processors.providers.minimax.MiniMaxProviderConfig", + }, { "name": "Meta", "processor_packages": ["llmstack.processors.providers.meta"],