diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 72b5750b..a944cca2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -80,6 +80,7 @@ When writing tests that require calling AI models, the following secrets are ava - `OPENAI_API_KEY` - API key for OpenAI GPT Models - `ZAI_API_KEY` - API key for Z.AI GLM Models - `MOONSHOT_API_KEY` - API key for MoonShot Kimi Models +- `MINIMAX_API_KEY` - API key or Token Plan Subscription Key for MiniMax Models - `DEEPSEEK_API_KEY` - API key for DeepSeek Models - `MODELVERSE_API_KEY` - API key for ModelVerse Models - `OPENROUTER_API_KEY` - API key for OpenRouter Models @@ -96,6 +97,7 @@ env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} + MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} MODELVERSE_API_KEY: ${{ secrets.MODELVERSE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml index 0246ca0e..54008d63 100644 --- a/.github/workflows/jest.yml +++ b/.github/workflows/jest.yml @@ -67,6 +67,7 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} + MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} MODELVERSE_API_KEY: ${{ secrets.MODELVERSE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index f60419ea..4b9bc100 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -60,6 +60,7 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} + MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }} MODELVERSE_API_KEY: ${{ secrets.MODELVERSE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} diff --git a/changelog/0.4.2/2026-08-03-minimax-m3.md b/changelog/0.4.2/2026-08-03-minimax-m3.md new file mode 100644 index 00000000..45fefe1b --- /dev/null +++ b/changelog/0.4.2/2026-08-03-minimax-m3.md @@ -0,0 +1,34 @@ +# MiniMax M3 support + +AgentHub now supports the official MiniMax `MiniMax-M3` Responses API at `https://api.minimax.io/v1`. The `minimax-m3` client accepts either a MiniMax Token Plan Subscription Key or a pay-as-you-go API key through `MINIMAX_API_KEY`; `MINIMAX_BASE_URL` overrides the default endpoint. + +## Protocol implementation + +- Added paired Python and TypeScript MiniMax Responses clients, exact `MiniMax-M3` routing, a supported-model registry entry, env-gated E2E registration, and CI secret forwarding. +- A two-round live capture verified reasoning, function calls, replayed function outputs, and a final answer. The first response produced two independent tool calls. +- The observed stream sequence is `response.created`, `response.in_progress`, reasoning item/content events, then `response.output_item.added`, argument deltas, `response.function_call_arguments.done`, and `response.output_item.done` for each function call before `response.completed`. The raw `arguments.done` event omitted the function name, so AgentHub finalizes calls from the authoritative completed output item. +- Fidelity is recorded only when the universal fields cannot represent information needed by the client: partial tool-call events retain `item_id` and `output_index` so interleaved parallel deltas remain correlatable, text items retain their output-item ID as `phase`, completed reasoning items retain their JSON wire item for replay, and completed function calls retain only their raw argument string so JSON numeric precision and formatting survive replay. Completed messages and the remaining function-call fields are rebuilt from their universal fields without duplicating the full wire item in fidelity. +- `response.completed` reports `tool_call` when its output contains a function call. `response.incomplete` maps `max_output_tokens` to `length` and `content_filter` to `stop`; failed and error events surface the provider's details. +- Captured reasoning uses `response.reasoning_text.delta` rather than OpenAI's reasoning-summary event. MiniMax does not use OpenAI's encrypted reasoning content, so AgentHub does not request it. +- Completed-response usage maps `input_tokens_details.cached_tokens` to `cached_tokens`, and `output_tokens_details.reasoning_tokens` to `thoughts_tokens`. + +## Configuration behavior + +| AgentHub configuration | MiniMax request behavior | +| --- | --- | +| `system_prompt` | `instructions` | +| `max_tokens` | `max_output_tokens` | +| `temperature` | Passed through for the documented range 0–1; other values raise `UnsupportedParameterError`. | +| `ThinkingLevel.NONE` | `reasoning.effort = "none"`. | +| `ThinkingLevel.LOW` / `MEDIUM` / `HIGH` | `reasoning.effort` `low` / `medium` / `high`. | +| `ThinkingLevel.XHIGH` | Gracefully degrades to `high`. | +| `thinking_summary` | Omitted because MiniMax documents no matching request field. | +| `PromptCaching.ENABLE` | Omitted; MiniMax caching is automatic. | +| `PromptCaching.DISABLE` | Raises `UnsupportedParameterError`; MiniMax documents no cache-disable setting. | +| `PromptCaching.ENHANCE` | Raises `UnsupportedParameterError`; MiniMax documents no configurable cache-retention setting. | +| `tool_choice` `auto` / `none` | Passed through. | +| `tool_choice` `required` or named-tool list | Raises `UnsupportedParameterError`; MiniMax does not document those modes. | + +## Registry metadata + +`MiniMax-M3` is registered with text and image input, text output, and a 1,000,000-token context window. Pricing is omitted because MiniMax doubles cache-read, input, and output rates above 512K input tokens, while the registry cannot express tiered pricing. diff --git a/changelog/0.4.2/README.md b/changelog/0.4.2/README.md index 4c105f3e..811c984b 100644 --- a/changelog/0.4.2/README.md +++ b/changelog/0.4.2/README.md @@ -1,3 +1,4 @@ # 0.4.2 (unreleased) +- [2026-08-03] Official MiniMax M3 direct Responses API and Token Plan Subscription Key support. ([details](2026-08-03-minimax-m3.md)) - [2026-07-24] Gemini 3 clients clamp thinking levels to what each model actually supports — fixes `gemini-3.1-pro` rejecting `ThinkingLevel.NONE` with "Thinking level MINIMAL is not supported". ([details](2026-07-24-gemini-thinking-level-clamp.md)) diff --git a/llmsdk_docs/README.md b/llmsdk_docs/README.md index 66cc941f..c0732c54 100644 --- a/llmsdk_docs/README.md +++ b/llmsdk_docs/README.md @@ -16,6 +16,7 @@ To use a specific model, please refer to its dedicated README: - **[GLM-5.2](./glm5_2/README.md)** - Z.AI's GLM-5.2 API documentation (reasoning_effort, thinking modes, tool streaming) - **[GPT-5.5](./gpt5_5/README.md)** - OpenAI's GPT-5.5 API documentation and examples - **[Kimi K3](./kimi_k3/README.md)** - Moonshot's Kimi K3 API documentation (reasoning_effort, tool calling, vision, caching) +- **[MiniMax M-series](./minimax_m3/README.md)** - Responses API-compatible documentation for MiniMax M3 and M2.7, plus Token Plan Subscription Key integration Each model directory contains: - `docs/` - Detailed documentation for the model's features and capabilities diff --git a/llmsdk_docs/minimax_m3/README.md b/llmsdk_docs/minimax_m3/README.md new file mode 100644 index 00000000..02705abf --- /dev/null +++ b/llmsdk_docs/minimax_m3/README.md @@ -0,0 +1,44 @@ +# MiniMax M3 SDK Documentation + +This directory contains the official-documentation snapshot used to implement MiniMax M3 Responses API support and Token Plan authentication. + +## Quick Start + +- **Python users**: See [quickstart.python.md](./quickstart.python.md) +- **TypeScript users**: See [quickstart.typescript.md](./quickstart.typescript.md) + +## Documentation + +The `docs/` directory contains the official MiniMax documentation used for this protocol: + +- [api-overview.md](./docs/api-overview.md) - API-key and Subscription Key overview, model list, and supported SDK surfaces +- [models-intro.md](./docs/models-intro.md) - Current language and multimodal model catalog +- [list-models.md](./docs/list-models.md) - `GET /v1/models` schema and model IDs +- [responses-create.md](./docs/responses-create.md) - `POST /v1/responses` request and response schemas, reasoning, tool calls, history replay, input modalities, and usage +- [errorcode.md](./docs/errorcode.md) - Common authentication, rate-limit, quota, content, and server error codes +- [text-openai-api.md](./docs/text-openai-api.md) - OpenAI-compatible model coverage and M3/M2.x behavior +- [text-anthropic-api.md](./docs/text-anthropic-api.md) - Anthropic-compatible model coverage, content modalities, tools, and thinking behavior +- [tool-use-interleaved-thinking.md](./docs/tool-use-interleaved-thinking.md) - Tool use and the requirement to preserve complete reasoning-bearing assistant history +- [prompt-caching.md](./docs/prompt-caching.md) - Passive caching behavior, cache-hit usage, and pricing semantics +- [pricing-token-plan.md](./docs/pricing-token-plan.md) - Token Plan pricing and quota coverage +- [pricing-paygo.md](./docs/pricing-paygo.md) - Current and legacy pay-as-you-go model pricing +- [token-plan-overview.md](./docs/token-plan-overview.md) - Subscription Key lifecycle, quota windows, and API-key distinction +- [index.md](./docs/index.md) - MiniMax's official documentation index and API-spec links + +The official Responses page documents SSE support but not the exact event sequence. AgentHub verifies event ordering with two-round live captures under the git-ignored `api_captures/minimax_m3/` directory and records the observed protocol details in the release changelog. + +## Official sources + +- https://platform.minimax.io/docs/api-reference/responses-create +- https://platform.minimax.io/docs/api-reference/models/openai/list-models +- https://platform.minimax.io/docs/api-reference/text-openai-api +- https://platform.minimax.io/docs/api-reference/text-anthropic-api +- https://platform.minimax.io/docs/guides/models-intro +- https://platform.minimax.io/docs/guides/pricing-paygo +- https://platform.minimax.io/docs/guides/text-m3-function-call +- https://platform.minimax.io/docs/token-plan/intro +- https://platform.minimax.io/docs/api-reference/api-overview +- https://platform.minimax.io/docs/api-reference/errorcode.md +- https://platform.minimax.io/docs/api-reference/text-prompt-caching.md +- https://platform.minimax.io/docs/guides/pricing-token-plan.md +- https://platform.minimax.io/docs/llms.txt diff --git a/llmsdk_docs/minimax_m3/docs/api-overview.md b/llmsdk_docs/minimax_m3/docs/api-overview.md new file mode 100644 index 00000000..f7131878 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/api-overview.md @@ -0,0 +1,353 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimax.io/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# API Overview + +> Overview of MiniMax API capabilities including language, speech, video, image, music, and file management. + +## Get API Key + +* **Pay-as-you-go**:Visit [API Keys > Create new secret key](https://platform.minimax.io/user-center/basic-information/interface-key) to get your **API Key** + Pay-as-you-go supports all modality models, including language, Video, Speech, and Image. + +* **Token Plan**:Visit [Billing > Token Plan](https://platform.minimax.io/user-center/payment/token-plan) to view your **Subscription Key** + The Subscription Key is used for Token Plan subscriptions and purchased Credits. It is separate from pay-as-you-go API Keys. See [Token Plan Overview](https://platform.minimax.io/docs/token-plan/intro) for details. + +*** + +## LLM + +The LLM API uses **MiniMax M3**, **MiniMax M2.7**, **MiniMax M2.7 highspeed**, **MiniMax M2.5**, **MiniMax M2.5 highspeed**, **MiniMax M2.1**, **MiniMax M2.1 highspeed**, and **MiniMax M2** to generate conversational content and trigger tool calls based on the provided context. + +It can be accessed via **HTTP requests**, the **Anthropic SDK** (Recommended), or the **OpenAI SDK**. + +**Supported Models** + +| Model Name | Context Window | Description | +| :--------------------- | :------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | +| MiniMax-M3 | 1,000,000 | **Latest M-series language model for agentic reasoning, tool use, coding, and long-context tasks** | +| MiniMax-M2.7 | 204,800 | **Beginning the journey of recursive self-improvement. (output speed approximately 60 tps)** | +| MiniMax-M2.7-highspeed | 204,800 | **M2.7 highspeed: Same performance, faster and more agile (output speed approximately 100 tps)** | +| MiniMax-M2.5 | 204,800 | **Peak Performance. Ultimate Value. Master the Complex (output speed approximately 60 tps)** | +| MiniMax-M2.5-highspeed | 204,800 | **M2.5 highspeed: Same performance, faster and more agile (output speed approximately 100 tps)** | +| MiniMax-M2.1 | 204,800 | **Powerful Multi-Language Programming Capabilities with Comprehensively Enhanced Programming Experience (output speed approximately 60 tps)** | +| MiniMax-M2.1-highspeed | 204,800 | **Faster and More Agile (output speed approximately 100 tps)** | +| MiniMax-M2 | 204,800 | **Agentic capabilities, Advanced reasoning** | + +Please note: The maximum token count refers to the total number of input and output tokens. + + + + Use Anthropic SDK with MiniMax models + + + + Use OpenAI SDK with MiniMax models + + + +*** + +## MiniMax-H3 \ + +This API is powered by MiniMax-H3 and supports video generation from multimodal input (text, images, video, audio), covering text-to-video, image-to-video, first-and-last-frame, and reference-to-video scenarios. + +**Supported Models** + +| Model | Description | +| :--------- | :---------------------------------------------------------------------------------------------------------------------------------------- | +| MiniMax-H3 | Multimodal video generation model supporting text / image / first-and-last-frame / reference input, 768P / 2K resolution, 4–15s duration. | + +**API Usage Guide** + +MiniMax-H3 tasks are asynchronous. There are three creation endpoints—**Create Video Generation Task**, **Create H3-Context-IR Task**, and **Create Video Regeneration Task**—and shared endpoints for querying, listing, and cancelling or deleting tasks. The workflow is as follows: + +1. Create a video generation task, create an H3-Context-IR task with the same multimodal input, or create a video regeneration task for a source video that meets the MiniMax-H3 768P output specifications. A regeneration request must contain exactly one source-video item with `role=base_video`. All three endpoints return a `task_id` on success. +2. Use **Query Task** with the `task_id` to retrieve its status and result. When a video task succeeds, get its output URL from `content.url`; when an H3-Context-IR task succeeds, get the enhanced prompt from `content.prompt`. You can also use **List Tasks** and distinguish `generation`, `h3_context_ir`, and `regeneration` with `task_type`. +3. Use **Cancel or Delete Task** to cancel a queued task or delete a succeeded or failed task record. + + + + Create a video generation task from multimodal content input + + + + Deeply interpret multimodal video-generation context and produce a structured, enhanced prompt + + + + Regenerate a video that meets the MiniMax-H3 768P output specifications as a 2K video + + + + Query task status by task\_id and get the video download URL + + + + List tasks from the last 7 days and filter by task type + + + + Cancel a queued task or delete a succeeded or failed task record + + + +*** + +## Text to Speech + +This API provides synchronous text-to-speech (T2A) generation, supporting up to **10,000** characters per request. +The interface is stateless: each call only processes the provided input without involving business logic, and the model does not store any user data. + +**Key Features** + +1. Access to 300+ system voices and custom cloned voices. +2. Adjustable volume, pitch, speed, and output formats. +3. Support for proportional audio mixing. +4. Configurable fixed time intervals. +5. Multiple audio formats and specifications supported: `mp3`, `pcm`, `flac`, `wav`. +6. Support for streaming output. + +**Typical Use Cases:** short text generation, voice chat, online social interactions. + +**Supported Models** + +| Model | Description | +| :--------------- | :------------------------------------------------------------------------------------------------------- | +| speech-2.8-hd | Latest HD model. Ultra-realistic quality featuring sound tags. | +| speech-2.8-turbo | Latest Turbo model. Seamless speed meets natural flow. | +| speech-2.6-hd | HD model with outstanding prosody and excellent cloning similarity. | +| speech-2.6-turbo | Turbo model with support for 40 languages. | +| speech-02-hd | Superior rhythm and stability, with outstanding performance in replication similarity and sound quality. | +| speech-02-turbo | Superior rhythm and stability, with enhanced multilingual capabilities and excellent performance. | + +**Available Interfaces** + +Synchronous speech synthesis provides two interfaces. Choose based on your needs: + +* HTTP T2A API +* WebSocket T2A API + +### Supported Languages + +MiniMax speech synthesis models offer robust multilingual capability, supporting **40 widely used languages** worldwide. + +| Support Languages | | | +| ----------------- | ------------- | ------------- | +| 1. Chinese | 15. Turkish | 28. Malay | +| 2. Cantonese | 16. Dutch | 29. Persian | +| 3. English | 17. Ukrainian | 30. Slovak | +| 4. Spanish | 18. Thai | 31. Swedish | +| 5. French | 19. Polish | 32. Croatian | +| 6. Russian | 20. Romanian | 33. Filipino | +| 7. German | 21. Greek | 34. Hungarian | +| 8. Portuguese | 22. Czech | 35. Norwegian | +| 9. Arabic | 23. Finnish | 36. Slovenian | +| 10. Italian | 24. Hindi | 37. Catalan | +| 11. Japanese | 25. Bulgarian | 38. Nynorsk | +| 12. Korean | 26. Danish | 39. Tamil | +| 13. Indonesian | 27. Hebrew | 40. Afrikaans | +| 14. Vietnamese | | | + + + + Synchronous speech synthesis via HTTP + + + + Streaming speech synthesis via WebSocket + + + +*** + +## Asynchronous Long-Text Speech Generation + +This API supports asynchronous text-to-speech generation. Each request can handle up to **1 million characters**, and the resulting audio can be retrieved asynchronously. + +Features supported: + +1. Choose from 100+ system voices and cloned voices. +2. Customize pitch, speed, volume, bitrate, sample rate, and output format. +3. Retrieve audio metadata, such as duration and file size. +4. Retrieve precise sentence-level timestamps (subtitles). +5. Input text directly as a string or via `file_id` after uploading a text file. +6. Detect illegal characters: + * If illegal characters are **≤10%**, audio is generated normally, with the ratio returned. + * If illegal characters are **>10%**, no audio will be generated (an error code will be returned). + +**Note:** The returned audio URL is valid for **9 hours** (32,400 seconds) from the time it is issued. After expiration, the URL becomes invalid and the generated data will be lost. + +**Use Case:** Converting entire books or other long texts into audio. + +**Supported Models** + +| Model | Description | +| :--------------- | :------------------------------------------------------------------------------------------------------- | +| speech-2.8-hd | Latest HD model. Ultra-realistic quality featuring sound tags. | +| speech-2.8-turbo | Latest Turbo model. Seamless speed meets natural flow. | +| speech-2.6-hd | HD model with outstanding prosody and excellent cloning similarity. | +| speech-2.6-turbo | Turbo model with support for 40 languages. | +| speech-02-hd | Superior rhythm and stability, with outstanding performance in replication similarity and sound quality. | +| speech-02-turbo | Superior rhythm and stability, with enhanced multilingual capabilities and excellent performance. | + +**API Overview** + +This feature includes **two APIs**: + +1. Create a speech generation task (returns `task_id`). +2. Query the speech generation task status using `task_id`. +3. If the task succeeds, use the returned `file_id` with the **File API** to view and download the result. + + + + Create a long-text speech generation task + + + + Query speech generation task status + + + +*** + +## Voice Cloning + +This API supports cloning voices from user-uploaded audio files along with optional sample audio to enhance cloning quality. + +**Use cases:** fast replication of a target timbre (IP voice recreation, voice cloning) where you need to quickly clone a specific voice. + +The API supports cloning from mono or stereo audio and can rapidly reproduce speech that matches the timbre of a provided reference file. + +**Supported Models** + +| Model | Description | +| :--------------- | :------------------------------------------------------------------------------------------------------- | +| speech-2.8-hd | Latest HD model. Ultra-realistic quality featuring sound tags. | +| speech-2.8-turbo | Latest Turbo model. Seamless speed meets natural flow. | +| speech-2.6-hd | HD model with real-time response, intelligent parsing, fluent LoRA voice | +| speech-2.6-turbo | Turbo model. Ultimate Value, 40 Languages | +| speech-02-hd | Superior rhythm and stability, with outstanding performance in replication similarity and sound quality. | +| speech-02-turbo | Superior rhythm and stability, with enhanced multilingual capabilities and excellent performance. | + +### Notes + +* Using this API to clone a voice **does not** immediately incur a cloning fee. The cloning fee is charged the **first time** you synthesize speech with the cloned voice in a T2A synthesis API (the preview/audition within this API does not count). +* Voices produced via this rapid cloning API are **temporary**. To keep a cloned voice permanently, call **any** T2A speech synthesis API with that voice **within 168 hours (7 days)** (the preview/audition within this API does not count). If the time limit is exceeded, the voice will be deleted. +* This API is stateless: each call only processes the incoming data, does not store user-uploaded content, and involves no business-logic state. + + + + Upload audio file to clone + + + + Execute voice cloning + + + +*** + +## Voice Design + +This API supports generating personalized custom voices based on user-provided voice description prompts. + +The generated voices (voice\_id) can then be used in the T2A API and the T2A Async API for speech generation. + +**Supported Models** + +> It is recommended to use **speech-02-hd** for the best results. + +| Model | Description | +| :--------------- | :------------------------------------------------------------------------------------------------------- | +| speech-2.8-hd | Latest HD model. Ultra-realistic quality featuring sound tags. | +| speech-2.8-turbo | Latest Turbo model. Seamless speed meets natural flow. | +| speech-2.6-hd | HD model with real-time response, intelligent parsing, fluent LoRA voice | +| speech-2.6-turbo | Turbo model. Ultimate Value, 40 Languages | +| speech-02-hd | Superior rhythm and stability, with outstanding performance in replication similarity and sound quality. | +| speech-02-turbo | Superior rhythm and stability, with enhanced multilingual capabilities and excellent performance. | + +### Notes + +> * Using this API to generate a voice does not immediately incur a fee. The generation fee will be charged upon the first use of the generated voice in speech synthesis. +> * Voices generated through this API are temporary. If you wish to keep a voice permanently, you must use it in any speech synthesis API within 168 hours (7 days). + + + Generate personalized voices from descriptions + + +*** + +## Image Generation + +This API supports images generations from text or references, allowing custom aspect ratios and resolutions for diverse needs. + +**API Description** + +You can generate images by creating an image generation task using text prompts and/or reference images. + +**Model List** + +| Model | Description | +| :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| image-01 | A high-quality image generation model that produces fine-grained details. Supports both text-to-image and image-to-image generation (with subject reference for people). | + + + + Generate image from text description + + + + Generate image from reference image + + + +*** + +## Music Generation + +This API generates a vocal song based on a music description (prompt) and lyrics. + +**Models** + +| Model | Usage | +| :-------- | :--------------------------------------------------------------------------------------------------------------------- | +| music-3.0 | The latest music generation model. Supports user-provided musical inspiration and lyrics to create AI-generated music. | + + + Generate music from description and lyrics + + +*** + +## File Management + +This API is for file management and is used with other MiniMax APIs. + +**API Description** + +This API includes 5 endpoints: **Upload**, **List**, **Retrieve**, **Retrieve Content**, **Delete**. + +Supported file formats, capacity, and size limits are defined by the **Upload File** API documentation — see [Upload File](/docs/api-reference/file-management-upload). + + + + Upload files to the platform + + + + Get list of uploaded files + + + +*** + +## Official MCP + +MiniMax provides official Model Context Protocol (MCP) server implementations: + +* [Python version](https://github.com/MiniMax-AI/MiniMax-MCP) +* [JavaScript version](https://github.com/MiniMax-AI/MiniMax-MCP-JS) + +Both support speech synthesis, voice cloning, video generation, and music generation. For details, refer to the [MiniMax MCP User Guide](/docs/guides/mcp-guide). diff --git a/llmsdk_docs/minimax_m3/docs/errorcode.md b/llmsdk_docs/minimax_m3/docs/errorcode.md new file mode 100644 index 00000000..7fe3f64c --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/errorcode.md @@ -0,0 +1,33 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimax.io/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# Error Codes + +> This document lists common MiniMax API error codes and solutions to help developers quickly resolve issues. + +| Error Code | Message | Solution | +| :--------- | :----------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | +| 1000 | unknown error | Please retry your requests later. | +| 1001 | request timeout | Please retry your requests later. | +| 1002 | rate limit | Please retry your requests later. | +| 1004 | not authorized / token not match group / cookie is missing, log in again | please check your API Key and make sure it is correct and active. | +| 1008 | insufficient balance | Please check your account balance. | +| 1024 | internal error | Please retry your requests later. | +| 1026 | input new_sensitive | Please change your input content. | +| 1027 | output new_sensitive | Please change your input content. | +| 1033 | system error / mysql failed | Please retry your requests later. | +| 1039 | token limit | Please retry your requests later. | +| 1041 | conn limit | Please contact us if the issue persists. | +| 1042 | invisible character ratio limit | Please check your input content for invisible or illegal characters. | +| 1043 | The asr similarity check failed | Please check file_id and text_validation. | +| 1044 | clone prompt similarity check failed | Please check clone prompt audio and prompt words. | +| 2013 | invalid params / glyph definition format error | Please check the request parameters. | +| 20132 | invalid samples or voice_id | Please check your file_id(in Voice Cloning API), voice_id(in T2A v2 API, T2A Large v2 API) and contact us if the issue persists. | +| 2037 | voice duration too short / voice duration too long | Please adjust the duration of your file_id for voice clone. | +| 2039 | voice clone voice id duplicate | Please check the voice_id to ensure no duplication with the existing ones. | +| 2042 | You don't have access to this voice_id | Please check whether you are the creator of this voice_id and contact us if the issue persists. | +| 2045 | rate growth limit | Please avoid sudden increases and decreases in requests. | +| 2048 | prompt audio too long | Please adjust the duration of the prompt_audio file (< 8s). | +| 2049 | invalid API Key | Please check your API Key and make sure it is correct and active. | +| 2056 | usage limit exceeded | Please wait for the resource release in the next 5-hour window. | diff --git a/llmsdk_docs/minimax_m3/docs/index.md b/llmsdk_docs/minimax_m3/docs/index.md new file mode 100644 index 00000000..ba80648c --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/index.md @@ -0,0 +1,149 @@ +# MiniMax API Docs + +## Docs + +- [Claude Code setup](https://platform.minimax.io/docs/ai-tools/claude-code.md): Configure Claude Code for your documentation workflow +- [Cursor setup](https://platform.minimax.io/docs/ai-tools/cursor.md): Configure Cursor for your documentation workflow +- [Windsurf setup](https://platform.minimax.io/docs/ai-tools/windsurf.md): Configure Windsurf for your documentation workflow +- [Explicit Prompt Caching (Anthropic API)](https://platform.minimax.io/docs/api-reference/anthropic-api-compatible-cache.md): MiniMax supports Anthropic API compatible caching that is managed through explicit cache_control settings. +- [API Overview](https://platform.minimax.io/docs/api-reference/api-overview.md): Overview of MiniMax API capabilities including language, speech, video, image, music, and file management. +- [Error Codes](https://platform.minimax.io/docs/api-reference/errorcode.md): This document lists common MiniMax API error codes and solutions to help developers quickly resolve issues. +- [Delete File](https://platform.minimax.io/docs/api-reference/file-management-delete.md): Delete files on the MiniMax API Platform. +- [List Files](https://platform.minimax.io/docs/api-reference/file-management-list.md): List files on the MiniMax API Platform. +- [Retrieve File](https://platform.minimax.io/docs/api-reference/file-management-retrieve.md): Retrieve files on the MiniMax API Platform. +- [Retrieve Content](https://platform.minimax.io/docs/api-reference/file-management-retrieve-content.md): Download the contents of a generated file. +- [Upload File](https://platform.minimax.io/docs/api-reference/file-management-upload.md): Upload a file on the MiniMax API Platform. +- [Image-to-Image Generation](https://platform.minimax.io/docs/api-reference/image-generation-i2i.md): Use this API to generate images from image input. +- [Text to Image Generation](https://platform.minimax.io/docs/api-reference/image-generation-t2i.md): Use this API to generate images from text input. +- [Lyrics Generation](https://platform.minimax.io/docs/api-reference/lyrics-generation.md): Use this API to generate lyrics, supporting full song creation and lyrics editing/continuation. +- [List Models](https://platform.minimax.io/docs/api-reference/models/anthropic/list-models.md): Returns a list of all available models compatible with Anthropic API specification. +- [Retrieve Model](https://platform.minimax.io/docs/api-reference/models/anthropic/retrieve-model.md): Retrieves details for a specific model, compatible with Anthropic API specification. +- [List Models](https://platform.minimax.io/docs/api-reference/models/openai/list-models.md): Returns a list of all available models compatible with OpenAI API specification. +- [Retrieve Model](https://platform.minimax.io/docs/api-reference/models/openai/retrieve-model.md): Retrieves details for a specific model, compatible with OpenAI API specification. +- [Music Cover Preprocess](https://platform.minimax.io/docs/api-reference/music-cover-preprocess.md): Preprocess reference audio to extract features and lyrics for two-step cover generation. +- [Music Generation](https://platform.minimax.io/docs/api-reference/music-generation.md): Use this API to generate a song from lyrics and a prompt. +- [Create Response](https://platform.minimax.io/docs/api-reference/responses-create.md): Call MiniMax models via the OpenAI Responses API compatible main endpoint. Generates model replies, supports streaming and non-streaming. +- [Estimate Input Tokens](https://platform.minimax.io/docs/api-reference/responses-input-tokens.md): Estimate the input token count of a request without invoking the model. Useful for evaluating request cost or checking context length limits before calling the main endpoint. +- [Create Speech Generation Task](https://platform.minimax.io/docs/api-reference/speech-t2a-async-create.md): Use this API to create an asynchronous Text-to-Speech task. +- [Query Speech Generation Task Status](https://platform.minimax.io/docs/api-reference/speech-t2a-async-query.md): Use this API to query the status of an asynchronous Text-to-Speech task. +- [Text to Speech (T2A) HTTP](https://platform.minimax.io/docs/api-reference/speech-t2a-http.md): Use this API for synchronous t2a over HTTP. +- [Text to Speech (T2A) WebSocket](https://platform.minimax.io/docs/api-reference/speech-t2a-websocket.md): Use this API for synchronous t2a over WebSocket. +- [AI SDK](https://platform.minimax.io/docs/api-reference/text-ai-sdk.md): Call MiniMax models using the AI SDK +- [Anthropic SDK](https://platform.minimax.io/docs/api-reference/text-anthropic-api.md): Call MiniMax models using the Anthropic SDK +- [Messages API](https://platform.minimax.io/docs/api-reference/text-chat-anthropic.md): Use the Anthropic API compatible Messages format to call MiniMax models. +- [Chat Completions API](https://platform.minimax.io/docs/api-reference/text-chat-openai.md): Use the OpenAI API compatible Chat Completions format to call MiniMax models. +- [OpenAI SDK](https://platform.minimax.io/docs/api-reference/text-openai-api.md): Call MiniMax models using the OpenAI SDK +- [Text Generation](https://platform.minimax.io/docs/api-reference/text-post.md): Use this API to create chat completions. +- [Prompt Caching](https://platform.minimax.io/docs/api-reference/text-prompt-caching.md): Prompt caching effectively reduces latency and costs. +- [Create Video Agent Task](https://platform.minimax.io/docs/api-reference/video-agent-create.md): Use this API to create video Agent tasks. +- [Query Video Template Generation Task](https://platform.minimax.io/docs/api-reference/video-agent-query.md): Use this API to query the task status of generated videos. +- [Video Download](https://platform.minimax.io/docs/api-reference/video-generation-download.md): Use this API to download generated videos. +- [Create First & Last Frame Video Generation Task](https://platform.minimax.io/docs/api-reference/video-generation-fl2v.md): Use this API to create a video generation task from start and end frame images, with optional text input. +- [Image-to-Video Task](https://platform.minimax.io/docs/api-reference/video-generation-i2v.md): Use this API to create a video generation task from image, with optional text input. +- [Query Video Generation Task Status](https://platform.minimax.io/docs/api-reference/video-generation-query.md) +- [Subject-Reference to Video Generation Task](https://platform.minimax.io/docs/api-reference/video-generation-s2v.md) +- [Create Text-to-Video Generation Task](https://platform.minimax.io/docs/api-reference/video-generation-t2v.md): Use this API to create a video generation task from text input. +- [Create Video Generation Task](https://platform.minimax.io/docs/api-reference/video-generation-v2-create.md): Video generation V2 endpoint. Provide multimodal input via the content array (text / image / video / audio) to support text-to-video, image-to-video (first & last frame), and reference-to-video, with 2K output. +- [Cancel or Delete Task](https://platform.minimax.io/docs/api-reference/video-generation-v2-delete.md): Cancel a queued task or delete a succeeded or failed video generation, H3-Context-IR, or video regeneration task record based on its current status. +- [Create H3-Context-IR Task](https://platform.minimax.io/docs/api-reference/video-generation-v2-h3-context-ir.md): Deeply interpret multimodal context and generate a structured, semantically enriched video prompt. +- [List Tasks](https://platform.minimax.io/docs/api-reference/video-generation-v2-list.md): List tasks from the last 7 days with pagination. Supports filtering by status, task ID, model, and task type. +- [Query Task](https://platform.minimax.io/docs/api-reference/video-generation-v2-query.md): Query the status and result of a single video generation, H3-Context-IR, or video regeneration task from the last 7 days by task_id. +- [Create Video Regeneration Task](https://platform.minimax.io/docs/api-reference/video-generation-v2-regeneration.md): Regenerate a source video that meets the MiniMax-H3 768P output specifications to produce a 2K video. +- [Voice Clone](https://platform.minimax.io/docs/api-reference/voice-cloning-clone.md): Use this API for rapid voice cloning. If a cloned voice is not used within 7 days, the system will delete it. +- [Upload Audio for Voice Cloning](https://platform.minimax.io/docs/api-reference/voice-cloning-uploadcloneaudio.md): Use this API to upload audio files for voice cloning. +- [Upload Prompt Auido](https://platform.minimax.io/docs/api-reference/voice-cloning-uploadprompt.md): Use this API to upload prompt audio file. Providing this file helps to enhance the voice similarity and stability of the Text-to-Speech output. +- [Voice Design](https://platform.minimax.io/docs/api-reference/voice-design-design.md): Use this API to design custom voices by inputting text. +- [Delete Voice](https://platform.minimax.io/docs/api-reference/voice-management-delete.md): Use this API to delete generated voices. +- [Get Voice](https://platform.minimax.io/docs/api-reference/voice-management-get.md): Use this API to list available voices by category. +- [Code blocks](https://platform.minimax.io/docs/essentials/code.md): Display inline code and code blocks +- [Images and embeds](https://platform.minimax.io/docs/essentials/images.md): Add image, video, and other HTML elements +- [Markdown syntax](https://platform.minimax.io/docs/essentials/markdown.md): Text, title, and styling in standard markdown +- [Navigation](https://platform.minimax.io/docs/essentials/navigation.md): The navigation field in docs.json defines the pages that go in the navigation menu +- [Reusable snippets](https://platform.minimax.io/docs/essentials/reusable-snippets.md): Reusable, custom snippets to keep content in sync +- [Global Settings](https://platform.minimax.io/docs/essentials/settings.md): Mintlify gives you complete control over the look and feel of your documentation using the docs.json file +- [About Account](https://platform.minimax.io/docs/faq/about-account.md): Find answers to common MiniMax account questions on billing, invoices, balance alerts, and resource management. +- [About APIs](https://platform.minimax.io/docs/faq/about-apis.md): Find answers to common questions about managing your MiniMax AI account. +- [Contact Us](https://platform.minimax.io/docs/faq/contact-us.md): This page provides MiniMax official contact channels, including email support, enabling you to quickly get technical assistance and business inquiries. +- [System Voice ID List](https://platform.minimax.io/docs/faq/system-voice-id.md): You can also obtain the latest system voice information through the [Get Voice API](/api-reference/voice-management-get) +- [Video Agent Template List](https://platform.minimax.io/docs/faq/video-agent-templates.md): This document lists all official MiniMax Video Agent templates with IDs, features, and usage examples. +- [Image Generation Guide](https://platform.minimax.io/docs/guides/image-generation.md): The Image Generation service provides two core capabilities: **Text-to-Image** and **Image-to-Image**. +- [Local Deployment Guide](https://platform.minimax.io/docs/guides/local-deploy.md): Deploy MiniMax-M2.7 locally using vLLM, SGLang (Linux GPU), or MLX (Mac Studio), with hardware-specific configuration guides. +- [Introduction to the Model Context Protocol (MCP)](https://platform.minimax.io/docs/guides/mcp-guide.md): This guide explains the Model Context Protocol (MCP) and its Python/JS tools for seamless multimodal AI integration. +- [Models](https://platform.minimax.io/docs/guides/models-intro.md): Overview of MiniMax AI models and their capabilities +- [Music Generation](https://platform.minimax.io/docs/guides/music-generation.md): Use the prompt parameter to define the music's style, mood, and scenario, and the lyrics parameter to provide the vocal content. This feature is ideal for quickly generating unique theme songs for videos, games, or applications. +- [Pay as You Go](https://platform.minimax.io/docs/guides/pricing-paygo.md): MiniMax Pay as You Go Pricing +- [Audio Subscription](https://platform.minimax.io/docs/guides/pricing-speech.md): MiniMax Audio Subscription Pricing +- [Token Plan](https://platform.minimax.io/docs/guides/pricing-token-plan.md): MiniMax Token Plan Subscription Pricing +- [Token Plan for Teams](https://platform.minimax.io/docs/guides/pricing-token-plan-team.md): How Token Plan seats, shared Credits, and pay-as-you-go resources work in Teams. +- [Video Packages](https://platform.minimax.io/docs/guides/pricing-video.md): MiniMax Video Packages Pricing +- [Privacy Policy](https://platform.minimax.io/docs/guides/privacy-policy.md) +- [Prerequisites](https://platform.minimax.io/docs/guides/quickstart-preparation.md): Before using the MiniMax API, you need to complete account registration and obtain an API Key. +- [Integrate via SDK](https://platform.minimax.io/docs/guides/quickstart-sdk.md): Use the Anthropic SDK to quickly integrate with the MiniMax API and start calling the MiniMax-M3 model. +- [Rate Limits](https://platform.minimax.io/docs/guides/rate-limits.md): Rate limits are restrictions that our API imposes on the number of times a user or client can access our services within a specified period of time. +- [Server Tools](https://platform.minimax.io/docs/guides/server-tools.md) +- [Async Long TTS Guide](https://platform.minimax.io/docs/guides/speech-t2a-async.md): MiniMax provides an asynchronous TTS for long-form audio synthesis tasks, with a maximum limit of 1M characters per request for text input. +- [Synchronous Text-to-Speech Guide (WebSocket)](https://platform.minimax.io/docs/guides/speech-t2a-websocket.md): Synchronous TTS allows real-time text-to-speech synthesis, handling up to 10,000 characters per request. +- [Voice Clone](https://platform.minimax.io/docs/guides/speech-voice-clone.md): MiniMax’s speech models provide robust voice cloning capabilities, allowing you to synthesize preview audio using cloned voices. +- [Terms of Service](https://platform.minimax.io/docs/guides/terms-of-service.md) +- [Chat Model](https://platform.minimax.io/docs/guides/text-chat.md): M2-her chat model, designed for role-playing, multi-turn conversations and dialogue scenarios. +- [Model Invocation](https://platform.minimax.io/docs/guides/text-generation.md): MiniMax LLMs, supporting multilingual programming, Agent workflows and complex task scenarios. +- [Aligning to What? Rethinking Agent Generalization in MiniMax M2](https://platform.minimax.io/docs/guides/text-m2-agent-generalization.md) +- [Why Did MiniMax M2 End Up as a Full Attention Model?](https://platform.minimax.io/docs/guides/text-m2-full-attention.md): MiniMax M2 End Up as a Full Attention Model +- [What makes good reasoning data](https://platform.minimax.io/docs/guides/text-m2-reasoning.md): MiniMax M2, ranks Top-1 among open-source models and Top-5 among all models +- [Tool Use & Interleaved Thinking](https://platform.minimax.io/docs/guides/text-m3-function-call.md): MiniMax-M3 is an Agentic Model with exceptional Tool Use capabilities. +- [Token Plan MCP Guide](https://platform.minimax.io/docs/guides/token-plan-mcp-guide.md): Token Plan MCP provides two exclusive tools: **web_search** and **understand_image**, helping developers quickly access information and understand image content during coding. +- [Transparency](https://platform.minimax.io/docs/guides/transparency.md) +- [Video Generation with Templates Guide](https://platform.minimax.io/docs/guides/video-agent.md): Video Agent generation service allows you to quickly create videos with a consistent style by filling predefined templates with assets such as images or text. +- [Video Generation](https://platform.minimax.io/docs/guides/video-generation.md): MiniMax's video model (MiniMax H3) enables efficient video content creation. +- [H3 Feature Highlights](https://platform.minimax.io/docs/guides/video-prompt.md): A gallery of representative examples showing MiniMax H3's three core capabilities: Native Multimodal Understanding & Generation, Precise Multimodal Editing & Control, and Production-Ready Content Creation Across Use Cases. +- [Product Pricing](https://platform.minimax.io/docs/pricing/overview.md): MiniMax offers two pricing categories — choose by usage scenario. +- [APIs](https://platform.minimax.io/docs/release-notes/apis.md): Track the latest MiniMax API updates to help developers build smarter, more seamless applications. +- [Models](https://platform.minimax.io/docs/release-notes/models.md): Stay updated on MiniMax's latest model releases across language, audio, video, image, and music. +- [Multi-Agent Cowork for Complex Tasks](https://platform.minimax.io/docs/solutions/eigent.md): In this tutorial, we showed how to integrate the MiniMax M2.1 model into Eigent, an open-source Cowork, to complete complex tasks. +- [Cookbook](https://platform.minimax.io/docs/solutions/index.md): Explore practical use cases and solutions for MiniMax API to quickly build AI applications. +- [Mini-Agent: Build Your First Intelligent Assistant](https://platform.minimax.io/docs/solutions/mini-agent.md): This tutorial will guide you through the core architecture of Mini-Agent and show you how to integrate with the MiniMax M2.1 model to build your own intelligent Agent. +- [OpenClaw](https://platform.minimax.io/docs/solutions/openclaw.md): In this tutorial, we'll show you how to use OpenClaw to connect MiniMax M3 to Telegram, creating your personal AI assistant that you can chat with anytime, anywhere. +- [Control Robot Arm with Conversation: Make Robots Understand You](https://platform.minimax.io/docs/solutions/robot-agent.md): This tutorial will guide you to use MiniMax LLM & MCP visual understanding to build an intelligent robot that can understand natural language instructions and perform complex robotic arm manipulation tasks. +- [Claude Code](https://platform.minimax.io/docs/token-plan/claude-code.md): Use the latest MiniMax M-series models for AI programming in Claude Code. +- [Codex](https://platform.minimax.io/docs/token-plan/codex.md): Use the latest MiniMax M-series models for AI programming in the Codex desktop app. +- [Cursor](https://platform.minimax.io/docs/token-plan/cursor.md): Use the latest MiniMax M-series models for AI programming in Cursor. +- [FAQs](https://platform.minimax.io/docs/token-plan/faq.md): Token Plan FAQs +- [Hermes Agent ☤](https://platform.minimax.io/docs/token-plan/hermes-agent.md): Use the latest MiniMax M-series models in Hermes Agent for autonomous AI-powered development. +- [Token Plan Overview](https://platform.minimax.io/docs/token-plan/intro.md): Token Plan subscription and usage overview +- [Web Search MCP](https://platform.minimax.io/docs/token-plan/mcp-guide.md): **Token Plan MCP** provides the **web_search** tool, helping developers quickly access information during coding. +- [Token Plan Migration Guide](https://platform.minimax.io/docs/token-plan/migration.md): How existing Token Plan subscriptions migrate to the M3-era plans, with quota protection and compensation top-ups for retired tiers. +- [Mini-Agent](https://platform.minimax.io/docs/token-plan/mini-agent.md): Mini-Agent is a minimalist yet professional project that demonstrates best practices for building Agents using MiniMax M3. The project is fully compatible with the Anthropic API and supports interleaved thinking, unlocking the model's powerful reasoning capabilities for long and complex tasks. +- [MiniMax CLI](https://platform.minimax.io/docs/token-plan/minimax-cli.md): [mmx-cli](https://github.com/MiniMax-AI/cli): one prompt to bring MiniMax into your AI agent +- [OpenClaw](https://platform.minimax.io/docs/token-plan/openclaw.md): Use the latest MiniMax M-series models for anything you can think of in OpenClaw. +- [Other Tools](https://platform.minimax.io/docs/token-plan/other-tools.md): Configure the latest MiniMax M-series models in any AI coding tool that supports custom OpenAI-compatible or Anthropic-compatible endpoints. +- [Referral Program](https://platform.minimax.io/docs/token-plan/promotion.md): Token Plan Co-builder Referral Program - Earn rewards by inviting friends! +- [M-series Usage Tips](https://platform.minimax.io/docs/token-plan/prompting-best-practices.md): Prompt patterns for using MiniMax Token Plan models effectively in coding, tool-use, agentic, and long-context workflows. +- [Quick Start](https://platform.minimax.io/docs/token-plan/quickstart.md): Quick guide to Token Plan subscription and integration +- [TRAE](https://platform.minimax.io/docs/token-plan/trae.md): Use the latest MiniMax M-series models for AI programming in TRAE. + +## OpenAPI Specs + +- [v2-video-generation](https://platform.minimax.io/docs/api-reference/video/generation/api/v2-video-generation.json) +- [openapi](https://platform.minimax.io/docs/zh/api-reference/openapi.json) +- [openapi-responses](https://platform.minimax.io/docs/api-reference/text/api/openapi-responses.json) +- [openapi-chat-openai](https://platform.minimax.io/docs/api-reference/text/api/openapi-chat-openai.json) +- [openapi-chat-anthropic](https://platform.minimax.io/docs/api-reference/text/api/openapi-chat-anthropic.json) +- [text-to-video](https://platform.minimax.io/docs/api-reference/video/generation/api/text-to-video.json) +- [subject-reference-to-video](https://platform.minimax.io/docs/api-reference/video/generation/api/subject-reference-to-video.json) +- [start-end-to-video](https://platform.minimax.io/docs/api-reference/video/generation/api/start-end-to-video.json) +- [image-to-video](https://platform.minimax.io/docs/api-reference/video/generation/api/image-to-video.json) +- [upload-prompt](https://platform.minimax.io/docs/api-reference/speech/voice-cloning/api/upload-prompt.json) +- [upload-file](https://platform.minimax.io/docs/api-reference/speech/voice-cloning/api/upload-file.json) +- [retrieve-model](https://platform.minimax.io/docs/api-reference/models/openai/api/retrieve-model.json) +- [list-models](https://platform.minimax.io/docs/api-reference/models/openai/api/list-models.json) +- [text-to-image](https://platform.minimax.io/docs/api-reference/image/generation/api/text-to-image.json) +- [image-to-image](https://platform.minimax.io/docs/api-reference/image/generation/api/image-to-image.json) +- [openapi-chat](https://platform.minimax.io/docs/api-reference/text/api/openapi-chat.json) + +## AsyncAPI Specs + +- [asyncapi](https://platform.minimax.io/docs/api-reference/speech/t2a/api/asyncapi.json) + +## Optional + +- [Developer Program](https://platform.minimax.io/contact-us) diff --git a/llmsdk_docs/minimax_m3/docs/list-models.md b/llmsdk_docs/minimax_m3/docs/list-models.md new file mode 100644 index 00000000..3549ab54 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/list-models.md @@ -0,0 +1,90 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimax.io/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# List Models + +> Returns a list of all available models compatible with OpenAI API specification. + + + +## OpenAPI + +````yaml api-reference/models/openai/api/list-models.json GET /v1/models +openapi: 3.1.0 +info: + title: MiniMax Models API + description: MiniMax models API compatible with OpenAI API specification. + version: 1.0.0 +servers: + - url: https://api.minimax.io +security: + - bearerAuth: [] +paths: + /v1/models: + get: + tags: + - Models + summary: List Models + description: >- + Returns a list of all available models. This endpoint is compatible with + OpenAI API specification. + operationId: listModels + responses: + '200': + description: A list of available models. + content: + application/json: + schema: + type: object + properties: + object: + type: string + description: Object type, always "list" + data: + type: array + description: Array of model objects + items: + type: object + properties: + id: + type: string + description: Model identifier + object: + type: string + description: Object type, always "model" + created: + type: integer + description: Unix timestamp when the model was created + owned_by: + type: string + description: Organization that owns the model + examples: + Default: + value: + object: list + data: + - id: MiniMax-M3 + object: model + created: 1780272000 + owned_by: minimax + - id: MiniMax-M2.7 + object: model + created: 1773799200 + owned_by: minimax + - id: MiniMax-M2.5 + object: model + created: 1770948000 + owned_by: minimax +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: |- + `HTTP: Bearer Auth` + - Security Scheme Type: http + - HTTP Authorization Scheme: Bearer API_key, used for account verification, can be viewed in [Account Management > API Keys](https://platform.minimax.io/user-center/basic-information/interface-key) + +```` \ No newline at end of file diff --git a/llmsdk_docs/minimax_m3/docs/models-intro.md b/llmsdk_docs/minimax_m3/docs/models-intro.md new file mode 100644 index 00000000..241bdcf0 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/models-intro.md @@ -0,0 +1,73 @@ +> ## Documentation Index +> +> Fetch the complete documentation index at: [/docs/llms.txt](https://platform.minimax.io/docs/llms.txt) +> +> Use this file to discover all available pages before exploring further. + +[Skip to main content](https://platform.minimax.io/docs/guides/models-intro#content-area) + +### [​](https://platform.minimax.io/docs/guides/models-intro\#language) Language + +| **Models** | **Description** | **Features** | +| --- | --- | --- | +| [MiniMax-M3](https://platform.minimax.io/docs/api-reference/text-anthropic-api) | Frontier multimodal coding model with 1M context window | • Multimodal
• 1M context window
• Frontier coding | +| [MiniMax-M2.7](https://platform.minimax.io/docs/api-reference/text-anthropic-api) | Beginning the journey of recursive self-improvement | • Top real-world engineering
• Professional office delivery
• Character-rich interaction | +| [MiniMax-M2.7-highspeed](https://platform.minimax.io/docs/api-reference/text-anthropic-api) | Same performance as M2.7
• Significantly faster inference | • Polyglot code mastery
• Precision code refactoring
• Low latency | + +Legacy Models + +| **Models** | **Description** | **Features** | +| --- | --- | --- | +| [MiniMax-M2.5](https://platform.minimax.io/docs/api-reference/text-anthropic-api) | • Optimized for code generation and refactoring | • Peak Performance. Ultimate Value. Master the Complex. | +| [MiniMax-M2.5-highspeed](https://platform.minimax.io/docs/api-reference/text-anthropic-api) | • Same performance as M2.5
• Significantly faster inference | • Polyglot code mastery
• Precision code refactoring
• Low latency | +| [MiniMax-M2.1](https://platform.minimax.io/docs/api-reference/text-anthropic-api) | • 230B total parameters with 10B activated per inference
• Optimized for code generation and refactoring | • Polyglot code mastery
• Precision code refactoring
• Enhanced reasoning | +| [MiniMax-M2.1-highspeed](https://platform.minimax.io/docs/api-reference/text-anthropic-api) | • Same performance as M2.1
• Significantly faster inference | • Polyglot code mastery
• Precision code refactoring
• Low latency | +| [MiniMax-M2](https://platform.minimax.io/docs/api-reference/text-anthropic-api) | • Context Length: 200k tokens
• Maximum Output: 128k tokens (including CoT) | • Agentic capabilities
• Function calling
• Advanced reasoning
• Real-time streaming | + +### [​](https://platform.minimax.io/docs/guides/models-intro\#video) Video + +| **Models** | **Description** | **Res.& Dur.** | **FPS** | +| --- | --- | --- | --- | +| [MiniMax H3](https://platform.minimax.io/docs/api-reference/video-generation-v2-create) | Next-gen open general-purpose multimodal video model
• Text-to-Video / Image-to-Video / First–Last Frame / Multimodal reference | • 768P / 2K
• 4–15s | 24 fps | + +Legacy Models + +| **Models** | **Description** | **Res.& Dur.** | **FPS** | +| --- | --- | --- | --- | +| [MiniMax Hailuo 2.3](https://platform.minimax.io/docs/api-reference/video-generation-t2v) | • Text to Video & Image to Video
• SOTA instruction following
• Extreme physics mastery | • 1080p 6s
• 768p 6s, 10s | 24 fps | +| [MiniMax Hailuo 2.3Fast](https://platform.minimax.io/docs/api-reference/video-generation-i2v) | • Image to Video
• Extreme physics mastery
• Value and Efficiency | • 1080p 6s
• 768p 6s, 10s | 24 fps | +| [MiniMax Hailuo 02](https://platform.minimax.io/docs/api-reference/video-generation-t2v) | • Text to Video & Image to Video
• SOTA instruction following
• Extreme physics mastery | • 1080p 6s
• 768p 6s, 10s
• 512p 6s, 10s | 24 fps | + +### [​](https://platform.minimax.io/docs/guides/models-intro\#audio) Audio + +| **Models** | **Description** | **Features** | +| --- | --- | --- | +| [speech-2.8-hd](https://platform.minimax.io/docs/api-reference/speech-t2a-http) | • Ultra-realistic quality featuring sound tags | • 40 languages supported
• 7 emotions supported
• specified languages and dialects supported | +| [speech-2.8-turbo](https://platform.minimax.io/docs/api-reference/speech-t2a-http) | • Seamless speed meets natural flow | • 40 languages supported
• 7 emotions supported
• specified languages and dialects supported | + +Legacy Models + +| **Models** | **Description** | **Features** | +| --- | --- | --- | +| [speech-2.6-hd](https://platform.minimax.io/docs/api-reference/speech-t2a-http) | • Ultimate Similarity
• Ultra-High Quality | • 40 languages supported
• 7 emotions supported
• specified languages and dialects supported | +| [speech-2.6-turbo](https://platform.minimax.io/docs/api-reference/speech-t2a-http) | • Ultimate Value
• Low latency | • 40 languages supported
• 7 emotions supported
• specified languages and dialects supported | +| [speech-02-hd](https://platform.minimax.io/docs/api-reference/speech-t2a-http) | • Stronger replication similarity
• High quality voice generation | • 24 languages supported
• 7 emotions supported
• specified languages and dialects supported | +| [speech-02-turbo](https://platform.minimax.io/docs/api-reference/speech-t2a-http) | • Superior rhythm and stability
• Low latency | • 24 languages supported
• 7 emotions supported
• specified languages and dialects supported | + +### [​](https://platform.minimax.io/docs/guides/models-intro\#music) Music + +| **Models** | **Description** | **Features** | +| --- | --- | --- | +| [music-3.0](https://platform.minimax.io/docs/api-reference/music-generation) | • New Music Generation Capabilities | • Intent Understood
• Sound Elevated
• Vocals Humanized | +| [music-2.6](https://platform.minimax.io/docs/api-reference/music-generation) | • Cover Reborn. Bass Redefined. | • Cover Reborn. Bass Redefined. | +| [music-cover](https://platform.minimax.io/docs/api-reference/music-generation) | • Generate cover versions from reference audio | • One-step cover generation
• Two-step cover with lyrics modification
• Style transfer
• Auto lyrics extraction | + +Legacy Models + +| **Models** | **Description** | **Features** | +| --- | --- | --- | +| [music-2.0](https://platform.minimax.io/docs/api-reference/music-generation) | • Text to Music
• Enhanced musicality
• Natural vocals and smooth melodies | • Human-like performance
• Riche emotional expression
• Enhanced tone control | + +[Prerequisites](https://platform.minimax.io/docs/guides/quickstart-preparation) + +⌘I \ No newline at end of file diff --git a/llmsdk_docs/minimax_m3/docs/pricing-paygo.md b/llmsdk_docs/minimax_m3/docs/pricing-paygo.md new file mode 100644 index 00000000..06e5985a --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/pricing-paygo.md @@ -0,0 +1,173 @@ +> ## Documentation Index +> +> Fetch the complete documentation index at: [/docs/llms.txt](https://platform.minimax.io/docs/llms.txt) +> +> Use this file to discover all available pages before exploring further. + +[Skip to main content](https://platform.minimax.io/docs/guides/pricing-paygo#content-area) + +Pay-as-you-go uses standard Open Platform API Keys and consumes your account balance by actual usage. Credits are a separate prepaid balance used through a Subscription Key with the same resource coverage as Token Plan. For Credits pricing and usage behavior, see [Token Plan pricing](https://platform.minimax.io/docs/guides/pricing-token-plan). + +## [​](https://platform.minimax.io/docs/guides/pricing-paygo\#llm) LLM + +[Recharge Now](https://platform.minimax.io/user-center/payment/balance) + +- Standard + +- Priority\* + + +| Model | Input | Output | Prompt caching Read | +| --- | --- | --- | --- | +| **MiniMax-M3**
≤ 512k input tokens Permanent 50% off | ~~$0.60~~ $0.30 / M tokens | ~~$2.40~~ $1.20 / M tokens | ~~$0.12~~ $0.06 / M tokens | +| **MiniMax-M3**
\> 512k input tokens\* Permanent 50% off | ~~$1.20~~ $0.60 / M tokens | ~~$4.80~~ $2.40 / M tokens | ~~$0.24~~ $0.12 / M tokens | + +| Model | Input | Output | Prompt caching Read | +| --- | --- | --- | --- | +| **MiniMax-M3**
≤ 512k input tokens Permanent 50% off | ~~$0.90~~ $0.45 / M tokens | ~~$3.60~~ $1.80 / M tokens | ~~$0.18~~ $0.09 / M tokens | +| **MiniMax-M3**
\> 512k input tokens Permanent 50% off | ~~$1.80~~ $0.90 / M tokens | ~~$7.20~~ $3.60 / M tokens | ~~$0.36~~ $0.18 / M tokens | + +\\* Priority provides priority admission for faster response times and improved request reliability. Set `service_tier` to `priority` to enable it. Pricing is 1.5x standard. + +| Model | Input | Output | Prompt caching Read | Prompt caching Write | +| --- | --- | --- | --- | --- | +| **MiniMax-M2.7** | $0.3 / M tokens | $1.2 / M tokens | $0.06 / M tokens | $0.375 / M tokens | +| **MiniMax-M2.7-highspeed** | $0.6 / M tokens | $2.4 / M tokens | $0.06 / M tokens | $0.375 / M tokens | + +Legacy Models + +| Model | Input | Output | Prompt caching Read | Prompt caching Write | +| --- | --- | --- | --- | --- | +| **MiniMax-M2.5** | $0.3 / M tokens | $1.2 / M tokens | $0.03 / M tokens | $0.375 / M tokens | +| **MiniMax-M2.5-highspeed** | $0.6 / M tokens | $2.4 / M tokens | $0.03 / M tokens | $0.375 / M tokens | +| **MiniMax-M2.1** | $0.3 / M tokens | $1.2 / M tokens | $0.03 / M tokens | $0.375 / M tokens | +| **MiniMax-M2.1-highspeed** | $0.6 / M tokens | $2.4 / M tokens | $0.03 / M tokens | $0.375 / M tokens | +| **MiniMax-M2** | $0.3 / M tokens | $1.2 / M tokens | $0.03 / M tokens | $0.375 / M tokens | + +Note: + +1. The billing item is token count; the token-to-character ratio varies slightly depending on the usage scenario, subject to actual consumption +2. Token to English word ratio (estimate): approximately 750 English words consume 1000 tokens + +## [​](https://platform.minimax.io/docs/guides/pricing-paygo\#audio) Audio + +[Recharge Now](https://platform.minimax.io/user-center/payment/balance) + +| API | Model | Price | +| --- | --- | --- | +| **T2A** | speech-2.8-turbo | $60/M characters | +| **T2A** | speech-2.8-hd | $100/M characters | +| **Rapid Voice Cloning** | All Models | $1.5 per voice | +| **Voice Design** | All Models | $3 per voice | + +Legacy Models + +| API | Model | Price | +| --- | --- | --- | +| **T2A** | speech-2.6-turbo / speech-02-turbo | $60/M characters | +| **T2A** | speech-2.6-hd / speech-02-hd | $100/M characters | + +## [​](https://platform.minimax.io/docs/guides/pricing-paygo\#video) Video + +[Recharge Now](https://platform.minimax.io/user-center/payment/balance)**Video Generation - Output Pricing** + +| **Model / API** | **Resolution** | **Billing Rules** | **List Price** | +| --- | --- | --- | --- | +| MiniMax-H3 | 2K | Billed per second | $0.13 / second | +| MiniMax-H3 | 768P | Billed per second | $0.08 / second | + +**Video Generation - Input Material Pricing** + +| **Model / API** | **Material Type** | **Billing Rules** | +| --- | --- | --- | +| MiniMax-H3 | Audio | Free | +| MiniMax-H3 | Image | First **5 images** free; **$0.04 per additional image** | +| MiniMax-H3 | Video | Billed by input video duration and output video resolution: **2K $0.13/sec**, **768P $0.08/sec** | + +**Video Regeneration - Output Pricing**Regenerate a previously produced 768P video into 2K, billed per second of the regenerated output. + +| **Model / API** | **Resolution** | **Billing Rules** | **List Price** | +| --- | --- | --- | --- | +| MiniMax-H3-Regeneration | 768P → 2K | Billed per second of the regenerated output | $0.05 / second | + +**Video Regeneration - Input Material Pricing**The input materials used in the original 768P generation task will be billed again. + +| **Model / API** | **Material Type** | **Billing Rules** | +| --- | --- | --- | +| MiniMax-H3-Regeneration | Audio | Free | +| MiniMax-H3-Regeneration | Image | First **5 images** free; **$0.025 per additional image** | +| MiniMax-H3-Regeneration | Video | Billed by input video duration from the original 768P task: **$0.05 / second** | + +**H3-Context-IR Task Pricing** + +| **Model / API** | **Input Price** | **Output Price** | +| --- | --- | --- | +| MiniMax-H3-Context-IR | $0.90 / M tokens | $3.60 / M tokens | + +Legacy Models + +| Model | Price | +| --- | --- | +| MiniMax-Hailuo-2.3-Fast | $0.19 per 768P, 6s video | +| MiniMax-Hailuo-2.3-Fast | $0.32 per 768P, 10s video | +| MiniMax-Hailuo-2.3-Fast | $0.33 per 1080P, 6s video | +| MiniMax-Hailuo-2.3 | $0.28 per 768P, 6s video | +| MiniMax-Hailuo-2.3 | $0.56 per 768P, 10s video | +| MiniMax-Hailuo-2.3 | $0.49 per 1080P, 6s video | +| MiniMax-Hailuo-02 | $0.28 per 768P, 6s video | +| MiniMax-Hailuo-02 | $0.56 per 768P, 10s video | +| MiniMax-Hailuo-02 | $0.49 per 1080P, 6s video | +| MiniMax-Hailuo-02 | $0.10 per 512P, 6s video | +| MiniMax-Hailuo-02 | $0.15 per 512P, 10s video | + +## [​](https://platform.minimax.io/docs/guides/pricing-paygo\#music) Music + +[Recharge Now](https://platform.minimax.io/user-center/payment/balance) + +| Model | Description | Price | +| --- | --- | --- | +| Music-3.0-free | RPM = 3 | Free | +| Music-3.0 | RPM = 120, contact sales to increase | $0.15/up-to-5 minutes music | +| Music-2.6-free | RPM = 3 | Free | +| Music-2.6 | RPM = 120, contact sales to increase | $0.15/up-to-5 minutes music | +| Lyrics Generation | Lyrics generation/editing | $0.01/per song | + +Legacy Models + +| Model | Description | Price | +| --- | --- | --- | +| Music-2.5+ | Instrumental unlocked, break through style boundaries | $0.15/up-to-5 minutes music | +| Music-2.5 | Direct the detail, define the real | $0.15/up-to-5 minutes music | +| Music-2.0 | Enhanced musical expression | $0.03/up-to-5 minutes music | + +## [​](https://platform.minimax.io/docs/guides/pricing-paygo\#image) Image + +[Recharge Now](https://platform.minimax.io/user-center/payment/balance) + +| Model | Price | +| --- | --- | +| image-01 | $0.0035 per image | + +## [​](https://platform.minimax.io/docs/guides/pricing-paygo\#mcp) MCP + +[Recharge Now](https://platform.minimax.io/user-center/payment/balance) + +| Model | Input Price | +| --- | --- | +| **API-vlm** | $0.01 / request | + +When API-vlm is called through Token Plan, usage deducts from the included Token Plan quota according to its pay-as-you-go price. If the included quota is exhausted and purchased Credits are available, additional usage can be automatically covered by purchased Credits. + +🔔 **Pricing Update Notice** — Effective July 22, 2026, the API-vlm price will be adjusted to $0.01 per call. Accordingly, the token quota deducted per API-vlm call under Token Plan subscriptions will decrease, allowing the same plan to support more calls. API endpoints and model capabilities remain unchanged — no code changes required. + +## [​](https://platform.minimax.io/docs/guides/pricing-paygo\#server-tools) Server Tools + +[Recharge Now](https://platform.minimax.io/user-center/payment/balance) + +| Server Tool | Description | Price | +| --- | --- | --- | +| **web\_search** | Web search; the model runs the search on the server and answers based on the results. See [Server Tools](https://platform.minimax.io/docs/guides/server-tools). | $0.01 / request | + +[Overview](https://platform.minimax.io/docs/pricing/overview) [Audio Subscription](https://platform.minimax.io/docs/guides/pricing-speech) + +⌘I \ No newline at end of file diff --git a/llmsdk_docs/minimax_m3/docs/pricing-token-plan.md b/llmsdk_docs/minimax_m3/docs/pricing-token-plan.md new file mode 100644 index 00000000..ebd28716 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/pricing-token-plan.md @@ -0,0 +1,40 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimax.io/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# Token Plan + +> MiniMax Token Plan Subscription Pricing + +Token Plan subscriptions provide a monthly usage quota and access to eligible resources through the Subscription Key. Usage is shown as a usage bar in the console. Supported text, image, speech, and music resources share one quota. + +Prepaid Credits packages are listed below. For team usage, see [Token Plan for Teams](/docs/guides/pricing-token-plan-team). + +## Monthly + +[Subscribe Now](https://platform.minimax.io/subscribe/token-plan) + +| | **Plus** | **Max** | **Ultra** | +| :---------------- | :-------------------------------- | :------------------------------------------- | :------------------------------------------ | +| **Price** | **$20 /month** | **$50 /month** | **$120 /month** | +| **Best for** | Personal projects and prototyping | Daily coding with agents and multimodal work | Heavy Agent workflows and extended sessions | +| **Quota windows** | 5-hour rolling and weekly windows | 5-hour rolling and weekly windows | 5-hour rolling and weekly windows | +| **Agent usage** | 3-4 agents | 4-5 agents | 6-7 agents | + +Available model coverage includes the full MiniMax lineup (M3 / M2.7 / image / speech / music). A small number of special models (MiniMax H3, voice design, rapid voice cloning, etc.) are not currently supported. Credits usage is unrestricted. + +## Credits Packages + +[Recharge Credits](https://platform.minimax.io/user-center/payment/credits) + +Credits packages are priced at **1,000 credits = $1**. Usage paid with Credits deducts the equivalent Credits amount at the resource's pay-as-you-go list price. + +| Price | Credits received | +| :-------- | :------------------ | +| **$5** | **5,000 credits** | +| **$25** | **25,000 credits** | +| **$100** | **100,000 credits** | + +Validity: **365 days** from each purchase date. + +Credits cover the same resources as Token Plan. When both Token Plan quota and Credits can cover usage, Token Plan quota is used first; Credits cover eligible overflow. diff --git a/llmsdk_docs/minimax_m3/docs/prompt-caching.md b/llmsdk_docs/minimax_m3/docs/prompt-caching.md new file mode 100644 index 00000000..84d56de9 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/prompt-caching.md @@ -0,0 +1,250 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimax.io/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# Prompt Caching + +> Prompt caching effectively reduces latency and costs. + +# Features + +* **Automatic Caching**: Passive caching that automatically identifies repeated context content without changing API call methods (*In contrast, the caching mode that requires explicitly setting parameters in the Anthropic API is called "Explicit Prompt Caching", see [Explicit Prompt Caching (Anthropic API)](/docs/api-reference/anthropic-api-compatible-cache)*) +* **Cost Reduction**: Input tokens that hit the cache are billed at a lower price, significantly saving costs +* **Speed Improvement**: Reduces processing time for repeated content, accelerating model response + +This mechanism is particularly suitable for the following scenarios: + +* System prompt reuse: In multi-turn conversations, system prompts typically remain unchanged +* Fixed tool lists: Tools used in a category of tasks are often consistent +* Multi-turn conversation history: In complex conversations, historical messages often contain a lot of repeated information + +Scenarios that meet the above conditions can effectively save token consumption and speed up response times using the caching mechanism. + +# Code Examples + + + + **Install SDK** + + ```bash theme={null} theme={null} + pip install anthropic + ``` + + **Environment Variable Setup** + + ```bash theme={null} theme={null} + export ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic + export ANTHROPIC_API_KEY=${YOUR_API_KEY} + ``` + + **First Request - Establish Cache** + + ```python theme={null} theme={null} + import anthropic + + client = anthropic.Anthropic() + + response1 = client.messages.create( + model="MiniMax-M3", + system="You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n", + messages=[ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "" + } + ] + }, + ], + max_tokens=10240, + ) + + print("First request result:") + for block in response1.content: + if block.type == "thinking": + print(f"Thinking:\n{block.thinking}\n") + elif block.type == "text": + print(f"Output:\n{block.text}\n") + print(f"Input Tokens: {response1.usage.input_tokens}") + print(f"Output Tokens: {response1.usage.output_tokens}") + print(f"Cache Hit Tokens: {response1.usage.cache_read_input_tokens}") + + ``` + + **Second Request - Reuse Cache** + + ```python theme={null} theme={null} + response2 = client.messages.create( + model="MiniMax-M3", + system="You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n", + messages=[ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "" + } + ] + }, + ], + max_tokens=10240, + ) + + print("\nSecond request result:") + for block in response2.content: + if block.type == "thinking": + print(f"Thinking:\n{block.thinking}\n") + elif block.type == "text": + print(f"Output:\n{block.text}\n") + print(f"Input Tokens: {response2.usage.input_tokens}") + print(f"Output Tokens: {response2.usage.output_tokens}") + print(f"Cache Hit Tokens: {response2.usage.cache_read_input_tokens}") + ``` + + **Response includes context cache token usage information:** + + ```json theme={null} theme={null} + { + "usage": { + "input_tokens": 108, + "output_tokens": 91, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 14813 + } + } + ``` + + + + **Install SDK** + + ```bash theme={null} theme={null} + pip install openai + ``` + + **Environment Variable Setup** + + ```bash theme={null} theme={null} + export OPENAI_BASE_URL=https://api.minimax.io/v1 + export OPENAI_API_KEY=${YOUR_API_KEY} + ``` + + **First Request - Establish Cache** + + ```python theme={null} theme={null} + from openai import OpenAI + + client = OpenAI() + + response1 = client.chat.completions.create( + model="MiniMax-M3", + messages=[ + {"role": "system", "content": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"}, + {"role": "user", "content": ""}, + ], + # Set reasoning_split=True to separate thinking content into reasoning_details field + extra_body={"reasoning_split": True}, + ) + + print("First request result:") + print(f"Response: {response1.choices[0].message.content}") + print(f"Total Tokens: {response1.usage.total_tokens}") + print(f"Cached Tokens: {response1.usage.prompt_tokens_details.cached_tokens if hasattr(response1.usage, 'prompt_tokens_details') else 0}") + + ``` + + **Second Request - Reuse Cache** + + ```python theme={null} theme={null} + response2 = client.chat.completions.create( + model="MiniMax-M3", + messages=[ + {"role": "system", "content": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"}, + {"role": "user", "content": ""}, + ], + # Set reasoning_split=True to separate thinking content into reasoning_details field + extra_body={"reasoning_split": True}, + ) + + print("\nSecond request result:") + print(f"Response: {response2.choices[0].message.content}") + print(f"Total Tokens: {response2.usage.total_tokens}") + print(f"Cached Tokens: {response2.usage.prompt_tokens_details.cached_tokens if hasattr(response2.usage, 'prompt_tokens_details') else 0}") + ``` + + **Response includes context cache token usage information:** + + ```json theme={null} theme={null} + { + "usage": { + "prompt_tokens": 1200, + "completion_tokens": 300, + "total_tokens": 1500, + "prompt_tokens_details": { + "cached_tokens": 800 + } + } + } + ``` + + + +# Important Notes + +* Caching applies to API calls with 512 or more input tokens +* Caching uses prefix matching, constructed in the order of "tool list → system prompts → user messages". Changes to any module's content may affect caching effectiveness + +# Best Practices + +* Place static or repeated content (including tool list, system prompts, user messages) at the beginning of the conversation, and put dynamic user information at the end of the conversation to maximize cache utilization +* Monitor cache performance through the usage tokens returned by the API, and regularly analyze to optimize your usage strategy + +# Pricing + +Prompt caching uses differentiated pricing: + +* Cache hit tokens: Billed at discounted price +* New input tokens: Billed at standard input price +* Output tokens: Billed at standard output price + +> See the [Pay as You Go pricing](/docs/guides/pricing-paygo) page for details. + +Pricing example: + +``` +Assuming the MiniMax-M3 standard price for input ≤512k tokens: input is $0.60/1M tokens, output is $2.40/1M tokens, and cache hit is $0.12/1M tokens: + +Single request token usage details: +- Total input tokens: 50000 +- Cache hit tokens: 45000 +- New input content tokens: 5000 +- Output tokens: 1000 + +Billing calculation: +- New input content cost: 5000 × 0.60/1000000 = $0.003 +- Cache cost: 45000 × 0.12/1000000 = $0.0054 +- Output cost: 1000 × 2.40/1000000 = $0.0024 +- Total cost: 0.003 + 0.0054 + 0.0024 = $0.0108 + +Compared to no caching (50000 × 0.60/1000000 + 1000 × 2.40/1000000 = $0.0324), saves about 66.7% +``` + +For MiniMax-M3, long-context pricing applies when input tokens are greater than 512k, including cache-hit tokens. + +# Further Reading + + + + + +# Cache Comparison + +| | Prompt Caching (Passive) | Explicit Prompt Caching (Anthropic API) | +| :--------------- | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | +| Usage | Automatically identifies and caches repeated content | Explicitly set cache\_control in API | +| Billing | Cache hit tokens billed at discounted price
No additional charge for cache writes | Cache hit tokens billed at discounted price
First-time cache writes incur additional charges | +| Expiration | Expiration time automatically adjusted based on system load | 5-minute expiration, automatically renewed with continued use | +| Supported Models | MiniMax-M3
MiniMax-M2.7 series
MiniMax-M2.5 series
MiniMax-M2.1 series | MiniMax-M2.7 series
MiniMax-M2.5 series
MiniMax-M2.1 series
MiniMax-M2 series | diff --git a/llmsdk_docs/minimax_m3/docs/responses-create.md b/llmsdk_docs/minimax_m3/docs/responses-create.md new file mode 100644 index 00000000..7f7cff48 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/responses-create.md @@ -0,0 +1,659 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimax.io/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# Create Response + +> Call MiniMax models via the OpenAI Responses API compatible main endpoint. Generates model replies, supports streaming and non-streaming. + +## Reasoning Control + +For `MiniMax-M3`, the `reasoning` field controls whether the response can include reasoning output. + +* If `reasoning` is omitted, reasoning is disabled by default and the response does not include an output item with `type: "reasoning"`. +* `reasoning: {"effort": "none"}` is the default behavior and disables reasoning output for `MiniMax-M3`. +* Values `minimal`, `low`, `medium`, and `high` are accepted for compatibility and enable reasoning output, but they do not tune MiniMax-M3's reasoning depth. +* For M2.x models, reasoning cannot be disabled; `reasoning: {"effort": "none"}` is accepted but reasoning remains on. + +```json theme={null} +{ + "model": "MiniMax-M3", + "input": "Which is larger, 9.11 or 9.9?" +} +``` + +```json theme={null} +{ + "model": "MiniMax-M3", + "input": "Which is larger, 9.11 or 9.9?", + "reasoning": { + "effort": "minimal" + } +} +``` + + +## OpenAPI + +````yaml api-reference/text/api/openapi-responses.json POST /v1/responses +openapi: 3.1.0 +info: + title: MiniMax Responses API + description: >- + MiniMax OpenAI Responses API compatible endpoints, supporting chat + generation and token estimation + license: + name: MIT + version: 1.0.0 +servers: + - url: https://api.minimax.io +security: + - bearerAuth: [] +paths: + /v1/responses: + post: + tags: + - Responses + summary: Create Response + operationId: createResponse + parameters: + - name: Content-Type + in: header + required: true + description: Media type of the request body. Must be set to `application/json` + schema: + type: string + enum: + - application/json + default: application/json + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateResponseReq' + examples: + SimpleText: + summary: Simple text input + value: + model: MiniMax-M3 + input: Hello! + ConversationHistory: + summary: Full conversation history + value: + model: MiniMax-M3 + instructions: You are a technical writing assistant. + input: + - role: user + content: >- + We are building a social product for overseas users. I + prefer Go + React Native + PostgreSQL. What do you + think? + Streaming: + summary: Streaming output + value: + model: MiniMax-M3 + input: Hello! + stream: true + FunctionCall: + summary: Function call + value: + model: MiniMax-M3 + input: What is the weather in Boston today? + tools: + - type: function + name: get_current_weather + description: Get the current weather in a given location + parameters: + type: object + properties: + location: + type: string + description: The city and state, e.g. San Francisco, CA + unit: + type: string + enum: + - celsius + - fahrenheit + required: + - location + - unit + MultiTurnFunctionCall: + summary: Multi-turn function call + value: + model: MiniMax-M3 + input: + - type: message + role: user + content: What is the weather in Beijing? + - type: function_call + call_id: call_abc123 + name: get_weather + arguments: '{"city":"Beijing"}' + - type: function_call_output + call_id: call_abc123 + output: Sunny, 22°C + tools: + - type: function + name: get_weather + description: Get current weather for a city + parameters: + type: object + properties: + city: + type: string + required: + - city + required: true + responses: + '200': + description: Successful response + content: + application/json: + schema: + $ref: '#/components/schemas/CreateResponseResp' + examples: + Response: + value: + id: abc123 + object: response + created_at: 1764000000 + model: MiniMax-M3 + status: completed + output: + - id: abc123_msg + type: message + status: completed + role: assistant + content: + - type: output_text + text: Hello! I'm MiniMax. How can I help you today? + annotations: [] + output_text: Hello! I'm MiniMax. How can I help you today? + usage: + input_tokens: 8 + input_tokens_details: + cached_tokens: 0 + output_tokens: 14 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 22 + parallel_tool_calls: true + store: false + truncation: disabled +components: + schemas: + CreateResponseReq: + type: object + required: + - model + - input + properties: + model: + type: string + description: Model name to invoke, e.g. `MiniMax-M3` + example: MiniMax-M3 + service_tier: + type: string + description: >- + Service tier for request admission. Supported values are `standard` + and `priority`. If omitted, the request uses the `standard` tier. + The `priority` [price](/guides/pricing-paygo) is 1.5 times the + `standard` price and ensures priority admission so the request is + processed ahead of other requests, leading to faster responses and + fewer failures. + enum: + - standard + - priority + default: standard + input: + description: >- + Conversation content. Supports either a simple text or a full + conversation history array + oneOf: + - type: string + description: Simple text input + - type: array + description: Full conversation history + items: + $ref: '#/components/schemas/InputItem' + instructions: + type: string + description: System instructions + max_output_tokens: + type: integer + description: Maximum output token count + temperature: + type: number + format: float + description: Sampling temperature, range (0, 1] + default: 1 + minimum: 0 + maximum: 1 + top_p: + type: number + format: float + description: Nucleus sampling, range (0, 1] + default: 0.95 + minimum: 0 + maximum: 1 + stream: + type: boolean + description: Set to `true` to enable SSE streaming response + default: false + tools: + type: array + description: Tool list + items: + $ref: '#/components/schemas/Tool' + tool_choice: + type: string + enum: + - none + - auto + description: >- + Tool selection strategy: `none` means no tool will be called; `auto` + lets the model decide whether to call tools + metadata: + type: object + description: Request metadata. Both keys and values are strings + additionalProperties: + type: string + prompt_cache_key: + type: string + description: Prompt cache routing identifier + text: + type: object + description: Output format control + properties: + format: + type: object + properties: + type: + type: string + enum: + - text + default: text + description: Output format type + reasoning: + type: object + description: >- + Reasoning control. For MiniMax-M3, the default is `none`, which + disables reasoning. Set `effort` to a non-`none` value (`minimal`, + `low`, `medium`, or `high`) to enable Adaptive Thinking, but this + does not tune MiniMax-M3's reasoning depth. For M2.x models, + reasoning cannot be disabled. + properties: + effort: + type: string + enum: + - minimal + - low + - medium + - high + - none + default: none + required: [] + CreateResponseResp: + type: object + required: + - id + - object + - created_at + - model + - status + - output + properties: + id: + type: string + description: Response ID + example: abc123 + object: + type: string + enum: + - response + description: Object type, always `response` + created_at: + type: integer + description: Response creation time (Unix seconds) + model: + type: string + description: Actual model that processed the request + status: + type: string + enum: + - completed + - incomplete + - failed + description: Response status + output: + type: array + description: Model output list + items: + $ref: '#/components/schemas/OutputItem' + output_text: + type: string + nullable: true + description: Convenience field. Concatenation of all text outputs + usage: + $ref: '#/components/schemas/Usage' + error: + type: object + nullable: true + description: Error info, only returned when `status=failed` + properties: + code: + type: string + description: Error code + message: + type: string + description: Human-readable error description + incomplete_details: + type: object + nullable: true + description: Reason for incompletion, only returned when `status=incomplete` + properties: + reason: + type: string + enum: + - max_output_tokens + - content_filter + parallel_tool_calls: + type: boolean + description: Whether parallel tool calls are supported + store: + type: boolean + description: Whether the response is persisted + truncation: + type: string + enum: + - disabled + description: Context truncation strategy + InputItem: + type: object + description: >- + Conversation history item. The `type` field determines the shape: + `message` (default) / `function_call` / `function_call_output` / + `reasoning` + properties: + type: + type: string + enum: + - message + - function_call + - function_call_output + - reasoning + default: message + description: Item type + role: + type: string + enum: + - user + - assistant + - system + - developer + - tool + description: Message role (only when `type` is `message`) + content: + description: >- + Message content; string or multimodal parts array (only when `type` + is `message`) + oneOf: + - type: string + - type: array + items: + $ref: '#/components/schemas/ContentPart' + call_id: + type: string + description: >- + Tool call ID (only when `type` is `function_call` or + `function_call_output`) + name: + type: string + description: Function name (only when `type` is `function_call`) + arguments: + type: string + description: >- + Function arguments as a JSON string (only when `type` is + `function_call`) + output: + description: Tool return result (only when `type` is `function_call_output`) + oneOf: + - type: string + - type: array + items: + $ref: '#/components/schemas/ContentPart' + summary: + type: array + description: Reasoning segment array (only when `type` is `reasoning`) + items: + type: object + properties: + type: + type: string + enum: + - summary_text + text: + type: string + description: Reasoning text + Tool: + type: object + required: + - type + - name + properties: + type: + type: string + enum: + - function + description: Tool type + name: + type: string + description: Function name + description: + type: string + description: Function description, helps the model decide when to call it + parameters: + type: object + description: Function parameter definition in JSON Schema format + OutputItem: + oneOf: + - title: Message + type: object + description: Assistant reply + properties: + id: + type: string + description: '`_msg` format' + type: + type: string + enum: + - message + status: + type: string + enum: + - completed + role: + type: string + enum: + - assistant + content: + type: array + items: + type: object + properties: + type: + type: string + enum: + - output_text + text: + type: string + description: Text generated by the model + annotations: + type: array + description: Reference annotations + - title: Reasoning + type: object + description: Reasoning output (only returned when reasoning is enabled) + properties: + id: + type: string + description: '`_rs` format' + type: + type: string + enum: + - reasoning + status: + type: string + enum: + - completed + summary: + type: array + description: Reasoning summary + content: + type: array + items: + type: object + properties: + type: + type: string + enum: + - reasoning_text + text: + type: string + description: Reasoning text + - title: Function Call + type: object + description: Function call + properties: + id: + type: string + description: '`_fc_` format' + type: + type: string + enum: + - function_call + status: + type: string + enum: + - completed + call_id: + type: string + description: Tool call ID, used to correlate with `function_call_output` + name: + type: string + description: Function name + arguments: + type: string + description: Function arguments as a JSON string + Usage: + type: object + properties: + input_tokens: + type: integer + description: Input token count + input_tokens_details: + type: object + properties: + cached_tokens: + type: integer + description: Prompt cache hit tokens + output_tokens: + type: integer + description: Output token count + output_tokens_details: + type: object + properties: + reasoning_tokens: + type: integer + description: >- + Tokens consumed by reasoning (only counted when reasoning is + enabled) + total_tokens: + type: integer + description: Total token count + ContentPart: + type: object + required: + - type + description: Message content part + properties: + type: + type: string + enum: + - input_text + - output_text + - input_image + - input_video + description: |- + Content part type: + - `input_text` / `output_text`: Text part + - `input_image`: Image input + - `input_video`: Video input + text: + type: string + description: Text content (when `type` is `input_text` / `output_text`) + image_url: + description: >- + Image input (when `type` is `input_image`). Supported formats: JPEG, + PNG, GIF, WEBP + oneOf: + - type: string + - type: object + required: + - url + properties: + url: + type: string + description: Image URL or Base64 encoding + detail: + type: string + enum: + - low + - default + - high + default: default + description: Image understanding precision tier + video_url: + description: >- + Video input (when `type` is `input_video`). Supported formats: MP4, + AVI, MOV, MKV + oneOf: + - type: string + - type: object + required: + - url + properties: + url: + type: string + description: >- + Video URL or Base64 encoding. Use the File API to upload + large files + fps: + type: number + format: float + default: 1 + minimum: 0.2 + maximum: 5 + description: Frame extraction rate + detail: + type: string + enum: + - low + - default + - high + default: default + description: Video understanding precision tier + max_long_side_pixel: + type: integer + description: Pixel constraint on the longest side of video frames + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: |- + `HTTP: Bearer Auth` + - Security Scheme Type: http + - HTTP Authorization Scheme: Bearer API_key, used to authenticate your account. View it in [Account Management > API Keys](https://platform.minimax.io/user-center/basic-information/interface-key) + +```` \ No newline at end of file diff --git a/llmsdk_docs/minimax_m3/docs/text-anthropic-api.md b/llmsdk_docs/minimax_m3/docs/text-anthropic-api.md new file mode 100644 index 00000000..4f62065f --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/text-anthropic-api.md @@ -0,0 +1,216 @@ +> ## Documentation Index +> +> Fetch the complete documentation index at: [/docs/llms.txt](https://platform.minimax.io/docs/llms.txt) +> +> Use this file to discover all available pages before exploring further. + +[Skip to main content](https://platform.minimax.io/docs/api-reference/text-anthropic-api#content-area) + +To meet developers’ needs for the Anthropic API ecosystem, our API now supports the Anthropic API format. With simple configuration, you can integrate MiniMax capabilities into the Anthropic API ecosystem. + +## [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#quick-start) Quick Start + +### [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#1-install-anthropic-sdk) 1\. Install Anthropic SDK + +Python + +Node.js + +``` +pip install anthropic +``` + +``` +npm install @anthropic-ai/sdk +``` + +### [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#2-configure-environment-variables) 2\. Configure Environment Variables + +``` +export ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic +export ANTHROPIC_API_KEY=${YOUR_API_KEY} +``` + +### [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#3-call-api) 3\. Call API + +Python + +``` +import anthropic + +client = anthropic.Anthropic() + +message = client.messages.create( + model="MiniMax-M3", + max_tokens=1000, + system="You are a helpful assistant.", + messages=[\ + {\ + "role": "user",\ + "content": [\ + {\ + "type": "text",\ + "text": "Hi, how are you?"\ + }\ + ]\ + }\ + ] +) + +for block in message.content: + if block.type == "thinking": + print(f"Thinking:\n{block.thinking}\n") + elif block.type == "text": + print(f"Text:\n{block.text}\n") +``` + +### [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#4-important-note) 4\. Important Note + +In multi-turn function call conversations, the complete model response (i.e., the assistant message) must be append to the conversation history to maintain the continuity of the reasoning chain. + +- Append the full `response.content` list to the message history (includes all content blocks: thinking/text/tool\_use) + +## [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#supported-models) Supported Models + +When using the Anthropic SDK, the `MiniMax-M3``MiniMax-M2.7``MiniMax-M2.7-highspeed``MiniMax-M2.5``MiniMax-M2.5-highspeed``MiniMax-M2.1``MiniMax-M2.1-highspeed``MiniMax-M2` model is supported: + +| Model Name | Context Window | Description | +| --- | --- | --- | +| MiniMax-M3 | 1,000,000 | **Latest M-series language model for agentic reasoning, tool use, coding, and long-context tasks** | +| MiniMax-M2.7 | 204,800 | **Beginning the journey of recursive self-improvement** (output speed approximately 60 tps) | +| MiniMax-M2.7-highspeed | 204,800 | **M2.7 Highspeed: Same performance, faster and more agile (output speed approximately 100 tps)** | +| MiniMax-M2.5 | 204,800 | **Peak Performance. Ultimate Value. Master the Complex (output speed approximately 60 tps)** | +| MiniMax-M2.5-highspeed | 204,800 | **M2.5 highspeed: Same performance, faster and more agile (output speed approximately 100 tps)** | +| MiniMax-M2.1 | 204,800 | **Powerful Multi-Language Programming Capabilities with Comprehensively Enhanced Programming Experience (output speed approximately 60 tps)** | +| MiniMax-M2.1-highspeed | 204,800 | **Faster and More Agile (output speed approximately 100 tps)** | +| MiniMax-M2 | 204,800 | **Agentic capabilities, Advanced reasoning** | + +For details on how tps (Tokens Per Second) is calculated, please refer to [FAQ > About APIs](https://platform.minimax.io/docs/faq/about-apis#q-how-is-tps-tokens-per-second-calculated-for-text-models). + +The Anthropic API compatibility interface currently only supports the +`MiniMax-M3``MiniMax-M2.7``MiniMax-M2.7-highspeed``MiniMax-M2.5``MiniMax-M2.5-highspeed``MiniMax-M2.1``MiniMax-M2.1-highspeed``MiniMax-M2` model. For other models, please use the standard MiniMax API +interface. + +## [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#compatibility) Compatibility + +### [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#supported-parameters) Supported Parameters + +When using the Anthropic SDK, we support the following input parameters: + +| Parameter | Support Status | Description | +| --- | --- | --- | +| `model` | Fully supported | supports `MiniMax-M3``MiniMax-M2.7``MiniMax-M2.7-highspeed``MiniMax-M2.5``MiniMax-M2.5-highspeed``MiniMax-M2.1``MiniMax-M2.1-highspeed``MiniMax-M2` model | +| `messages` | Partial support | `MiniMax-M3` supports text, image, video, tool use, tool result, and thinking blocks. The M2.7, M2.5, M2.1, and M2 series support text and tool-call content blocks only; they do not support image or video input | +| `max_tokens` | Fully supported | Maximum number of tokens to generate | +| `stream` | Fully supported | Streaming response | +| `system` | Fully supported | System prompt | +| `temperature` | Fully supported | Range \[0, 2\], controls output randomness, recommended value: 1 | +| `tool_choice` | Fully supported | Tool selection strategy | +| `tools` | Fully supported | Tool definitions | +| `top_p` | Fully supported | Nucleus sampling parameter, range \[0, 1\]. Default 0.95 for `MiniMax-M3` and 0.9 for M2.x models | +| `metadata` | Fully Supported | Metadata | +| `thinking` | Fully Supported | Thinking is off by default for MiniMax-M3 and can be enabled with `adaptive`. Thinking cannot be disabled for M2.x models. | +| `service_tier` | Fully Supported | Request admission tier. Supported values are `standard` and `priority`; if omitted, requests use `standard`. The `priority` [price](https://platform.minimax.io/docs/guides/pricing-paygo) is 1.5 times the `standard` price and ensures priority admission so the request is processed ahead of other requests, leading to faster responses and fewer failures. | +| `top_k` | Ignored | This parameter will be ignored | +| `stop_sequences` | Ignored | This parameter will be ignored | +| `mcp_servers` | Ignored | This parameter will be ignored | +| `context_management` | Ignored | This parameter will be ignored | +| `container` | Ignored | This parameter will be ignored | + +### [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#thinking-control) Thinking Control + +For `MiniMax-M3`, the `thinking` parameter controls whether the model can emit `thinking` content blocks. + +- If `thinking` is omitted, thinking is off by default and the response does not include `thinking` blocks. +- Set `thinking: {"type": "adaptive"}` to explicitly enable thinking. For MiniMax-M3, `adaptive` is equivalent to thinking on. +- Set `thinking: {"type": "disabled"}` to explicitly keep MiniMax-M3 thinking output off. +- For M2.x models, thinking cannot be disabled; `thinking: {"type": "disabled"}` is accepted but thinking remains on. + +When a response includes `thinking` blocks, preserve them unchanged in later turns, especially in tool-use conversations. + +### [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#messages-field-support) Messages Field Support + +| Field Type | Support Status | Description | +| --- | --- | --- | +| `type="text"` | Fully supported | Text messages | +| `type="image"` | M3 only | Image input via URL or base64. Supports JPEG, PNG, GIF, WEBP | +| `type="video"` | M3 only | Video input via URL, base64, or `mm_file://{file_id}`. Supports MP4, AVI, MOV, MKV | +| `type="tool_use"` | Fully supported | Tool calls | +| `type="tool_result"` | Fully supported | Tool call results | +| `type="thinking"` | Fully supported | Reasoning content. Return the block unchanged in multi-turn thinking conversations | + +For `MiniMax-M3`, URL or base64 videos can be up to 50 MB, images can be up to 10 MB, and the request body can be up to 64 MB. For larger videos, upload through the Files API and pass `mm_file://{file_id}`; Files API videos can be up to 512 MB.Image token usage depends on image size and content. Use this as a rough single-image heuristic; check `POST /anthropic/v1/messages/count_tokens` or response `usage` for exact usage: + +| `detail` | Rough single-image token usage | +| --- | --- | +| `low` | Usually a few hundred tokens, up to ~600 | +| `default` | Often ~1k-3k tokens, up to ~5k | +| `high` | Often several thousand tokens, up to ~15k+ | + +The Anthropic-compatible API also supports `POST /anthropic/v1/messages/count_tokens` for `MiniMax-M3` token estimation. This endpoint returns input token usage without generating model output. + +## [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#examples) Examples + +### [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#streaming-response) Streaming Response + +Python + +``` +import anthropic + +client = anthropic.Anthropic() + +print("Starting stream response...\n") +print("=" * 60) +print("Thinking Process:") +print("=" * 60) + +stream = client.messages.create( + model="MiniMax-M3", + max_tokens=1000, + system="You are a helpful assistant.", + messages=[\ + {"role": "user", "content": [{"type": "text", "text": "Hi, how are you?"}]}\ + ], + stream=True, +) + +reasoning_buffer = "" +text_buffer = "" + +for chunk in stream: + if chunk.type == "content_block_start": + if hasattr(chunk, "content_block") and chunk.content_block: + if chunk.content_block.type == "text": + print("\n" + "=" * 60) + print("Response Content:") + print("=" * 60) + + elif chunk.type == "content_block_delta": + if hasattr(chunk, "delta") and chunk.delta: + if chunk.delta.type == "thinking_delta": + # Stream output thinking process + new_thinking = chunk.delta.thinking + if new_thinking: + print(new_thinking, end="", flush=True) + reasoning_buffer += new_thinking + elif chunk.delta.type == "text_delta": + # Stream output text content + new_text = chunk.delta.text + if new_text: + print(new_text, end="", flush=True) + text_buffer += new_text + +print("\n") +``` + +## [​](https://platform.minimax.io/docs/api-reference/text-anthropic-api\#important-notes) Important Notes + +1. The Anthropic API compatibility interface currently only supports the `MiniMax-M3``MiniMax-M2.7``MiniMax-M2.7-highspeed``MiniMax-M2.5``MiniMax-M2.5-highspeed``MiniMax-M2.1``MiniMax-M2.1-highspeed``MiniMax-M2` model +2. The `temperature` parameter range is \[0, 2\], values outside this range will return an error +3. Some Anthropic parameters (such as `top_k`, `stop_sequences`, `mcp_servers`, `context_management`, `container`) will be ignored +4. `MiniMax-M3` supports image and video input through Anthropic-compatible content blocks. The M2.7, M2.5, M2.1, and M2 series support text and tool-call content blocks only + +[Error Code Reference](https://platform.minimax.io/docs/api-reference/errorcode) [OpenAI SDK](https://platform.minimax.io/docs/api-reference/text-openai-api) + +Ctrl+I \ No newline at end of file diff --git a/llmsdk_docs/minimax_m3/docs/text-openai-api.md b/llmsdk_docs/minimax_m3/docs/text-openai-api.md new file mode 100644 index 00000000..88680c67 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/text-openai-api.md @@ -0,0 +1,240 @@ +> ## Documentation Index +> +> Fetch the complete documentation index at: [/docs/llms.txt](https://platform.minimax.io/docs/llms.txt) +> +> Use this file to discover all available pages before exploring further. + +[Skip to main content](https://platform.minimax.io/docs/api-reference/text-openai-api#content-area) + +To meet developers’ needs for the OpenAI API ecosystem, our API now supports the OpenAI API format. With simple configuration, you can integrate MiniMax capabilities into the OpenAI API ecosystem. + +## [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#quick-start) Quick Start + +### [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#1-install-openai-sdk) 1\. Install OpenAI SDK + +Python + +Node.js + +``` +pip install openai +``` + +``` +npm install openai +``` + +### [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#2-configure-environment-variables) 2\. Configure Environment Variables + +``` +export OPENAI_BASE_URL=https://api.minimax.io/v1 +export OPENAI_API_KEY=${YOUR_API_KEY} +``` + +### [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#3-call-api) 3\. Call API + +Python + +``` +from openai import OpenAI + +client = OpenAI() + +response = client.chat.completions.create( + model="MiniMax-M3", + messages=[\ + {"role": "system", "content": "You are a helpful assistant."},\ + {"role": "user", "content": "Hi, how are you?"},\ + ], + # Set reasoning_split=True to separate thinking content into reasoning_details field + extra_body={"reasoning_split": True}, +) + +print(f"Thinking:\n{response.choices[0].message.reasoning_details[0]['text']}\n") +print(f"Text:\n{response.choices[0].message.content}\n") +``` + +### [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#4-important-note) 4\. Important Note + +In multi-turn function call conversations, the complete model response (i.e., the assistant message) must be append to the conversation history to maintain the continuity of the reasoning chain. + +- Append the full `response_message` object (including the `tool_calls` field) to the message history + - For native OpenAI API with `MiniMax-M3``MiniMax-M2.7``MiniMax-M2.7-highspeed``MiniMax-M2.5``MiniMax-M2.5-highspeed``MiniMax-M2.1``MiniMax-M2.1-highspeed``MiniMax-M2` models, the `content` field will contain `` tag content, which must be preserved completely + - In the Interleaved Thinking compatible format, by enabling the additional parameter (`reasoning_split=True`), the model’s thinking content is provided separately via the `reasoning_details` field, which must also be preserved completely + +## [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#supported-models) Supported Models + +When using the OpenAI SDK, the following MiniMax models are supported: + +| Model Name | Context Window | Description | +| --- | --- | --- | +| MiniMax-M3 | 1,000,000 | **Latest M-series language model for agentic reasoning, tool use, coding, and long-context tasks** | +| MiniMax-M2.7 | 204,800 | **Beginning the journey of recursive self-improvement** (output speed approximately 60 tps) | +| MiniMax-M2.7-highspeed | 204,800 | **M2.7 Highspeed: Same performance, faster and more agile (output speed approximately 100 tps)** | +| MiniMax-M2.5 | 204,800 | **Peak Performance. Ultimate Value. Master the Complex (output speed approximately 60 tps)** | +| MiniMax-M2.5-highspeed | 204,800 | **M2.5 highspeed: Same performance, faster and more agile (output speed approximately 100 tps)** | +| MiniMax-M2.1 | 204,800 | **Powerful Multi-Language Programming Capabilities with Comprehensively Enhanced Programming Experience (output speed approximately 60 tps)** | +| MiniMax-M2.1-highspeed | 204,800 | **Faster and More Agile (output speed approximately 100 tps)** | +| MiniMax-M2 | 204,800 | **Agentic capabilities, Advanced reasoning** | + +For details on how tps (Tokens Per Second) is calculated, please refer to [FAQ > About APIs](https://platform.minimax.io/docs/faq/about-apis#q-how-is-tps-tokens-per-second-calculated-for-text-models). + +For more model information, please refer to the standard MiniMax API +documentation. + +## [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#multimodal-input) Multimodal Input + +OpenAI-compatible Chat Completions support text, image, and video input for `MiniMax-M3`.Use `image_url` content parts for images and `video_url` content parts for videos. The `detail` field accepts `low`, `default`, or `high` and defaults to `default`; `max_long_side_pixel` can be used to control the longest side. Images support JPEG, PNG, GIF, and WEBP. Videos support MP4, AVI, MOV, and MKV; `fps` defaults to 1 and accepts values from 0.2 to 5. URL or base64 videos can be up to 50 MB, images can be up to 10 MB, and the request body can be up to 64 MB. For larger videos, upload through the Files API and pass `mm_file://{file_id}`; Files API videos can be up to 512 MB.Image token usage depends on image size and content. Use this as a rough single-image heuristic; check response `usage` or token counting where available for exact usage: + +| `detail` | Rough single-image token usage | +| --- | --- | +| `low` | Usually a few hundred tokens, up to ~600 | +| `default` | Often ~1k-3k tokens, up to ~5k | +| `high` | Often several thousand tokens, up to ~15k+ | + +Python + +``` +response = client.chat.completions.create( + model="MiniMax-M3", + messages=[\ + {\ + "role": "user",\ + "content": [\ + {"type": "text", "text": "Summarize what is happening here."},\ + {\ + "type": "image_url",\ + "image_url": {\ + "url": "https://example.com/image.png",\ + "detail": "default",\ + },\ + },\ + {\ + "type": "video_url",\ + "video_url": {\ + "url": "mm_file://file_id",\ + "detail": "default",\ + },\ + },\ + ],\ + }\ + ], +) +``` + +## [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#minimax-m3-request-parameters) MiniMax-M3 Request Parameters + +`MiniMax-M3` supports these additional Chat Completions parameters through the OpenAI-compatible API: + +| Parameter | Description | +| --- | --- | +| `thinking` | Controls MiniMax-M3 thinking. `type` can be `disabled` or `adaptive`; when omitted, thinking is on by default. For M2.x models, thinking cannot be disabled. | +| `stream_options.include_usage` | When streaming, set to `true` to include token usage in the stream. | +| `max_tokens` | Legacy generation length limit. | +| `max_completion_tokens` | Generation length limit; use this field for new integrations. | +| `temperature` | Sampling temperature. Range `[0, 2]`, default `1`. | +| `top_p` | Nucleus sampling. Range `[0, 1]`. Default `0.95` for `MiniMax-M3` and `0.9` for M2.x models. | +| `tools` | Function tool definitions. | +| `reasoning_split` | Output-format switch. When enabled, separates thinking content into `reasoning_content` and `reasoning_details`. | +| `service_tier` | Request admission tier. Supported values are `standard` and `priority`; if omitted, requests use `standard`. The `priority` [price](https://platform.minimax.io/docs/guides/pricing-paygo) is 1.5 times the `standard` price and ensures priority admission so the request is processed ahead of other requests, leading to faster responses and fewer failures. | + +### [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#thinking-control) Thinking Control + +For `MiniMax-M3`, the `thinking` parameter controls whether the model can emit thinking content. + +- If `thinking` is omitted, thinking is on by default and the response includes thinking content. +- Set `thinking: {"type": "adaptive"}` to explicitly keep thinking on. For MiniMax-M3, `adaptive` is equivalent to thinking on. +- Set `thinking: {"type": "disabled"}` to skip thinking and answer directly. +- For M2.x models, thinking cannot be disabled; `thinking: {"type": "disabled"}` is accepted but thinking remains on. + +`reasoning_split` does not enable or disable thinking. It only controls how thinking content is returned: when `true`, thinking is exposed through `reasoning_content` and `reasoning_details`; when `false`, native Chat Completions responses keep thinking inside the `content` field with `...` tags. + +Python + +``` +response = client.chat.completions.create( + model="MiniMax-M3", + messages=[{"role": "user", "content": "Hi, how are you?"}], + extra_body={ + "thinking": {"type": "adaptive"}, + }, +) +``` + +## [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#examples) Examples + +### [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#streaming-response) Streaming Response + +Python + +``` +from openai import OpenAI + +client = OpenAI() + +print("Starting stream response...\n") +print("=" * 60) +print("Thinking Process:") +print("=" * 60) + +stream = client.chat.completions.create( + model="MiniMax-M3", + messages=[\ + {"role": "system", "content": "You are a helpful assistant."},\ + {"role": "user", "content": "Hi, how are you?"},\ + ], + # Set reasoning_split=True to separate thinking content into reasoning_details field + extra_body={"reasoning_split": True}, + stream=True, +) + +reasoning_buffer = "" +text_buffer = "" + +for chunk in stream: + if ( + hasattr(chunk.choices[0].delta, "reasoning_details") + and chunk.choices[0].delta.reasoning_details + ): + for detail in chunk.choices[0].delta.reasoning_details: + if "text" in detail: + reasoning_text = detail["text"] + new_reasoning = reasoning_text[len(reasoning_buffer) :] + if new_reasoning: + print(new_reasoning, end="", flush=True) + reasoning_buffer = reasoning_text + + if chunk.choices[0].delta.content: + content_text = chunk.choices[0].delta.content + new_text = content_text[len(text_buffer) :] if text_buffer else content_text + if new_text: + print(new_text, end="", flush=True) + text_buffer = content_text + +print("\n" + "=" * 60) +print("Response Content:") +print("=" * 60) +print(f"{text_buffer}\n") +``` + +### [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#tool-use-&-interleaved-thinking) Tool Use & Interleaved Thinking + +Learn how to use M3 Tool Use and Interleaved Thinking capabilities with OpenAI SDK, please refer to the following documentation. + +## Tool Use & Interleaved Thinking + +Learn how to leverage MiniMax-M3 tool calling and interleaved thinking capabilities to enhance performance in complex tasks. + +Click here + +## [​](https://platform.minimax.io/docs/api-reference/text-openai-api\#important-notes) Important Notes + +1. The `temperature` parameter range is \[0, 2\], recommended value: 1.0, values outside this range will return an error +2. Some OpenAI parameters (such as `presence_penalty`, `frequency_penalty`, `logit_bias`, etc.) will be ignored +3. Image and video inputs are supported by `MiniMax-M3` through OpenAI-compatible message content parts; audio input is not currently supported +4. The `n` parameter only supports value 1 +5. The deprecated `function_call` is not supported, please use the `tools` parameter + +[Anthropic SDK (Recommended)](https://platform.minimax.io/docs/api-reference/text-anthropic-api) [AI SDK](https://platform.minimax.io/docs/api-reference/text-ai-sdk) + +⌘I \ No newline at end of file diff --git a/llmsdk_docs/minimax_m3/docs/token-plan-overview.md b/llmsdk_docs/minimax_m3/docs/token-plan-overview.md new file mode 100644 index 00000000..48609372 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/token-plan-overview.md @@ -0,0 +1,120 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimax.io/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# Token Plan Overview + +> Token Plan subscription and usage overview + +Token Plan + +## Welcome to the Token Plan! + +MiniMax is one of the few AI labs that develops frontier models across the full spectrum of modalities: language, speech, video, music, and image. The [Token Plan](https://platform.minimax.io/subscribe/token-plan) extends upon our former Coding Plan by providing included Token Plan usage beyond language models, allowing more creative agents and applications to be built and used. + +## Core Advantages + + + + One subscription covers eligible MiniMax resources through a shared usage bar. + + + + Plans are designed for long-context agent, coding, and multimodal workflows. + + + + A flat subscription fee includes broad resource coverage while keeping usage predictable. + + + +## Subscription Key + +Each user has a dedicated **Subscription Key** for each Team they belong to. This key can exist before the Team has purchased Token Plan seats or Credits. If no resources are available to the user, the key has no usable paid resources. Once a Token Plan seat is assigned, or Credits access is available, the same Subscription Key can use those resources. + +For subscription and Credits rules, see [Token Plan pricing](/docs/guides/pricing-token-plan). For team usage, see [Token Plan for Teams](/docs/guides/pricing-token-plan-team). + +## Usage Quota + +The [Token Plan](https://platform.minimax.io/subscribe/token-plan) usage quota is shown as a usage bar in the console. For API endpoints that have pay-as-you-go pricing, usage deducts from the included Token Plan quota according to the corresponding endpoint pricing. + +| | **Plus** | **Max** | **Ultra** | +| :---------------- | :-------------------------------- | :------------------------------------------- | :------------------------------------------ | +| **Price** | **\$20 /month** | **\$50 /month** | **\$120 /month** | +| **Best for** | Personal projects and prototyping | Daily coding with agents and multimodal work | Heavy Agent workflows and extended sessions | +| **Quota windows** | 5-hour rolling and weekly windows | 5-hour rolling and weekly windows | 5-hour rolling and weekly windows | +| **Agent usage** | 3-4 agents | 4-5 agents | 6-7 agents | + +
Available model coverage includes the full MiniMax lineup (M3 / M2.7 / image / speech / music). A small number of special models (MiniMax H3, voice design, rapid voice cloning, etc.) are not currently supported. Credits usage is unrestricted.
+ + + To call supported multimodal resources with your Subscription Key, see the [MiniMax CLI guide](https://platform.minimax.io/docs/token-plan/minimax-cli). + + +## Getting Started + + + + Visit the [Token Plan](https://platform.minimax.io/subscribe/token-plan) subscription page to buy an individual subscription or Credits in your Default Team, or join a Team where a Token Plan seat or shared Credits are available. + + + + Navigate to [Account / Token Plan](https://platform.minimax.io/user-center/payment/token-plan) to view your available resources and get your **Subscription Key**. + + + + + **Important Notes** + + * The Subscription Key is used for Token Plan subscriptions and purchased Credits. + * The Subscription Key is not interchangeable with pay-as-you-go API Keys. + * A Subscription Key can exist before any paid resource is available. It becomes usable when the user has a Token Plan seat or Credits access. + * Please protect your API Key to prevent any loss of resources. + + +## Use in AI Agents and Coding Tools + +Pick your tool and follow the integration guide: + + + + + + + + + + + + + +For other tools, see [Other Tools](/docs/token-plan/other-tools). + +## After Reaching the Usage Limit + +When you reach the 5-hour rolling quota or weekly quota, you have the following options: + +1. **Use purchased Credits**: + If purchased Credits are available, usage within Token Plan resource coverage can be automatically covered by purchased Credits. +2. **Upgrade or get another assignment**: + Upgrade your subscription, or ask the Team Owner or Admin to assign a higher available plan. +3. **Switch to Pay-As-You-Go**: + If you wish to continue without rate limits, you can replace your Subscription Key with your [pay-as-you-go API Key](https://platform.minimax.io/user-center/basic-information/interface-key). This will switch the tool to a pay-as-you-go model based on actual token usage, which will consume your API account balance. +4. **Wait for the quota window to reset**: + The included Token Plan quota uses 5-hour rolling and weekly windows. Unused subscription quota does not carry over to the next billing cycle. + +## Next Steps + + + + Run your first MiniMax API call in 5 minutes. + + + + Common questions on quotas, billing, switching, refunds. + + + + Current discount campaigns. + + diff --git a/llmsdk_docs/minimax_m3/docs/tool-use-interleaved-thinking.md b/llmsdk_docs/minimax_m3/docs/tool-use-interleaved-thinking.md new file mode 100644 index 00000000..ae5ca558 --- /dev/null +++ b/llmsdk_docs/minimax_m3/docs/tool-use-interleaved-thinking.md @@ -0,0 +1,609 @@ +> ## Documentation Index +> Fetch the complete documentation index at: https://platform.minimax.io/docs/llms.txt +> Use this file to discover all available pages before exploring further. + +# Tool Use & Interleaved Thinking + +> MiniMax-M3 is an Agentic Model with exceptional Tool Use capabilities. + +M3 natively supports Interleaved Thinking, enabling it to reason between each round of tool interactions. Before every Tool Use, the model reflects on the current environment and the tool outputs to decide its next action. + + + +This ability allows M3 to excel at long-horizon and complex tasks, achieving state-of-the-art (SOTA) results on benchmarks such as SWE, BrowseCamp, and xBench, which test both coding and agentic reasoning performance. + +In the following examples, we’ll illustrate best practices for Tool Use and Interleaved Thinking with M3. The key principle is to return the model’s full response each time—especially the internal reasoning fields (e.g., thinking or reasoning\_details). + +## Parameters + +### Request Parameters + +* `tools`: Defines the list of callable functions, including function names, descriptions, and parameter schemas + +### Response Parameters + +Key fields in Tool Use responses: + +* `thinking/reasoning_details`: The model's thinking/reasoning process +* `text/content`: The text content output by the model +* `tool_calls`: Contains information about functions the model has decided to invoke +* `function.name`: The name of the function being called +* `function.arguments`: Function call parameters (JSON string format) +* `id`: Unique identifier for the tool call + +## Important Note + +In multi-turn function call conversations, the complete model response (i.e., the assistant message) must be append to the conversation history to maintain the continuity of the reasoning chain. + +**OpenAI SDK:** + +* Append the full `response_message` object (including the `tool_calls` field) to the message history + * When using MiniMax-M3, the `content` field contains `` tags which will be automatically preserved + * In the Interleaved Thinking Compatible Format, by using the additional parameter (`reasoning_split=True`), the model's thinking content is separated into the `reasoning_details` field. This content also needs to be added to historical messages. + +**Anthropic SDK:** + +* Append the full `response.content` list to the message history (includes all content blocks: thinking/text/tool\_use) + +See examples below for implementation details. + +## Examples + +### Anthropic SDK + +#### Configure Environment Variables + +For international users, use `https://api.minimax.io/anthropic`; for users in China, use `https://api.minimaxi.com/anthropic` + +```bash theme={null} +export ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic +export ANTHROPIC_API_KEY=${YOUR_API_KEY} +``` + +#### Example + +```python theme={null} +import anthropic +import json + +# Initialize client +client = anthropic.Anthropic() + +# Define tool: weather query +tools = [ + { + "name": "get_weather", + "description": "Get weather of a location, the user should supply a location first.", + "input_schema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, US", + } + }, + "required": ["location"] + } + } +] + +def send_messages(messages): + params = { + "model": "MiniMax-M3", + "max_tokens": 4096, + "messages": messages, + "tools": tools, + } + + response = client.messages.create(**params) + return response + +def process_response(response): + thinking_blocks = [] + text_blocks = [] + tool_use_blocks = [] + + # Iterate through all content blocks + for block in response.content: + if block.type == "thinking": + thinking_blocks.append(block) + print(f"💭 Thinking>\n{block.thinking}\n") + elif block.type == "text": + text_blocks.append(block) + print(f"💬 Model>\t{block.text}") + elif block.type == "tool_use": + tool_use_blocks.append(block) + print(f"🔧 Tool>\t{block.name}({json.dumps(block.input, ensure_ascii=False)})") + + return thinking_blocks, text_blocks, tool_use_blocks + +# 1. User query +messages = [{"role": "user", "content": "How's the weather in San Francisco?"}] +print(f"\n👤 User>\t {messages[0]['content']}") + +# 2. Model returns first response (may include tool calls) +response = send_messages(messages) +thinking_blocks, text_blocks, tool_use_blocks = process_response(response) + +# 3. If tool calls exist, execute tools and continue conversation +if tool_use_blocks: + # ⚠️ Critical: Append the assistant's complete response to message history + # response.content contains a list of all blocks: [thinking block, text block, tool_use block] + # Must be fully preserved, otherwise subsequent conversation will lose context + messages.append({ + "role": "assistant", + "content": response.content + }) + + # Execute tool and return result (simulating weather API call) + print(f"\n🔨 Executing tool: {tool_use_blocks[0].name}") + tool_result = "24℃, sunny" + print(f"📊 Tool result: {tool_result}") + + # Add tool execution result + messages.append({ + "role": "user", + "content": [ + { + "type": "tool_result", + "tool_use_id": tool_use_blocks[0].id, + "content": tool_result + } + ] + }) + + # 4. Get final response + final_response = send_messages(messages) + process_response(final_response) +``` + +**Output:** + +```nushell theme={null} +👤 User> How's the weather in San Francisco? +💭 Thinking> +Okay, so the user is asking about the weather in San Francisco. This is a straightforward request that requires me to get current weather information for a specific location. + +Looking at my available tools, I see I have a `get_weather` function that can provide weather information for a location. This is exactly what I need to answer the user's question. The function requires a "location" parameter, which should be a string containing the city and potentially the state/country. + +In this case, the user has clearly specified San Francisco as the location. San Francisco is a major city in US, so I don't need to include the country code - just "San Francisco" should be sufficient for the weather service to identify the correct location. + +The required parameters for the get_weather tool are: +- location: "San Francisco" + +I should call this tool to retrieve the current weather information for San Francisco before I can provide a meaningful answer to the user. Once I get the weather data back from the tool, I'll be able to share details like temperature, conditions (sunny, cloudy, rainy, etc.), and possibly other relevant information like humidity or wind speed. + +So I'll make a tool call to get_weather with the location parameter set to "San Francisco". After I receive the response from this tool call, I'll be able to provide the user with the current weather information they requested. + +🔧 Tool> get_weather({"location": "San Francisco"}) + +🔨 Executing tool: get_weather +📊 Tool result: 24℃, sunny +💭 Thinking> +I've just called the get_weather tool to check the current conditions in San Francisco as the user requested. Let me analyze what information I received back. + +The tool returned a simple response: "24℃, sunny". This is quite straightforward - it tells me the current temperature is 24 degrees Celsius and the weather conditions are sunny. This is exactly the basic weather information the user was asking about. + +The temperature is given in Celsius (24℃), which is appropriate since US uses the metric system. I could convert this to Fahrenheit for users who might be more familiar with that scale, but since the user didn't specify a preference, I'll stick with the metric measurement as provided by the tool. + +For the weather condition, I received "sunny" which indicates clear skies and good visibility. This is useful information that tells the user they can expect good weather if they're planning to be outside. + +I don't have additional details like humidity, wind speed, or UV index from the tool response. If the user wants more detailed information, they could ask a follow-up question, and I might need to provide general advice about sunny weather conditions or suggest checking a more detailed weather service. + +Now I need to formulate a clear, concise response to the user that directly answers their question about the weather in San Francisco. I'll keep it simple and factual, stating the temperature and conditions clearly. I should also add a friendly closing to invite further questions if needed. + +The most straightforward way to present this information is to state the temperature first, followed by the conditions, and then add a friendly note inviting the user to ask for more information if they want it. + +💬 Model> The current weather in San Francisco is 24℃ and sunny. +``` + +**Response Body** + +```json theme={null} +{ + "id": "05566b15ee32962663694a2772193ac7", + "type": "message", + "role": "assistant", + "model": "MiniMax-M3", + "content": [ + { + "thinking": "Let me think about this request. The user is asking about the weather in San Francisco. This is a straightforward request that requires current weather information.\n\nTo provide accurate weather information, I need to use the appropriate tool. Looking at the tools available to me, I see there's a \"get_weather\" tool that seems perfect for this task. This tool requires a location parameter, which should include both the city and state/region.\n\nThe user has specified \"San Francisco\" as the location, but they haven't included the state. For the US, it's common practice to include the state when specifying a city, especially for well-known cities like San Francisco that exist in multiple states (though there's really only one San Francisco that's famous).\n\nAccording to the tool description, I need to provide the location in the format \"San Francisco, US\" - with the city, comma, and the country code for the United States. This follows the standard format specified in the tool's parameter description: \"The city and state, e.g. San Francisco, US\".\n\nSo I need to call the get_weather tool with the location parameter set to \"San Francisco, US\". This will retrieve the current weather information for San Francisco, which I can then share with the user.\n\nI'll format my response using the required XML tags for tool calls, providing the tool name \"get_weather\" and the arguments as a JSON object with the location parameter set to \"San Francisco, US\".", + "signature": "cfa12f9d651953943c7a33278051b61f586e2eae016258ad6b824836778406bd", + "type": "thinking" + }, + { + "type": "tool_use", + "id": "call_function_3679004591_1", + "name": "get_weather", + "input": { + "location": "San Francisco, US" + } + } + ], + "usage": { + "input_tokens": 222, + "output_tokens": 321 + }, + "stop_reason": "tool_use", + "base_resp": { + "status_code": 0, + "status_msg": "" + } +} +``` + +### OpenAI SDK + +#### Configure Environment Variables + +For international users, use `https://api.minimax.io/v1`; for users in China, use `https://api.minimaxi.com/v1` + +```bash theme={null} +export OPENAI_BASE_URL=https://api.minimax.io/v1 +export OPENAI_API_KEY=${YOUR_API_KEY} +``` + +#### Interleaved Thinking Compatible Format + +When calling MiniMax-M3 via the OpenAI SDK, you can pass the extra parameter `reasoning_split=True` to get a more developer-friendly output format. + + + Important Note: To ensure that Interleaved Thinking functions properly and the model’s chain of thought remains uninterrupted, the entire `response_message` — including the `reasoning_details` field — must be preserved in the message history and passed back to the model in the next round of interaction.This is essential for achieving the model’s best performance. + + +Be sure to review how your API request and response handling function (e.g., `send_messages`) is implemented, as well as how you append the historical messages with `messages.append(response_message)`. + +```python theme={null} +import json + +from openai import OpenAI + +client = OpenAI() + +# Define tool: weather query +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather of a location, the user should supply a location first.", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, US", + } + }, + "required": ["location"], + }, + }, + }, +] + + +def send_messages(messages): + """Send messages and return response""" + response = client.chat.completions.create( + model="MiniMax-M3", + messages=messages, + tools=tools, + # Set reasoning_split=True to separate thinking content into reasoning_details field + extra_body={"reasoning_split": True}, + ) + return response.choices[0].message + + +# 1. User query +messages = [{"role": "user", "content": "How's the weather in San Francisco?"}] +print(f"👤 User>\t {messages[0]['content']}") + +# 2. Model returns tool call +response_message = send_messages(messages) + +if response_message.tool_calls: + tool_call = response_message.tool_calls[0] + function_args = json.loads(tool_call.function.arguments) + print(f"💭 Thinking>\t {response_message.reasoning_details[0]['text']}") + print(f"💬 Model>\t {response_message.content}") + print(f"🔧 Tool>\t {tool_call.function.name}({function_args['location']})") + + # 3. Execute tool and return result + messages.append(response_message) + messages.append( + { + "role": "tool", + "tool_call_id": tool_call.id, + "content": "24℃, sunny", # In real applications, call actual weather API here + } + ) + + # 4. Get final response + final_message = send_messages(messages) + print( + f"💭 Thinking>\t {final_message.model_dump()['reasoning_details'][0]['text']}" + ) + print(f"💬 Model>\t {final_message.content}") +else: + print(f"💬 Model>\t {response_message.content}") +``` + +**Output:** + +``` +👤 User> How's the weather in San Francisco? +💭 Thinking> Alright, the user is asking about the weather in San Francisco. This is a straightforward question that requires real-time information about current weather conditions. + +Looking at the available tools, I see I have access to a "get_weather" tool that's specifically designed for this purpose. The tool requires a "location" parameter, which should be in the format of city and state, like "San Francisco, CA". + +The user has clearly specified they want weather information for "San Francisco" in their question. However, they didn't include the state (California), which is recommended for the tool parameter. While "San Francisco" alone might be sufficient since it's a well-known city, for accuracy and to follow the parameter format, I should include the state as well. + +Since I need to use the tool to get the current weather information, I'll need to call the "get_weather" tool with "San Francisco, CA" as the location parameter. This will provide the user with the most accurate and up-to-date weather information for their query. + +I'll format my response using the required tool_calls XML tags and include the tool name and arguments in the specified JSON format. +💬 Model> + +🔧 Tool> get_weather(San Francisco, US) +💭 Thinking> Okay, I've received the user's question about the weather in San Francisco, and I've used the get_weather tool to retrieve the current conditions. + +The tool has returned a simple response: "24℃, sunny". This gives me two pieces of information - the temperature is 24 degrees Celsius, and the weather condition is sunny. That's quite straightforward and matches what I would expect for San Francisco on a nice day. + +Now I need to present this information to the user in a clear, concise way. Since the response from the tool was quite brief, I'll keep my answer similarly concise. I'll directly state the temperature and weather condition that the tool provided. + +I should make sure to mention that this information is current, so the user understands they're getting up-to-date conditions. I don't need to provide additional details like humidity, wind speed, or forecast since the user only asked about the current weather. + +The temperature is given in Celsius (24℃), which is the standard metric unit, so I'll leave it as is rather than converting to Fahrenheit, though I could mention the conversion if the user seems to be more familiar with Fahrenheit. + +Since this is a simple informational query, I don't need to ask follow-up questions or suggest activities based on the weather. I'll just provide the requested information clearly and directly. + +My response will be a single sentence stating the current temperature and weather conditions in San Francisco, which directly answers the user's question. +💬 Model> The weather in San Francisco is currently sunny with a temperature of 24℃. +``` + +**Response Body** + +```json theme={null} +{ + "id": "05566b8d51ded3a3016d6cc100685cad", + "choices": [ + { + "finish_reason": "tool_calls", + "index": 0, + "message": { + "content": "\n", + "role": "assistant", + "name": "MiniMax AI", + "tool_calls": [ + { + "id": "call_function_2831178524_1", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"location\": \"San Francisco, US\"}" + }, + "index": 0 + } + ], + "audio_content": "", + "reasoning_details": [ + { + "type": "reasoning.text", + "id": "reasoning-text-1", + "format": "MiniMax-response-v1", + "index": 0, + "text": "Let me think about this request. The user is asking about the weather in San Francisco. This is a straightforward request where they want to know current weather conditions in a specific location.\n\nLooking at the tools available to me, I have access to a \"get_weather\" tool that can retrieve weather information for a location. The tool requires a location parameter in the format of \"city, state\" or \"city, country\". In this case, the user has specified \"San Francisco\" which is a city in the United States.\n\nTo properly use the tool, I need to format the location parameter correctly. The tool description mentions examples like \"San Francisco, US\" which follows the format of city, country code. However, since the user just mentioned \"San Francisco\" without specifying the state, and San Francisco is a well-known city that is specifically in California, I could use \"San Francisco, CA\" as the parameter value instead.\n\nActually, \"San Francisco, US\" would also work since the user is asking about the famous San Francisco in the United States, and there aren't other well-known cities with the same name that would cause confusion. The US country code is explicit and clear.\n\nBoth \"San Francisco, CA\" and \"San Francisco, US\" would be valid inputs for the tool. I'll go with \"San Francisco, US\" since it follows the exact format shown in the tool description example and is unambiguous.\n\nSo I'll need to call the get_weather tool with the location parameter set to \"San Francisco, US\". This will retrieve the current weather information for San Francisco, which I can then present to the user." + } + ] + } + } + ], + "created": 1762080909, + "model": "MiniMax-M3", + "object": "chat.completion", + "usage": { + "total_tokens": 560, + "total_characters": 0, + "prompt_tokens": 203, + "completion_tokens": 357 + }, + "input_sensitive": false, + "output_sensitive": false, + "input_sensitive_type": 0, + "output_sensitive_type": 0, + "output_sensitive_int": 0, + "base_resp": { + "status_code": 0, + "status_msg": "" + } +} +``` + +#### OpenAI Native Format + +Since the OpenAI ChatCompletion API native format does not natively support thinking return and pass-back, the model's thinking is injected into the `content` field in the form of `reasoning_content`. Developers can manually parse it for display purposes. However, we strongly recommend developers use the Interleaved Thinking compatible format. + +What `extra_body={"reasoning_split": False}` does: + +* Embeds thinking in content: The model's reasoning is wrapped in `` tags within the `content` field +* Requires manual parsing: You need to parse `` tags if you want to display reasoning separately + + + Important Reminder: If you choose to use the native format, please note that in the message history, do not modify the `content` field. You must preserve the model's thinking content completely, i.e., `reasoning_content`. This is essential to ensure Interleaved Thinking works effectively and achieves optimal model performance! + + +```python theme={null} +from openai import OpenAI +import json + +# Initialize client +client = OpenAI( + api_key="", + base_url="https://api.minimax.io/v1", +) + +# Define tool: weather query +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather of a location, the user should supply a location first.", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, US", + } + }, + "required": ["location"] + }, + } + }, +] + +def send_messages(messages): + """Send messages and return response""" + response = client.chat.completions.create( + model="MiniMax-M3", + messages=messages, + tools=tools, + # Set reasoning_split=False to keep thinking content in tags within content field + extra_body={"reasoning_split": False}, + ) + return response.choices[0].message + +# 1. User query +messages = [{"role": "user", "content": "How's the weather in San Francisco?"}] +print(f"👤 User>\t {messages[0]['content']}") + +# 2. Model returns tool call +response_message = send_messages(messages) + +if response_message.tool_calls: + tool_call = response_message.tool_calls[0] + function_args = json.loads(tool_call.function.arguments) + print(f"💬 Model>\t {response_message.content}") + print(f"🔧 Tool>\t {tool_call.function.name}({function_args['location']})") + + # 3. Execute tool and return result + messages.append(response_message) + messages.append({ + "role": "tool", + "tool_call_id": tool_call.id, + "content": "24℃, sunny" # In production, call actual weather API here + }) + + # 4. Get final response + final_message = send_messages(messages) + print(f"💬 Model>\t {final_message.content}") +else: + print(f"💬 Model>\t {response_message.content}") +``` + +**Output:** + +```nushell theme={null} +👤 User> How's the weather in San Francisco? +💬 Model> +Alright, the user is asking about the weather in San Francisco. This is a straightforward request that I can handle using the tools provided to me. + +I see that I have access to a tool called "get_weather" which can provide weather information for a location. Looking at the parameters, it requires a "location" parameter which should be a string in the format of "city and state, e.g. San Francisco, US". + +In this case, the user has already specified the location as "San Francisco", which is a major city in California, US. I need to format this properly for the tool call. Following the example format in the tool description, I should format it as "San Francisco, US". + +The user didn't specify any other parameters or requirements, so a simple weather query should be sufficient. I don't need to ask for clarification since they've provided a clear location. + +Let me prepare the tool call to get the weather information for San Francisco. I'll use the "get_weather" tool with the location parameter set to "San Francisco, US". This should return the current weather conditions for San Francisco, which is what the user is asking about. + +Once I get the weather information back from the tool, I'll be able to provide the user with details about the current weather in San Francisco, such as temperature, conditions (sunny, cloudy, rainy, etc.), and possibly other relevant information like humidity or wind speed if that data is available. + +So I'll proceed with making the tool call to get_weather with the location parameter. + + +🔧 Tool> get_weather(San Francisco, US) +💬 Model> +Let me analyze what's happening in this conversation. The user asked about the weather in San Francisco, and I needed to provide them with this information. + +Looking at the tools available to me, I have access to a "get_weather" tool that can retrieve weather information for a specific location. I used this tool and called it with the argument "location": "San Francisco, US" as specified in the tool's parameters. + +The tool has now returned a response with the weather information for San Francisco. The response is quite concise - it simply states "24℃, sunny". This gives me two pieces of information: +1. The temperature is 24 degrees Celsius +2. The weather condition is sunny + +This is exactly what the user wanted to know - how's the weather in San Francisco. The information is clear and straightforward. + +Now I need to format this information in a clear, natural way for the user. Since the tool returned the temperature in Celsius, I'll use that unit rather than converting to Fahrenheit (though 24°C is about 75°F if the user happens to think in those terms). + +I should keep my response concise since the weather information itself is simple. I don't need to add any caveats or additional explanations since the weather report is straightforward. I won't include any details about wind, humidity, or other meteorological data since the tool didn't provide that information. + +So my response will simply state the current temperature and that it's sunny in San Francisco, which directly answers the user's question. + + +The weather in San Francisco is currently sunny with a temperature of 24℃. +``` + +**Response Body** + +```JSON theme={null} +{ + "id": "055b7928a143b2d21ad6b2bab2c8f8b2", + "choices": [{ + "finish_reason": "tool_calls", + "index": 0, + "message": { + "content": "\nAlright, the user is asking about the weather in San Francisco. This is a straightforward request that I can handle using the tools provided to me.\n\nI see that I have access to a tool called \"get_weather\" which can provide weather information for a location. Looking at the parameters, it requires a \"location\" parameter which should be a string in the format of \"city and state, e.g. San Francisco, US\".\n\nIn this case, the user has already specified the location as \"San Francisco\", which is a major city in California, US. I need to format this properly for the tool call. Following the example format in the tool description, I should format it as \"San Francisco, US\".\n\nThe user didn't specify any other parameters or requirements, so a simple weather query should be sufficient. I don't need to ask for clarification since they've provided a clear location.\n\nLet me prepare the tool call to get the weather information for San Francisco. I'll use the \"get_weather\" tool with the location parameter set to \"San Francisco, US\". This should return the current weather conditions for San Francisco, which is what the user is asking about.\n\nOnce I get the weather information back from the tool, I'll be able to provide the user with details about the current weather in San Francisco, such as temperature, conditions (sunny, cloudy, rainy, etc.), and possibly other relevant information like humidity or wind speed if that data is available.\n\nSo I'll proceed with making the tool call to get_weather with the location parameter.\n\n\n\n", + "role": "assistant", + "name": "MiniMax AI", + "tool_calls": [{ + "id": "call_function_1202729600_1", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"location\": \"San Francisco, US\"}" + }, + "index": 0 + }], + "audio_content": "" + } + }], + "created": 1762412072, + "model": "MiniMax-M3", + "object": "chat.completion", + "usage": { + "total_tokens": 560, + "total_characters": 0, + "prompt_tokens": 222, + "completion_tokens": 338 + }, + "input_sensitive": false, + "output_sensitive": false, + "input_sensitive_type": 0, + "output_sensitive_type": 0, + "output_sensitive_int": 0, + "base_resp": { + "status_code": 0, + "status_msg": "" + } +} +``` + +## Recommended Reading + + + + MiniMax-M3 excels at code understanding, dialogue, and reasoning. + + + + Supports text generation via compatible Anthropic API and OpenAI API. + + + + Use Anthropic SDK with MiniMax models + + + + Use OpenAI SDK with MiniMax models + + diff --git a/llmsdk_docs/minimax_m3/quickstart.python.md b/llmsdk_docs/minimax_m3/quickstart.python.md new file mode 100644 index 00000000..783db6b1 --- /dev/null +++ b/llmsdk_docs/minimax_m3/quickstart.python.md @@ -0,0 +1,32 @@ +# MiniMax M3 Python Quick Start + +MiniMax documents an OpenAI Responses API-compatible endpoint at `https://api.minimax.io/v1/responses`. + +```python +from openai import AsyncOpenAI + +client = AsyncOpenAI( + api_key="", + base_url="https://api.minimax.io/v1", +) + +stream = await client.responses.create( + model="MiniMax-M3", + input="Explain the purpose of a hash function.", + reasoning={"effort": "minimal"}, + stream=True, +) + +async for event in stream: + print(event) +``` + +Use a Token Plan **Subscription Key** for Token Plan quota/Credits, or a separate API Key for pay-as-you-go billing. The keys are not interchangeable. + +For multi-turn tool use, preserve and replay the full assistant response, including reasoning output and function-call items, before adding matching function-call output items. + +Sources: + +- https://platform.minimax.io/docs/api-reference/responses-create +- https://platform.minimax.io/docs/guides/text-m3-function-call +- https://platform.minimax.io/docs/token-plan/intro diff --git a/llmsdk_docs/minimax_m3/quickstart.typescript.md b/llmsdk_docs/minimax_m3/quickstart.typescript.md new file mode 100644 index 00000000..87290deb --- /dev/null +++ b/llmsdk_docs/minimax_m3/quickstart.typescript.md @@ -0,0 +1,33 @@ +# MiniMax M3 TypeScript Quick Start + +MiniMax documents an OpenAI Responses API-compatible endpoint at `https://api.minimax.io/v1/responses`. + +```typescript +import OpenAI from "openai"; + +const client = new OpenAI({ + apiKey: "", + baseURL: "https://api.minimax.io/v1", +}); + +const stream = await client.responses.create({ + model: "MiniMax-M3", + input: "Explain the purpose of a hash function.", + reasoning: { effort: "minimal" }, + stream: true, +}); + +for await (const event of stream) { + console.log(event); +} +``` + +Use a Token Plan **Subscription Key** for Token Plan quota/Credits, or a separate API Key for pay-as-you-go billing. The keys are not interchangeable. + +For multi-turn tool use, preserve and replay the full assistant response, including reasoning output and function-call items, before adding matching function-call output items. + +Sources: + +- https://platform.minimax.io/docs/api-reference/responses-create +- https://platform.minimax.io/docs/guides/text-m3-function-call +- https://platform.minimax.io/docs/token-plan/intro diff --git a/src_py/agenthub/auto_client.py b/src_py/agenthub/auto_client.py index 38f942a8..f9418efd 100644 --- a/src_py/agenthub/auto_client.py +++ b/src_py/agenthub/auto_client.py @@ -46,7 +46,11 @@ def _create_client_for_model( self, model: str, api_key: str | None = None, base_url: str | None = None, client_type: str | None = None ) -> LLMClient: """Create the appropriate client for the given model.""" - client_type = (client_type or os.getenv("CLIENT_TYPE", model)).lower() + client_type = (client_type or os.getenv("CLIENT_TYPE") or model).lower() + if client_type == "minimax-m3": + from .minimax_m3 import MiniMaxM3Client + + return MiniMaxM3Client(model=model, api_key=api_key, base_url=base_url) # gemini-3.6 must be matched before the broader gemini-3 prefix below if any( prefix in client_type for prefix in ("gemini-3.6", "gemini-3.5-flash-lite") @@ -105,7 +109,10 @@ def _create_client_for_model( else: raise ValueError( f"{client_type} is not supported. " - "Supported client types: gemini-3.6, gemini-3, claude-5, claude-4-8, claude-4-7, claude-4-6, gpt-5.5, gpt-5.4, glm-5.2, glm-5.1, kimi-k3, kimi-k2.6, kimi-k2.5, deepseek-v4, openai-embedding, openai." + "Supported client types: minimax-m3, gemini-3.6, gemini-3, " + "claude-5, claude-4-8, claude-4-7, claude-4-6, gpt-5.5, gpt-5.4, glm-5.2, glm-5.1, kimi-k3, " + "kimi-k2.6, kimi-k2.5, " + "deepseek-v4, openai-embedding, openai." ) def transform_uni_config_to_model_config(self, config: UniConfig) -> Any: diff --git a/src_py/agenthub/minimax_m3/__init__.py b/src_py/agenthub/minimax_m3/__init__.py new file mode 100644 index 00000000..7dce4d52 --- /dev/null +++ b/src_py/agenthub/minimax_m3/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2025 Prism Shadow. and/or its affiliates +# +# 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. + +from .client import MiniMaxM3Client + + +__all__ = ["MiniMaxM3Client"] diff --git a/src_py/agenthub/minimax_m3/client.py b/src_py/agenthub/minimax_m3/client.py new file mode 100644 index 00000000..737464a0 --- /dev/null +++ b/src_py/agenthub/minimax_m3/client.py @@ -0,0 +1,457 @@ +# Copyright 2025 Prism Shadow. and/or its affiliates +# +# 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. + +import json +import os +from typing import Any, AsyncIterator + +from openai import AsyncOpenAI + +from ..base_client import LLMClient +from ..errors import UnsupportedParameterError, parse_tool_call_arguments +from ..types import ( + EventType, + FinishReason, + PartialContentItem, + PromptCaching, + ThinkingLevel, + ToolChoice, + UniConfig, + UniEvent, + UniMessage, + UsageMetadata, +) + + +_DEFAULT_BASE_URL = "https://api.minimax.io/v1" +_WIRE_ITEM_FIDELITY_KEY = "wire_item" + + +def _field(value: Any, name: str, default: Any = None) -> Any: + """Read a field from either an SDK model or a raw capture dictionary.""" + if isinstance(value, dict): + return value.get(name, default) + return getattr(value, name, default) + + +def _normalize_json_value(value: Any) -> Any: + """Convert SDK models and nested containers to JSON-style values.""" + model_dump = getattr(value, "model_dump", None) + if callable(model_dump): + value = model_dump(mode="json", exclude_unset=True) + if isinstance(value, dict): + return {key: _normalize_json_value(item) for key, item in value.items()} + if isinstance(value, (list, tuple)): + return [_normalize_json_value(item) for item in value] + return value + + +def _require_json_object(value: Any, context: str) -> dict[str, Any]: + """Normalize and validate a JSON object used for wire-fidelity replay.""" + normalized = _normalize_json_value(value) + if not isinstance(normalized, dict): + raise ValueError(f"{context} must be a JSON object.") + try: + json.dumps(normalized, ensure_ascii=False, allow_nan=False) + except (TypeError, ValueError) as exc: + raise ValueError(f"{context} must contain only JSON-style values.") from exc + return normalized + + +def _wire_item_from_fidelity(fidelity: dict[str, Any], context: str) -> dict[str, Any] | None: + """Extract a complete provider output item from a fidelity payload.""" + wire_item = fidelity.get(_WIRE_ITEM_FIDELITY_KEY) + if wire_item is None: + return None + return _require_json_object(wire_item, f"{context} wire item") + + +def _wire_content_text(wire_item: dict[str, Any], content_type: str) -> str | None: + """Concatenate text from matching content parts in a provider output item.""" + content = wire_item.get("content") + if not isinstance(content, list): + return None + + text_parts: list[str] = [] + for part in content: + if not isinstance(part, dict) or part.get("type") != content_type: + continue + text = part.get("text") + if not isinstance(text, str): + return None + text_parts.append(text) + return "".join(text_parts) if text_parts else None + + +def _json_values_equal(left: Any, right: Any) -> bool: + """Compare JSON-style values without conflating booleans and numbers.""" + if isinstance(left, bool) or isinstance(right, bool): + return isinstance(left, bool) and isinstance(right, bool) and left == right + if isinstance(left, dict) and isinstance(right, dict): + return left.keys() == right.keys() and all(_json_values_equal(left[key], right[key]) for key in left) + if isinstance(left, list) and isinstance(right, list): + return len(left) == len(right) and all( + _json_values_equal(left_item, right_item) for left_item, right_item in zip(left, right, strict=True) + ) + if isinstance(left, (int, float)) and isinstance(right, (int, float)): + return left == right + return type(left) is type(right) and left == right + + +def _usage_from_response(response: Any) -> UsageMetadata | None: + usage = _field(response, "usage") + if usage is None: + return None + + input_details = _field(usage, "input_tokens_details", {}) + output_details = _field(usage, "output_tokens_details", {}) + cached_tokens = _field(input_details, "cached_tokens", 0) or 0 + reasoning_tokens = _field(output_details, "reasoning_tokens", 0) or 0 + input_tokens = _field(usage, "input_tokens", 0) or 0 + output_tokens = _field(usage, "output_tokens", 0) or 0 + return { + "cached_tokens": cached_tokens, + "prompt_tokens": input_tokens - cached_tokens, + "thoughts_tokens": reasoning_tokens, + "response_tokens": output_tokens - reasoning_tokens, + } + + +def _format_response_error(event_type: str, error: Any, response_id: Any = None) -> str: + normalized_error = _normalize_json_value(error) + details: list[str] = [] + if isinstance(normalized_error, dict): + for field_name in ("code", "message", "param"): + if field_name in normalized_error and normalized_error[field_name] is not None: + details.append(f"{field_name}={normalized_error[field_name]!r}") + elif normalized_error is not None: + details.append(f"details={normalized_error!r}") + if not details: + details.append("no error details were provided") + + response_context = f" for response {response_id!r}" if response_id is not None else "" + return f"MiniMax Responses API {event_type}{response_context}: {', '.join(details)}" + + +class MiniMaxM3Client(LLMClient): + """MiniMax M3 client using MiniMax's Responses API.""" + + def __init__(self, model: str, api_key: str | None = None, base_url: str | None = None): + """Initialize a MiniMax M3 Responses client with a Subscription Key or API key.""" + if model.lower() != "minimax-m3": + raise ValueError(f"{model} is not supported by MiniMaxM3Client.") + self._model = model + self._client = AsyncOpenAI( + api_key=api_key or os.getenv("MINIMAX_API_KEY"), + base_url=base_url or os.getenv("MINIMAX_BASE_URL") or _DEFAULT_BASE_URL, + ) + self._history: list[UniMessage] = [] + + def _convert_thinking_level_to_effort(self, thinking_level: ThinkingLevel) -> str: + """Map AgentHub thinking levels to the MiniMax reasoning effort vocabulary.""" + mapping = { + ThinkingLevel.NONE: "none", + ThinkingLevel.LOW: "low", + ThinkingLevel.MEDIUM: "medium", + ThinkingLevel.HIGH: "high", + ThinkingLevel.XHIGH: "high", + } + return mapping[thinking_level] + + def _convert_tool_choice(self, tool_choice: ToolChoice) -> str: + """Validate MiniMax's supported automatic tool-selection modes.""" + if tool_choice in ("auto", "none"): + return tool_choice + raise UnsupportedParameterError( + self.__class__.__name__, + "tool_choice", + "MiniMax Responses API does not support required or named tool selection.", + ) + + def transform_uni_config_to_model_config(self, config: UniConfig) -> dict[str, Any]: + """Transform universal configuration to MiniMax's Responses API payload.""" + minimax_config: dict[str, Any] = {"model": self._model, "store": False} + + if config.get("system_prompt") is not None: + minimax_config["instructions"] = config["system_prompt"] + if config.get("max_tokens") is not None: + minimax_config["max_output_tokens"] = config["max_tokens"] + if config.get("temperature") is not None: + temperature = config["temperature"] + if not 0 <= temperature <= 1: + raise UnsupportedParameterError( + self.__class__.__name__, + "temperature", + "MiniMax Responses API does not support temperatures outside the range 0 to 1.", + ) + minimax_config["temperature"] = temperature + if config.get("thinking_level") is not None: + minimax_config["reasoning"] = {"effort": self._convert_thinking_level_to_effort(config["thinking_level"])} + if config.get("tools") is not None: + minimax_config["tools"] = [{"type": "function", **tool} for tool in config["tools"]] + if config.get("tool_choice") is not None: + minimax_config["tool_choice"] = self._convert_tool_choice(config["tool_choice"]) + if config.get("prompt_caching") == PromptCaching.DISABLE: + raise UnsupportedParameterError( + self.__class__.__name__, + "prompt_caching", + "MiniMax Responses API does not support disabling its automatic prompt cache.", + ) + if config.get("prompt_caching") == PromptCaching.ENHANCE: + raise UnsupportedParameterError( + self.__class__.__name__, + "prompt_caching", + "MiniMax Responses API does not support enhancing its automatic prompt cache.", + ) + + return minimax_config + + def transform_uni_message_to_model_input(self, messages: list[UniMessage]) -> list[dict[str, Any]]: + """Transform universal messages to MiniMax Responses input items.""" + input_list: list[dict[str, Any]] = [] + + for message in messages: + content_items: list[dict[str, Any]] = [] + for item in message["content_items"]: + if item["type"] == "text": + content_items.append( + { + "type": "input_text" if message["role"] == "user" else "output_text", + "text": item["text"], + } + ) + continue + if item["type"] == "image_url": + content_items.append({"type": "input_image", "image_url": item["image_url"]}) + continue + + if content_items: + input_list.append({"role": message["role"], "content": content_items}) + content_items = [] + + if item["type"] == "thinking": + fidelity = _require_json_object(item.get("fidelity") or {}, "MiniMax reasoning fidelity") + wire_item = _wire_item_from_fidelity(fidelity, "MiniMax reasoning fidelity") + if wire_item is None: + wire_item = { + key: value + for key, value in fidelity.items() + if key not in {_WIRE_ITEM_FIDELITY_KEY, "phase"} + } + if ( + not isinstance(wire_item.get("id"), str) + or not isinstance(wire_item.get("summary"), list) + or not isinstance(wire_item.get("content"), list) + ): + raise ValueError("MiniMax reasoning replay requires valid id, summary, and content fidelity.") + + wire_text = _wire_content_text(wire_item, "reasoning_text") + reasoning_content = ( + wire_item["content"] + if wire_text == item["thinking"] + else [{"type": "reasoning_text", "text": item["thinking"]}] + ) + input_list.append({**wire_item, "type": "reasoning", "content": reasoning_content}) + elif item["type"] == "tool_call": + arguments = json.dumps(item["arguments"], ensure_ascii=False, separators=(",", ":")) + raw_arguments = (item.get("fidelity") or {}).get("arguments") + if isinstance(raw_arguments, str): + parsed_arguments = parse_tool_call_arguments( + raw_arguments, + self.__class__.__name__, + item["name"], + item["tool_call_id"], + ) + if _json_values_equal(parsed_arguments, item["arguments"]): + arguments = raw_arguments + + input_list.append( + { + "type": "function_call", + "call_id": item["tool_call_id"], + "name": item["name"], + "arguments": arguments, + } + ) + elif item["type"] == "tool_result": + output: str | list[dict[str, Any]] = item["text"] + if item.get("images"): + output = [{"type": "input_text", "text": item["text"]}] + output.extend({"type": "input_image", "image_url": image_url} for image_url in item["images"]) + input_list.append( + { + "type": "function_call_output", + "call_id": item["tool_call_id"], + "output": output, + } + ) + else: + raise ValueError(f"Unknown item: {item}") + + if content_items: + input_list.append({"role": message["role"], "content": content_items}) + + return input_list + + def transform_model_output_to_uni_event(self, model_output: Any) -> UniEvent: + """Transform a MiniMax streaming event to AgentHub's universal event format.""" + event_type: EventType = "unused" + content_items: list[PartialContentItem] = [] + usage_metadata: UsageMetadata | None = None + finish_reason: FinishReason | None = None + minimax_event_type = _field(model_output, "type") + + if minimax_event_type == "response.output_text.delta": + event_type = "delta" + content_items.append( + { + "type": "text", + "text": _field(model_output, "delta", ""), + "fidelity": {"phase": _normalize_json_value(_field(model_output, "item_id"))}, + } + ) + elif minimax_event_type == "response.reasoning_text.delta": + event_type = "delta" + content_items.append({"type": "thinking", "thinking": _field(model_output, "delta", "")}) + elif minimax_event_type == "response.output_item.added": + item = _field(model_output, "item") + if _field(item, "type") == "function_call": + event_type = "start" + content_items.append( + { + "type": "partial_tool_call", + "name": _field(item, "name", ""), + "arguments": "", + "tool_call_id": _field(item, "call_id", ""), + "fidelity": { + "item_id": _normalize_json_value(_field(item, "id")), + "output_index": _normalize_json_value(_field(model_output, "output_index")), + }, + } + ) + elif minimax_event_type == "response.function_call_arguments.delta": + event_type = "delta" + content_items.append( + { + "type": "partial_tool_call", + "name": "", + "arguments": _field(model_output, "delta", ""), + "tool_call_id": "", + "fidelity": { + "item_id": _normalize_json_value(_field(model_output, "item_id")), + "output_index": _normalize_json_value(_field(model_output, "output_index")), + }, + } + ) + elif minimax_event_type == "response.output_item.done": + item = _normalize_json_value(_field(model_output, "item")) + if not isinstance(item, dict): + raise ValueError(f"MiniMax output item must be a JSON object, got: {item!r}") + + if item.get("type") == "reasoning": + event_type = "delta" + content_items.append( + { + "type": "thinking", + "thinking": "", + "fidelity": {_WIRE_ITEM_FIDELITY_KEY: item}, + } + ) + elif item.get("type") == "function_call": + event_type = "delta" + tool_name = item.get("name") or "" + tool_call_id = item.get("call_id") or "" + content_items.append( + { + "type": "tool_call", + "name": tool_name, + "arguments": parse_tool_call_arguments( + item.get("arguments"), self.__class__.__name__, tool_name, tool_call_id + ), + "tool_call_id": tool_call_id, + "fidelity": {"arguments": item.get("arguments")}, + } + ) + elif item.get("type") == "message": + event_type = "delta" + phase = item.get("id") or f"output-{_field(model_output, 'output_index', 0)}" + content_items.append( + { + "type": "text", + "text": "", + "fidelity": {"phase": phase}, + } + ) + elif minimax_event_type in {"response.completed", "response.incomplete"}: + event_type = "stop" + response = _field(model_output, "response") + usage_metadata = _usage_from_response(response) + if minimax_event_type == "response.completed": + output = _field(response, "output", []) or [] + finish_reason = ( + "tool_call" if any(_field(item, "type") == "function_call" for item in output) else "stop" + ) + else: + incomplete_reason = _field(_field(response, "incomplete_details"), "reason") + finish_reason = {"max_output_tokens": "length", "content_filter": "stop"}.get( + incomplete_reason, "unknown" + ) + elif minimax_event_type == "response.failed": + response = _field(model_output, "response") + raise RuntimeError( + _format_response_error(minimax_event_type, _field(response, "error"), _field(response, "id")) + ) + elif minimax_event_type in {"error", "response.error"}: + response = _field(model_output, "response") + error = _field(model_output, "error") + if error is None and response is not None: + error = _field(response, "error") + raise RuntimeError( + _format_response_error( + minimax_event_type, + error if error is not None else model_output, + _field(response, "id") if response is not None else None, + ) + ) + elif minimax_event_type not in { + "response.created", + "response.in_progress", + "response.output_text.done", + "response.reasoning_text.done", + "response.function_call_arguments.done", + "response.content_part.added", + "response.content_part.done", + }: + raise ValueError(f"Unknown output: {model_output}") + + return { + "role": "assistant", + "event_type": event_type, + "content_items": content_items, + "usage_metadata": usage_metadata, + "finish_reason": finish_reason, + } + + async def _streaming_response_internal( + self, messages: list[UniMessage], config: UniConfig + ) -> AsyncIterator[UniEvent]: + """Stream MiniMax Responses events.""" + minimax_config = self.transform_uni_config_to_model_config(config) + input_list = self.transform_uni_message_to_model_input(messages) + stream = await self._client.responses.create(**minimax_config, input=input_list, stream=True) + + async for model_event in stream: + event = self.transform_model_output_to_uni_event(model_event) + if event["event_type"] != "unused": + yield event diff --git a/src_py/agenthub/registry.py b/src_py/agenthub/registry.py index 8115bd21..92c3dc82 100644 --- a/src_py/agenthub/registry.py +++ b/src_py/agenthub/registry.py @@ -61,6 +61,7 @@ class SupportedModel(TypedDict): _DEEPSEEK = "https://api.deepseek.com" _OPENROUTER = "https://openrouter.ai/api/v1" _SILICONFLOW = "https://api.siliconflow.cn/v1" +_MINIMAX = "https://api.minimax.io/v1" # Display convention shared with the AgentHub apps: prices are stored in USD (official CNY # list prices pre-converted at 7 CNY/USD), so requesting CNY shows the vendor's numbers. @@ -187,6 +188,14 @@ def rate(value: float) -> float: "context_window": 1050000, "pricing": _usd(5.0, 30.0, cached=0.5), }, + { + "model": "MiniMax-M3", + "base_url": _MINIMAX, + "client": "minimax-m3", + "input_modalities": ["Text", "Image"], + "output_modalities": ["Text"], + "context_window": 1000000, + }, { "model": "text-embedding-3-large", "base_url": _OPENAI, diff --git a/src_py/tests/test_client.py b/src_py/tests/test_client.py index 6d4fb0bb..fe6eafaf 100644 --- a/src_py/tests/test_client.py +++ b/src_py/tests/test_client.py @@ -23,7 +23,7 @@ import httpx import pytest -from agenthub import AutoLLMClient, ThinkingLevel, list_supported_models +from agenthub import AutoLLMClient, PromptCaching, ThinkingLevel, UnsupportedParameterError, list_supported_models IMAGE = "https://cdn.britannica.com/80/120980-050-D1DA5C61/Poet-narcissus.jpg" @@ -41,6 +41,7 @@ class Model: support_embedding: bool = False provider: Literal["official", "bedrock", "vertex", "siliconflow", "openrouter", "modelverse"] = "official" client_type: str | None = None + requires_explicit_tool_prompt: bool = False def __repr__(self) -> str: return f"{self.name}:{self.provider}" @@ -100,6 +101,9 @@ def __repr__(self) -> str: if os.getenv("MOONSHOT_API_KEY"): AVAILABLE_MODELS.append(Model(name="kimi-k3", support_temperature=False)) +if os.getenv("MINIMAX_API_KEY"): + AVAILABLE_MODELS.append(Model(name="MiniMax-M3", client_type="minimax-m3", requires_explicit_tool_prompt=True)) + if os.getenv("DEEPSEEK_API_KEY"): AVAILABLE_MODELS.append( Model(name="deepseek-v4-flash", support_temperature=False, support_image_understanding=False) @@ -398,6 +402,340 @@ async def test_unknown_model(): async def test_list_supported_models(): """Test that the registry lists model entries accepted by AutoLLMClient.""" entries = list_supported_models() + minimax_entries = [entry for entry in entries if entry["base_url"] == "https://api.minimax.io/v1"] + assert minimax_entries == [ + { + "model": "MiniMax-M3", + "base_url": "https://api.minimax.io/v1", + "client": "minimax-m3", + "input_modalities": ["Text", "Image"], + "output_modalities": ["Text"], + "context_window": 1000000, + } + ] + + minimax_m3 = AutoLLMClient(model="MiniMax-M3", api_key="test-key") + assert minimax_m3._client.__class__.__name__ == "MiniMaxM3Client" + for client_type in (None, "minimax-m3"): + with pytest.raises(ValueError, match="not support"): + AutoLLMClient(model="MiniMax-M3-preview", api_key="test-key", client_type=client_type) + + expected_m3_efforts = { + ThinkingLevel.NONE: "none", + ThinkingLevel.LOW: "low", + ThinkingLevel.MEDIUM: "medium", + ThinkingLevel.HIGH: "high", + ThinkingLevel.XHIGH: "high", + } + for thinking_level, effort in expected_m3_efforts.items(): + config = minimax_m3.transform_uni_config_to_model_config({"thinking_level": thinking_level}) + assert config["reasoning"] == {"effort": effort} + assert "prompt_caching" not in minimax_m3.transform_uni_config_to_model_config( + {"prompt_caching": PromptCaching.ENABLE} + ) + for prompt_caching in (PromptCaching.DISABLE, PromptCaching.ENHANCE): + with pytest.raises(UnsupportedParameterError, match="does not support"): + minimax_m3.transform_uni_config_to_model_config({"prompt_caching": prompt_caching}) + for tool_choice in ("required", ["lookup"]): + with pytest.raises(UnsupportedParameterError, match="does not support"): + minimax_m3.transform_uni_config_to_model_config({"tool_choice": tool_choice}) + + reasoning_wire_item = { + "id": "reasoning-1", + "status": "completed", + "summary": [], + "content": [{"type": "reasoning_text", "text": "reasoning"}], + "type": "reasoning", + } + function_wire_item = { + "id": "function-1", + "status": "completed", + "type": "function_call", + "call_id": "call-1", + "name": "lookup", + "arguments": '{"城市": "上海"}', + } + ordered_input = minimax_m3.transform_uni_message_to_model_input( + [ + { + "role": "assistant", + "content_items": [ + {"type": "text", "text": "before"}, + { + "type": "thinking", + "thinking": "reasoning", + "fidelity": {"wire_item": reasoning_wire_item}, + }, + { + "type": "tool_call", + "name": "lookup", + "arguments": {"城市": "上海"}, + "tool_call_id": "call-1", + }, + {"type": "text", "text": "after"}, + ], + } + ] + ) + assert ordered_input == [ + {"role": "assistant", "content": [{"type": "output_text", "text": "before"}]}, + reasoning_wire_item, + { + "type": "function_call", + "call_id": "call-1", + "name": "lookup", + "arguments": '{"城市":"上海"}', + }, + {"role": "assistant", "content": [{"type": "output_text", "text": "after"}]}, + ] + + reasoning_events = [ + minimax_m3.transform_model_output_to_uni_event( + {"type": "response.reasoning_text.delta", "delta": "reasoning"} + ), + minimax_m3.transform_model_output_to_uni_event( + {"type": "response.output_item.done", "output_index": 0, "item": reasoning_wire_item} + ), + ] + replay_reasoning = minimax_m3.concat_uni_events_to_uni_message(reasoning_events) + assert replay_reasoning["content_items"] == [ + { + "type": "thinking", + "thinking": "reasoning", + "fidelity": {"wire_item": reasoning_wire_item}, + } + ] + assert minimax_m3.transform_uni_message_to_model_input([replay_reasoning]) == [reasoning_wire_item] + + message_wire_item = { + "id": "message-1", + "type": "message", + "status": "completed", + "role": "assistant", + "content": [ + { + "type": "output_text", + "text": "exact response", + "annotations": [], + "logprobs": None, + } + ], + "phase": None, + } + message_events = [ + minimax_m3.transform_model_output_to_uni_event( + { + "type": "response.output_text.delta", + "item_id": "message-1", + "output_index": 0, + "content_index": 0, + "delta": "exact response", + } + ), + minimax_m3.transform_model_output_to_uni_event( + {"type": "response.output_item.done", "output_index": 0, "item": message_wire_item} + ), + ] + replay_message = minimax_m3.concat_uni_events_to_uni_message(message_events) + assert replay_message["content_items"] == [ + { + "type": "text", + "text": "exact response", + "fidelity": {"phase": "message-1"}, + } + ] + assert minimax_m3.transform_uni_message_to_model_input([replay_message]) == [ + {"role": "assistant", "content": [{"type": "output_text", "text": "exact response"}]} + ] + + second_function_wire_item = { + **function_wire_item, + "id": "function-2", + "call_id": "call-2", + "arguments": '{"城市": "北京"}', + } + function_events = [ + minimax_m3.transform_model_output_to_uni_event( + {"type": "response.output_item.done", "output_index": index, "item": wire_item} + ) + for index, wire_item in enumerate((function_wire_item, second_function_wire_item)) + ] + replay_functions = minimax_m3.concat_uni_events_to_uni_message(function_events) + assert replay_functions["content_items"] == [ + { + "type": "tool_call", + "name": "lookup", + "arguments": {"城市": "上海"}, + "tool_call_id": "call-1", + "fidelity": {"arguments": '{"城市": "上海"}'}, + }, + { + "type": "tool_call", + "name": "lookup", + "arguments": {"城市": "北京"}, + "tool_call_id": "call-2", + "fidelity": {"arguments": '{"城市": "北京"}'}, + }, + ] + assert minimax_m3.transform_uni_message_to_model_input([replay_functions]) == [ + { + "type": "function_call", + "call_id": "call-1", + "name": "lookup", + "arguments": '{"城市": "上海"}', + }, + { + "type": "function_call", + "call_id": "call-2", + "name": "lookup", + "arguments": '{"城市": "北京"}', + }, + ] + + precision_arguments = '{"large":9007199254740993,"decimal":1.0}' + precision_function_event = minimax_m3.transform_model_output_to_uni_event( + { + "type": "response.output_item.done", + "output_index": 0, + "item": { + "id": "function-precision", + "status": "completed", + "type": "function_call", + "call_id": "call-precision", + "name": "lookup", + "arguments": precision_arguments, + }, + } + ) + precision_message = minimax_m3.concat_uni_events_to_uni_message([precision_function_event]) + assert minimax_m3.transform_uni_message_to_model_input([precision_message]) == [ + { + "type": "function_call", + "call_id": "call-precision", + "name": "lookup", + "arguments": precision_arguments, + } + ] + + serialized_arguments_input = minimax_m3.transform_uni_message_to_model_input( + [ + { + "role": "assistant", + "content_items": [ + { + "type": "tool_call", + "name": "lookup", + "arguments": {"value": True}, + "tool_call_id": "call-bool", + "fidelity": {"arguments": '{"value":1}'}, + } + ], + } + ] + ) + assert serialized_arguments_input[0]["arguments"] == '{"value":true}' + + with pytest.raises(ValueError, match="valid id, summary, and content"): + minimax_m3.transform_uni_message_to_model_input( + [ + { + "role": "assistant", + "content_items": [ + { + "type": "thinking", + "thinking": "invalid", + "fidelity": { + "wire_item": { + "id": 123, + "type": "reasoning", + "summary": "invalid", + "content": [], + } + }, + } + ], + } + ] + ) + + completed_event = minimax_m3.transform_model_output_to_uni_event( + { + "type": "response.completed", + "response": { + "output": [function_wire_item, second_function_wire_item], + "usage": { + "input_tokens": 10, + "input_tokens_details": {"cached_tokens": 2}, + "output_tokens": 6, + "output_tokens_details": {"reasoning_tokens": 1}, + }, + }, + } + ) + assert completed_event["finish_reason"] == "tool_call" + assert completed_event["usage_metadata"] == { + "cached_tokens": 2, + "prompt_tokens": 8, + "thoughts_tokens": 1, + "response_tokens": 5, + } + + partial_start = minimax_m3.transform_model_output_to_uni_event( + { + "type": "response.output_item.added", + "output_index": 3, + "item": { + "id": "function-partial", + "type": "function_call", + "call_id": "call-partial", + "name": "lookup", + "arguments": "", + }, + } + ) + partial_delta = minimax_m3.transform_model_output_to_uni_event( + { + "type": "response.function_call_arguments.delta", + "item_id": "function-partial", + "output_index": 3, + "delta": '{"value":1}', + } + ) + assert partial_start["content_items"][0]["fidelity"] == { + "item_id": "function-partial", + "output_index": 3, + } + assert partial_delta["content_items"][0]["fidelity"] == { + "item_id": "function-partial", + "output_index": 3, + } + + incomplete_event = minimax_m3.transform_model_output_to_uni_event( + { + "type": "response.incomplete", + "response": { + "output": [], + "usage": None, + "incomplete_details": {"reason": "max_output_tokens"}, + }, + } + ) + assert incomplete_event["finish_reason"] == "length" + with pytest.raises(RuntimeError, match="provider_failure"): + minimax_m3.transform_model_output_to_uni_event( + { + "type": "response.failed", + "response": { + "id": "response-1", + "error": {"code": "provider_failure", "message": "failed"}, + }, + } + ) + with pytest.raises(RuntimeError, match="bad_request"): + minimax_m3.transform_model_output_to_uni_event( + {"type": "error", "error": {"code": "bad_request", "message": "invalid"}} + ) + kimi = next(entry for entry in entries if entry["model"] == "kimi-k3") assert kimi["base_url"] == "https://api.moonshot.cn/v1" assert kimi["client"] == "kimi-k3" @@ -495,10 +833,19 @@ async def test_tool_use(model: Model): } config = {"tools": [weather_tool]} + tool_name = None + tool_arguments = {} tool_call_id = None partial_tool_call_data = {} - message1 = {"role": "user", "content_items": [{"type": "text", "text": "What is the weather in San Francisco?"}]} + tool_prompt = "What is the weather in San Francisco?" + if model.requires_explicit_tool_prompt: + tool_prompt = ( + "You must invoke get_weather exactly once with location San Francisco. " + "Your only allowed action before the tool result is that function call; return no text before its result. " + "After the result is provided, answer using the returned weather." + ) + message1 = {"role": "user", "content_items": [{"type": "text", "text": tool_prompt}]} async for event in client.streaming_response_stateful(message=message1, config=config): await _check_event_integrity(event) for item in event["content_items"]: @@ -641,7 +988,9 @@ async def test_tool_result_with_image(model: Model): # Define a tool that returns an image image_tool = { "name": "get_image", - "description": "Get an image URL", + "description": ( + "Retrieve an image URL for a numeric seed." if model.requires_explicit_tool_prompt else "Get an image URL" + ), "parameters": { "type": "object", "properties": { @@ -655,12 +1004,16 @@ async def test_tool_result_with_image(model: Model): } config = {"tools": [image_tool]} + tool_name = None tool_call_id = None - message1 = { - "role": "user", - "content_items": [{"type": "text", "text": "Get me a random image and describe it briefly."}], - } + tool_prompt = "Get me a random image and describe it briefly." + if model.requires_explicit_tool_prompt: + tool_prompt = ( + "You must invoke the get_image function exactly once with seed 42. " + "Your only allowed action in this turn is that function call; return no text before its result." + ) + message1 = {"role": "user", "content_items": [{"type": "text", "text": tool_prompt}]} async for event in client.streaming_response_stateful(message=message1, config=config): await _check_event_integrity(event) for item in event["content_items"]: @@ -676,7 +1029,11 @@ async def test_tool_result_with_image(model: Model): "content_items": [ { "type": "tool_result", - "text": "Here is the result image:", + "text": ( + "Here is the result image. Describe it briefly." + if model.requires_explicit_tool_prompt + else "Here is the result image:" + ), "images": [IMAGE], "tool_call_id": tool_call_id, } diff --git a/src_ts/src/autoClient.ts b/src_ts/src/autoClient.ts index d4a491c4..6d3dd012 100644 --- a/src_ts/src/autoClient.ts +++ b/src_ts/src/autoClient.ts @@ -25,6 +25,7 @@ import { KimiK3Client } from "./kimi_k3"; import { OpenaiClient } from "./openai"; import { OpenaiEmbeddingClient } from "./openai_embedding"; import { DeepSeekV4Client } from "./deepseek_v4"; +import { MiniMaxM3Client } from "./minimax_m3"; import { UniConfig, UniEvent, UniMessage } from "./types"; /** @@ -72,12 +73,11 @@ export class AutoLLMClient extends LLMClient { baseUrl?: string | null, clientType?: string | null, ): LLMClient { - clientType = ( - clientType || - process.env.CLIENT_TYPE || - model.toLowerCase() - ).toLowerCase(); + clientType = (clientType || process.env.CLIENT_TYPE || model).toLowerCase(); + if (clientType === "minimax-m3") { + return new MiniMaxM3Client({ model, apiKey, baseUrl }); + } // gemini-3.6 must be matched before the broader gemini-3 prefix below if ( clientType.includes("gemini-3.6") || @@ -130,7 +130,10 @@ export class AutoLLMClient extends LLMClient { } else { throw new Error( `${clientType} is not supported. ` + - "Supported client types: gemini-3.6, gemini-3, claude-5, claude-4-8, claude-4-7, claude-4-6, gpt-5.5, gpt-5.4, glm-5.2, glm-5.1, kimi-k3, kimi-k2.6, kimi-k2.5, deepseek-v4, openai-embedding, openai.", + "Supported client types: minimax-m3, gemini-3.6, gemini-3, " + + "claude-5, claude-4-8, claude-4-7, " + + "claude-4-6, gpt-5.5, gpt-5.4, glm-5.2, glm-5.1, kimi-k3, kimi-k2.6, kimi-k2.5, " + + "deepseek-v4, openai-embedding, openai.", ); } } diff --git a/src_ts/src/minimax_m3/client.ts b/src_ts/src/minimax_m3/client.ts new file mode 100644 index 00000000..0f350f54 --- /dev/null +++ b/src_ts/src/minimax_m3/client.ts @@ -0,0 +1,641 @@ +// Copyright 2025 Prism Shadow. and/or its affiliates +// +// 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. + +import { isDeepStrictEqual } from "node:util"; +import OpenAI from "openai"; +import type { + Response, + ResponseCreateParamsStreaming, + ResponseStreamEvent, +} from "openai/resources/responses/responses"; +import { LLMClient } from "../baseClient"; +import { parseToolCallArguments, UnsupportedParameterError } from "../errors"; +import { + EventType, + FinishReason, + PartialContentItem, + PromptCaching, + ThinkingLevel, + ToolChoice, + UniConfig, + UniEvent, + UniMessage, + UsageMetadata, +} from "../types"; + +const DEFAULT_BASE_URL = "https://api.minimax.io/v1"; + +type JsonValue = string | number | boolean | null | JsonValue[] | JsonObject; + +interface JsonObject { + [key: string]: JsonValue; +} + +type MiniMaxReasoningEffort = "none" | "low" | "medium" | "high"; + +interface MiniMaxFunctionTool { + type: "function"; + name: string; + description: string; + parameters?: Record; +} + +interface MiniMaxResponseConfig { + model: string; + store: false; + instructions?: string; + max_output_tokens?: number; + temperature?: number; + reasoning?: { effort: MiniMaxReasoningEffort }; + tools?: MiniMaxFunctionTool[]; + tool_choice?: "auto" | "none"; +} + +interface MiniMaxInputTextContent extends JsonObject { + type: "input_text"; + text: string; +} + +interface MiniMaxOutputTextContent extends JsonObject { + type: "output_text"; + text: string; +} + +interface MiniMaxInputImageContent extends JsonObject { + type: "input_image"; + image_url: string; +} + +type MiniMaxMessageContent = + | MiniMaxInputTextContent + | MiniMaxOutputTextContent + | MiniMaxInputImageContent; + +type MiniMaxToolOutputContent = + | MiniMaxInputTextContent + | MiniMaxInputImageContent; + +interface MiniMaxMessageInput { + type?: "message"; + role: UniMessage["role"]; + content: JsonObject[]; +} + +interface MiniMaxReasoningInput extends JsonObject { + id: string; + type: "reasoning"; + summary: JsonValue[]; + content: JsonValue[]; +} + +interface MiniMaxFunctionCallInput extends JsonObject { + type: "function_call"; + call_id: string; + name: string; + arguments: string; +} + +interface MiniMaxFunctionCallOutputInput extends JsonObject { + type: "function_call_output"; + call_id: string; + output: string | MiniMaxToolOutputContent[]; +} + +type MiniMaxInputItem = + | MiniMaxMessageInput + | MiniMaxReasoningInput + | MiniMaxFunctionCallInput + | MiniMaxFunctionCallOutputInput; + +interface MiniMaxResponseCreateParamsStreaming extends MiniMaxResponseConfig { + input: MiniMaxInputItem[]; + stream: true; +} + +const WIRE_ITEM_FIDELITY_KEY = "wire_item"; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function isJsonValue(value: unknown): value is JsonValue { + if ( + value === null || + typeof value === "string" || + typeof value === "boolean" + ) { + return true; + } + if (typeof value === "number") { + return Number.isFinite(value); + } + if (Array.isArray(value)) { + return value.every(isJsonValue); + } + return isJsonObject(value); +} + +function isJsonObject(value: unknown): value is JsonObject { + if (!isRecord(value)) { + return false; + } + const prototype = Object.getPrototypeOf(value); + if (prototype !== Object.prototype && prototype !== null) { + return false; + } + return Object.values(value).every(isJsonValue); +} + +function requireJsonObject(value: unknown, context: string): JsonObject { + if (!isJsonObject(value)) { + throw new Error(`${context} must contain only JSON-style values.`); + } + return value; +} + +function isJsonArray(value: JsonValue | undefined): value is JsonValue[] { + return Array.isArray(value); +} + +function wireItemFromFidelity( + fidelity: unknown, + context: string, +): JsonObject | undefined { + if (!isRecord(fidelity) || !(WIRE_ITEM_FIDELITY_KEY in fidelity)) { + return undefined; + } + return requireJsonObject( + fidelity[WIRE_ITEM_FIDELITY_KEY], + `${context} wire item`, + ); +} + +function legacyWireItemFromFidelity(fidelity: JsonObject): JsonObject { + const wireItem: JsonObject = {}; + for (const [key, value] of Object.entries(fidelity)) { + if (key !== WIRE_ITEM_FIDELITY_KEY && key !== "phase") { + wireItem[key] = value; + } + } + return wireItem; +} + +function wireContentText( + wireItem: JsonObject, + contentType: string, +): string | undefined { + const content = wireItem.content; + if (!Array.isArray(content)) { + return undefined; + } + + const textParts: string[] = []; + for (const part of content) { + if (!isJsonObject(part) || part.type !== contentType) { + continue; + } + if (typeof part.text !== "string") { + return undefined; + } + textParts.push(part.text); + } + return textParts.length > 0 ? textParts.join("") : undefined; +} + + +function transformUsage(response: Response): UsageMetadata | null { + const usage = response.usage; + if (!usage) { + return null; + } + + const cachedTokens = usage.input_tokens_details.cached_tokens ?? 0; + const reasoningTokens = usage.output_tokens_details.reasoning_tokens ?? 0; + return { + cached_tokens: cachedTokens, + prompt_tokens: usage.input_tokens - cachedTokens, + thoughts_tokens: reasoningTokens, + response_tokens: usage.output_tokens - reasoningTokens, + }; +} + +function transformFinishReason( + response: Response, + eventType: "response.completed" | "response.incomplete", +): FinishReason { + if (eventType === "response.incomplete") { + const incompleteReason = response.incomplete_details?.reason; + if (incompleteReason === "max_output_tokens") { + return "length"; + } + if (incompleteReason === "content_filter") { + return "stop"; + } + return "unknown"; + } + if (response.output.some((item) => item.type === "function_call")) { + return "tool_call"; + } + return "stop"; +} + +function responseFailure(response: Response): Error { + if (response.error) { + return new Error( + `MiniMax response ${response.id} failed (${response.error.code}): ${response.error.message}`, + ); + } + return new Error( + `MiniMax response ${response.id} failed without error details from the API.`, + ); +} + +/** MiniMax M3 client using MiniMax's Responses API. */ +export class MiniMaxM3Client extends LLMClient { + protected _model: string; + private _client: OpenAI; + + constructor(options: { + model: string; + apiKey?: string; + baseUrl?: string | null; + clientType?: string | null; + }) { + super(); + if (options.model.toLowerCase() !== "minimax-m3") { + throw new Error(`${options.model} is not supported by MiniMaxM3Client.`); + } + this._model = options.model; + this._client = new OpenAI({ + apiKey: options.apiKey || process.env.MINIMAX_API_KEY || undefined, + baseURL: + options.baseUrl || process.env.MINIMAX_BASE_URL || DEFAULT_BASE_URL, + }); + } + + private _convertThinkingLevelToEffort( + thinkingLevel: ThinkingLevel, + ): MiniMaxReasoningEffort { + const mapping: Record = { + [ThinkingLevel.NONE]: "none", + [ThinkingLevel.LOW]: "low", + [ThinkingLevel.MEDIUM]: "medium", + [ThinkingLevel.HIGH]: "high", + [ThinkingLevel.XHIGH]: "high", + }; + return mapping[thinkingLevel]; + } + + private _convertToolChoice(toolChoice: ToolChoice): "auto" | "none" { + if (toolChoice === "auto" || toolChoice === "none") { + return toolChoice; + } + throw new UnsupportedParameterError({ + client: this.constructor.name, + parameter: "tool_choice", + message: "MiniMax Responses API does not support required or named tool selection.", + }); + } + + transformUniConfigToModelConfig(config: UniConfig): MiniMaxResponseConfig { + const minimaxConfig: MiniMaxResponseConfig = { + model: this._model, + store: false, + }; + + if (config.system_prompt !== undefined) { + minimaxConfig.instructions = config.system_prompt; + } + if (config.max_tokens !== undefined) { + minimaxConfig.max_output_tokens = config.max_tokens; + } + if (config.temperature !== undefined) { + if (config.temperature < 0 || config.temperature > 1) { + throw new UnsupportedParameterError({ + client: this.constructor.name, + parameter: "temperature", + message: "MiniMax Responses API does not support temperatures outside the range 0 to 1.", + }); + } + minimaxConfig.temperature = config.temperature; + } + if (config.thinking_level !== undefined) { + minimaxConfig.reasoning = { + effort: this._convertThinkingLevelToEffort(config.thinking_level), + }; + } + if (config.tools !== undefined) { + minimaxConfig.tools = config.tools.map((tool) => ({ + type: "function", + name: tool.name, + description: tool.description, + ...(tool.parameters !== undefined + ? { parameters: tool.parameters } + : {}), + })); + } + if (config.tool_choice !== undefined) { + minimaxConfig.tool_choice = this._convertToolChoice(config.tool_choice); + } + if (config.prompt_caching === PromptCaching.DISABLE) { + throw new UnsupportedParameterError({ + client: this.constructor.name, + parameter: "prompt_caching", + message: "MiniMax Responses API does not support disabling its automatic prompt cache.", + }); + } + if (config.prompt_caching === PromptCaching.ENHANCE) { + throw new UnsupportedParameterError({ + client: this.constructor.name, + parameter: "prompt_caching", + message: "MiniMax Responses API does not support enhancing its automatic prompt cache.", + }); + } + + return minimaxConfig; + } + + transformUniMessageToModelInput(messages: UniMessage[]): MiniMaxInputItem[] { + const inputList: MiniMaxInputItem[] = []; + + for (const message of messages) { + let contentItems: MiniMaxMessageContent[] = []; + const flushContentItems = (): void => { + if (contentItems.length > 0) { + inputList.push({ role: message.role, content: contentItems }); + contentItems = []; + } + }; + + for (const item of message.content_items) { + if (item.type === "text") { + if (message.role === "user") { + contentItems.push({ type: "input_text", text: item.text }); + } else { + contentItems.push({ type: "output_text", text: item.text }); + } + } else if (item.type === "image_url") { + contentItems.push({ type: "input_image", image_url: item.image_url }); + } else if (item.type === "thinking") { + flushContentItems(); + const fidelity = requireJsonObject( + item.fidelity ?? {}, + "MiniMax reasoning fidelity", + ); + const wireItem = + wireItemFromFidelity(fidelity, "MiniMax reasoning fidelity") ?? + legacyWireItemFromFidelity(fidelity); + const id = wireItem.id; + const summary = wireItem.summary; + const wireContent = wireItem.content; + if ( + typeof id !== "string" || + !isJsonArray(summary) || + !isJsonArray(wireContent) + ) { + throw new Error( + "MiniMax reasoning replay requires valid id, summary, and content fidelity.", + ); + } + const content = + wireContentText(wireItem, "reasoning_text") === item.thinking + ? wireContent + : [{ type: "reasoning_text", text: item.thinking }]; + inputList.push({ + ...wireItem, + id, + type: "reasoning", + summary, + content, + }); + } else if (item.type === "tool_call") { + flushContentItems(); + let serializedArguments = JSON.stringify(item.arguments); + if (serializedArguments === undefined) { + throw new Error( + `MiniMax tool call ${item.name} arguments could not be serialized.`, + ); + } + const rawArguments = item.fidelity?.arguments; + if ( + typeof rawArguments === "string" && + isDeepStrictEqual( + parseToolCallArguments( + rawArguments, + this.constructor.name, + item.name, + item.tool_call_id, + ), + item.arguments, + ) + ) { + serializedArguments = rawArguments; + } + inputList.push({ + type: "function_call", + call_id: item.tool_call_id, + name: item.name, + arguments: serializedArguments, + }); + } else if (item.type === "tool_result") { + flushContentItems(); + let output: string | MiniMaxToolOutputContent[] = item.text; + if (item.images?.length) { + const outputItems: MiniMaxToolOutputContent[] = [ + { type: "input_text", text: item.text }, + ]; + for (const imageUrl of item.images) { + outputItems.push({ type: "input_image", image_url: imageUrl }); + } + output = outputItems; + } + inputList.push({ + type: "function_call_output", + call_id: item.tool_call_id, + output, + }); + } else { + throw new Error(`Unknown item: ${JSON.stringify(item)}`); + } + } + + flushContentItems(); + } + + return inputList; + } + + transformModelOutputToUniEvent(modelOutput: ResponseStreamEvent): UniEvent { + let eventType: EventType = "unused"; + const contentItems: PartialContentItem[] = []; + let usageMetadata: UsageMetadata | null = null; + let finishReason: FinishReason | null = null; + + switch (modelOutput.type) { + case "response.output_text.delta": + eventType = "delta"; + contentItems.push({ + type: "text", + text: modelOutput.delta, + fidelity: { phase: modelOutput.item_id }, + }); + break; + case "response.reasoning_text.delta": + eventType = "delta"; + contentItems.push({ + type: "thinking", + thinking: modelOutput.delta, + }); + break; + case "response.output_item.added": + if (modelOutput.item.type === "function_call") { + if (modelOutput.item.id === undefined) { + throw new Error( + "MiniMax function-call start event is missing its output item id.", + ); + } + eventType = "start"; + contentItems.push({ + type: "partial_tool_call", + name: modelOutput.item.name, + arguments: "", + tool_call_id: modelOutput.item.call_id, + fidelity: { + item_id: modelOutput.item.id, + output_index: modelOutput.output_index, + }, + }); + } + break; + case "response.function_call_arguments.delta": + eventType = "delta"; + contentItems.push({ + type: "partial_tool_call", + name: "", + arguments: modelOutput.delta, + tool_call_id: "", + fidelity: { + item_id: modelOutput.item_id, + output_index: modelOutput.output_index, + }, + }); + break; + case "response.output_item.done": { + const wireItem = requireJsonObject( + modelOutput.item, + "MiniMax output item", + ); + if (modelOutput.item.type === "reasoning") { + eventType = "delta"; + contentItems.push({ + type: "thinking", + thinking: "", + fidelity: { [WIRE_ITEM_FIDELITY_KEY]: wireItem }, + }); + } else if (modelOutput.item.type === "function_call") { + eventType = "delta"; + contentItems.push({ + type: "tool_call", + name: modelOutput.item.name, + arguments: parseToolCallArguments( + modelOutput.item.arguments, + this.constructor.name, + modelOutput.item.name, + modelOutput.item.call_id, + ), + tool_call_id: modelOutput.item.call_id, + fidelity: { arguments: modelOutput.item.arguments }, + }); + } else if (modelOutput.item.type === "message") { + eventType = "delta"; + const phase = + typeof wireItem.id === "string" + ? wireItem.id + : `output-${modelOutput.output_index}`; + contentItems.push({ + type: "text", + text: "", + fidelity: { phase }, + }); + } + break; + } + case "response.completed": + case "response.incomplete": + eventType = "stop"; + usageMetadata = transformUsage(modelOutput.response); + finishReason = transformFinishReason( + modelOutput.response, + modelOutput.type, + ); + break; + case "response.failed": + throw responseFailure(modelOutput.response); + case "error": { + const code = modelOutput.code ? ` (${modelOutput.code})` : ""; + const parameter = modelOutput.param + ? ` for parameter ${modelOutput.param}` + : ""; + throw new Error( + `MiniMax stream error${code}${parameter}: ${modelOutput.message}`, + ); + } + case "response.created": + case "response.in_progress": + case "response.output_text.done": + case "response.reasoning_text.done": + case "response.content_part.added": + case "response.content_part.done": + case "response.function_call_arguments.done": + break; + default: + throw new Error(`Unknown output: ${JSON.stringify(modelOutput)}`); + } + + return { + role: "assistant", + event_type: eventType, + content_items: contentItems, + usage_metadata: usageMetadata, + finish_reason: finishReason, + }; + } + + async *_streamingResponseInternal(options: { + messages: UniMessage[]; + config: UniConfig; + signal?: AbortSignal; + }): AsyncGenerator { + const minimaxConfig = this.transformUniConfigToModelConfig(options.config); + const inputList = this.transformUniMessageToModelInput(options.messages); + const params: MiniMaxResponseCreateParamsStreaming = { + ...minimaxConfig, + input: inputList, + stream: true, + }; + + // MiniMax accepts output_text assistant inputs and function tools without + // OpenAI's required strict field, so narrow the compatibility cast to this boundary. + const stream = await this._client.responses.create( + params as ResponseCreateParamsStreaming, + { signal: options.signal }, + ); + for await (const modelEvent of stream) { + const event = this.transformModelOutputToUniEvent(modelEvent); + if (event.event_type !== "unused") { + yield event; + } + } + } +} diff --git a/src_ts/src/minimax_m3/index.ts b/src_ts/src/minimax_m3/index.ts new file mode 100644 index 00000000..e3ba3ad8 --- /dev/null +++ b/src_ts/src/minimax_m3/index.ts @@ -0,0 +1,15 @@ +// Copyright 2025 Prism Shadow. and/or its affiliates +// +// 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. + +export { MiniMaxM3Client } from "./client"; diff --git a/src_ts/src/registry.ts b/src_ts/src/registry.ts index 66af42f7..7fb207d1 100644 --- a/src_ts/src/registry.ts +++ b/src_ts/src/registry.ts @@ -57,6 +57,7 @@ const MOONSHOT = "https://api.moonshot.cn/v1"; const DEEPSEEK = "https://api.deepseek.com"; const OPENROUTER = "https://openrouter.ai/api/v1"; const SILICONFLOW = "https://api.siliconflow.cn/v1"; +const MINIMAX = "https://api.minimax.io/v1"; // Display convention shared with the AgentHub apps: prices are stored in USD // (official CNY list prices pre-converted at 7 CNY/USD), so requesting CNY @@ -182,6 +183,14 @@ const SUPPORTED_MODELS: SupportedModel[] = [ context_window: 1050000, pricing: usd(5.0, 30.0, 0.5), }, + { + model: "MiniMax-M3", + base_url: MINIMAX, + client: "minimax-m3", + input_modalities: ["Text", "Image"], + output_modalities: ["Text"], + context_window: 1000000, + }, { model: "text-embedding-3-large", base_url: OPENAI, diff --git a/src_ts/tests/client.test.ts b/src_ts/tests/client.test.ts index caf1ee07..595922f9 100644 --- a/src_ts/tests/client.test.ts +++ b/src_ts/tests/client.test.ts @@ -14,7 +14,13 @@ import { AutoLLMClient } from "../src/autoClient"; import { listSupportedModels } from "../src/registry"; -import { ThinkingLevel, UniMessage, UniConfig, UniEvent } from "../src/types"; +import { + PromptCaching, + ThinkingLevel, + UniMessage, + UniConfig, + UniEvent, +} from "../src/types"; import { expect, describe, test } from "@jest/globals"; const IMAGE = @@ -30,6 +36,7 @@ interface Model { supportAudioGeneration: boolean; supportEmbedding: boolean; clientType?: string; + requiresExplicitToolPrompt?: boolean; provider: | "official" | "bedrock" @@ -153,6 +160,21 @@ if (process.env.MOONSHOT_API_KEY) { }); } +if (process.env.MINIMAX_API_KEY) { + AVAILABLE_MODELS.push({ + name: "MiniMax-M3", + supportTextGeneration: true, + supportTemperature: true, + supportImageUnderstanding: true, + supportImageGeneration: false, + supportAudioGeneration: false, + supportEmbedding: false, + clientType: "minimax-m3", + requiresExplicitToolPrompt: true, + provider: "official", + }); +} + if (process.env.DEEPSEEK_API_KEY) { AVAILABLE_MODELS.push({ name: "deepseek-v4-flash", @@ -659,11 +681,14 @@ if (AVAILABLE_MODELS.length > 0) { let toolName: string | undefined; let toolArguments: Record | undefined; + const toolPrompt = model.requiresExplicitToolPrompt + ? "You must invoke get_weather exactly once with location San Francisco. " + + "Your only allowed action before the tool result is that function call; return no text before its result. " + + "After the result is provided, answer using the returned weather." + : "What is the weather in San Francisco?"; const message1: UniMessage = { role: "user", - content_items: [ - { type: "text", text: "What is the weather in San Francisco?" }, - ], + content_items: [{ type: "text", text: toolPrompt }], }; for await (const event of client.streamingResponseStateful({ message: message1, @@ -850,7 +875,9 @@ if (AVAILABLE_MODELS.length > 0) { const imageTool = { name: "get_image", - description: "Get an image URL", + description: model.requiresExplicitToolPrompt + ? "Retrieve an image URL for a numeric seed." + : "Get an image URL", parameters: { type: "object", properties: { @@ -867,14 +894,13 @@ if (AVAILABLE_MODELS.length > 0) { let toolCallId: string | undefined; let toolName: string | undefined; + const toolPrompt = model.requiresExplicitToolPrompt + ? "You must invoke the get_image function exactly once with seed 42. " + + "Your only allowed action in this turn is that function call; return no text before its result." + : "Get me a random image and describe it briefly."; const message1: UniMessage = { role: "user", - content_items: [ - { - type: "text", - text: "Get me a random image and describe it briefly.", - }, - ], + content_items: [{ type: "text", text: toolPrompt }], }; for await (const event of client.streamingResponseStateful({ message: message1, @@ -897,7 +923,9 @@ if (AVAILABLE_MODELS.length > 0) { content_items: [ { type: "tool_result", - text: "Here is the result image:", + text: model.requiresExplicitToolPrompt + ? "Here is the result image. Describe it briefly." + : "Here is the result image:", images: [IMAGE], tool_call_id: toolCallId || "", }, @@ -1084,6 +1112,391 @@ test("should list supported model entries", () => { expect(glm52?.base_url).toBe("https://openrouter.ai/api/v1"); expect(glm52?.client).toBe("glm-5.2"); + const minimaxEntries = entries.filter( + (entry) => entry.base_url === "https://api.minimax.io/v1", + ); + expect(minimaxEntries).toEqual([ + { + model: "MiniMax-M3", + base_url: "https://api.minimax.io/v1", + client: "minimax-m3", + input_modalities: ["Text", "Image"], + output_modalities: ["Text"], + context_window: 1000000, + }, + ]); + + const minimaxM3 = new AutoLLMClient({ + model: "MiniMax-M3", + apiKey: "test-key", + }); + for (const clientType of [undefined, "minimax-m3"]) { + expect( + () => + new AutoLLMClient({ + model: "MiniMax-M3-preview", + apiKey: "test-key", + clientType, + }), + ).toThrow("not supported"); + } + + const m3ThinkingEfforts = [ + [ThinkingLevel.NONE, "none"], + [ThinkingLevel.LOW, "low"], + [ThinkingLevel.MEDIUM, "medium"], + [ThinkingLevel.HIGH, "high"], + [ThinkingLevel.XHIGH, "high"], + ] as const; + for (const [thinkingLevel, effort] of m3ThinkingEfforts) { + expect( + minimaxM3.transformUniConfigToModelConfig({ + thinking_level: thinkingLevel, + }).reasoning, + ).toEqual({ effort }); + } + expect( + minimaxM3.transformUniConfigToModelConfig({ + prompt_caching: PromptCaching.ENABLE, + }), + ).not.toHaveProperty("prompt_caching"); + for (const promptCaching of [ + PromptCaching.DISABLE, + PromptCaching.ENHANCE, + ]) { + expect(() => + minimaxM3.transformUniConfigToModelConfig({ + prompt_caching: promptCaching, + }), + ).toThrow("does not support"); + } + expect(() => + minimaxM3.transformUniConfigToModelConfig({ tool_choice: "required" }), + ).toThrow("does not support"); + expect(() => + minimaxM3.transformUniConfigToModelConfig({ tool_choice: ["lookup"] }), + ).toThrow("does not support"); + + const reasoningWireItem = { + id: "reasoning-1", + status: "completed", + summary: [], + content: [{ type: "reasoning_text", text: "reasoning" }], + type: "reasoning", + }; + const functionWireItem = { + id: "function-1", + status: "completed", + type: "function_call", + call_id: "call-1", + name: "lookup", + arguments: '{"城市": "上海"}', + }; + expect( + minimaxM3.transformUniMessageToModelInput([ + { + role: "assistant", + content_items: [ + { type: "text", text: "before" }, + { + type: "thinking", + thinking: "reasoning", + fidelity: { wire_item: reasoningWireItem }, + }, + { + type: "tool_call", + name: "lookup", + arguments: { 城市: "上海" }, + tool_call_id: "call-1", + }, + { type: "text", text: "after" }, + ], + }, + ]), + ).toEqual([ + { + role: "assistant", + content: [{ type: "output_text", text: "before" }], + }, + reasoningWireItem, + { + type: "function_call", + call_id: "call-1", + name: "lookup", + arguments: '{"城市":"上海"}', + }, + { + role: "assistant", + content: [{ type: "output_text", text: "after" }], + }, + ]); + + const reasoningEvents = [ + minimaxM3.transformModelOutputToUniEvent({ + type: "response.reasoning_text.delta", + delta: "reasoning", + }), + minimaxM3.transformModelOutputToUniEvent({ + type: "response.output_item.done", + output_index: 0, + item: reasoningWireItem, + }), + ]; + const replayReasoning = + minimaxM3.concatUniEventsToUniMessage(reasoningEvents); + expect(replayReasoning.content_items).toEqual([ + { + type: "thinking", + thinking: "reasoning", + fidelity: { wire_item: reasoningWireItem }, + }, + ]); + expect( + minimaxM3.transformUniMessageToModelInput([replayReasoning]), + ).toEqual([reasoningWireItem]); + + const messageWireItem = { + id: "message-1", + type: "message", + status: "completed", + role: "assistant", + content: [ + { + type: "output_text", + text: "exact response", + annotations: [], + logprobs: null, + }, + ], + phase: null, + }; + const messageEvents = [ + minimaxM3.transformModelOutputToUniEvent({ + type: "response.output_text.delta", + item_id: "message-1", + output_index: 0, + content_index: 0, + delta: "exact response", + }), + minimaxM3.transformModelOutputToUniEvent({ + type: "response.output_item.done", + output_index: 0, + item: messageWireItem, + }), + ]; + const replayMessage = minimaxM3.concatUniEventsToUniMessage(messageEvents); + expect(replayMessage.content_items).toEqual([ + { + type: "text", + text: "exact response", + fidelity: { phase: "message-1" }, + }, + ]); + expect(minimaxM3.transformUniMessageToModelInput([replayMessage])).toEqual([ + { + role: "assistant", + content: [{ type: "output_text", text: "exact response" }], + }, + ]); + + const secondFunctionWireItem = { + ...functionWireItem, + id: "function-2", + call_id: "call-2", + arguments: '{"城市": "北京"}', + }; + const functionEvents = [functionWireItem, secondFunctionWireItem].map( + (wireItem, outputIndex) => + minimaxM3.transformModelOutputToUniEvent({ + type: "response.output_item.done", + output_index: outputIndex, + item: wireItem, + }), + ); + const replayFunctions = + minimaxM3.concatUniEventsToUniMessage(functionEvents); + expect(replayFunctions.content_items).toEqual([ + { + type: "tool_call", + name: "lookup", + arguments: { 城市: "上海" }, + tool_call_id: "call-1", + fidelity: { arguments: '{"城市": "上海"}' }, + }, + { + type: "tool_call", + name: "lookup", + arguments: { 城市: "北京" }, + tool_call_id: "call-2", + fidelity: { arguments: '{"城市": "北京"}' }, + }, + ]); + expect( + minimaxM3.transformUniMessageToModelInput([replayFunctions]), + ).toEqual([ + { + type: "function_call", + call_id: "call-1", + name: "lookup", + arguments: '{"城市": "上海"}', + }, + { + type: "function_call", + call_id: "call-2", + name: "lookup", + arguments: '{"城市": "北京"}', + }, + ]); + + const precisionArguments = '{"large":9007199254740993,"decimal":1.0}'; + const precisionFunctionEvent = minimaxM3.transformModelOutputToUniEvent({ + type: "response.output_item.done", + output_index: 0, + item: { + id: "function-precision", + status: "completed", + type: "function_call", + call_id: "call-precision", + name: "lookup", + arguments: precisionArguments, + }, + }); + const precisionMessage = minimaxM3.concatUniEventsToUniMessage([ + precisionFunctionEvent, + ]); + expect( + minimaxM3.transformUniMessageToModelInput([precisionMessage]), + ).toEqual([ + { + type: "function_call", + call_id: "call-precision", + name: "lookup", + arguments: precisionArguments, + }, + ]); + + const serializedArgumentsInput = minimaxM3.transformUniMessageToModelInput([ + { + role: "assistant", + content_items: [ + { + type: "tool_call", + name: "lookup", + arguments: { value: true }, + tool_call_id: "call-bool", + fidelity: { arguments: '{"value":1}' }, + }, + ], + }, + ]); + expect(serializedArgumentsInput[0].arguments).toBe('{"value":true}'); + + expect(() => + minimaxM3.transformUniMessageToModelInput([ + { + role: "assistant", + content_items: [ + { + type: "thinking", + thinking: "invalid", + fidelity: { + wire_item: { + id: 123, + type: "reasoning", + summary: "invalid", + content: [], + }, + }, + }, + ], + }, + ]), + ).toThrow("valid id, summary, and content"); + + const completedEvent = minimaxM3.transformModelOutputToUniEvent({ + type: "response.completed", + response: { + output: [functionWireItem, secondFunctionWireItem], + usage: { + input_tokens: 10, + input_tokens_details: { cached_tokens: 2 }, + output_tokens: 6, + output_tokens_details: { reasoning_tokens: 1 }, + }, + }, + }); + expect(completedEvent.finish_reason).toBe("tool_call"); + expect(completedEvent.usage_metadata).toEqual({ + cached_tokens: 2, + prompt_tokens: 8, + thoughts_tokens: 1, + response_tokens: 5, + }); + + const partialStart = minimaxM3.transformModelOutputToUniEvent({ + type: "response.output_item.added", + output_index: 3, + item: { + id: "function-partial", + type: "function_call", + call_id: "call-partial", + name: "lookup", + arguments: "", + }, + }); + const partialDelta = minimaxM3.transformModelOutputToUniEvent({ + type: "response.function_call_arguments.delta", + item_id: "function-partial", + output_index: 3, + delta: '{"value":1}', + }); + const partialStartItem = partialStart.content_items[0]; + const partialDeltaItem = partialDelta.content_items[0]; + expect(partialStartItem.type).toBe("partial_tool_call"); + expect(partialDeltaItem.type).toBe("partial_tool_call"); + if ( + partialStartItem.type !== "partial_tool_call" || + partialDeltaItem.type !== "partial_tool_call" + ) { + throw new Error("Expected MiniMax partial tool-call events."); + } + expect(partialStartItem.fidelity).toEqual({ + item_id: "function-partial", + output_index: 3, + }); + expect(partialDeltaItem.fidelity).toEqual({ + item_id: "function-partial", + output_index: 3, + }); + + const incompleteEvent = minimaxM3.transformModelOutputToUniEvent({ + type: "response.incomplete", + response: { + output: [], + usage: null, + incomplete_details: { reason: "max_output_tokens" }, + }, + }); + expect(incompleteEvent.finish_reason).toBe("length"); + expect(() => + minimaxM3.transformModelOutputToUniEvent({ + type: "response.failed", + response: { + id: "response-1", + error: { code: "provider_failure", message: "failed" }, + }, + }), + ).toThrow("provider_failure"); + expect(() => + minimaxM3.transformModelOutputToUniEvent({ + type: "error", + code: "bad_request", + message: "invalid", + param: null, + }), + ).toThrow("bad_request"); + + for (const entry of entries) { expect(entry.input_modalities.length).toBeGreaterThan(0); expect(entry.output_modalities.length).toBeGreaterThan(0);