Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions comfy_api_nodes/apis/minimax.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,45 @@ class MinimaxVideoGenerationResponse(BaseModel):
task_id: str = Field(
..., description='The task ID for the asynchronous video generation task.'
)


class MiniMaxChatModel(str, Enum):
M2_7 = 'MiniMax-M2.7'
M2_7_highspeed = 'MiniMax-M2.7-highspeed'
M2_5 = 'MiniMax-M2.5'
M2_5_highspeed = 'MiniMax-M2.5-highspeed'


class MiniMaxChatMessage(BaseModel):
role: str = Field(..., description='The role of the message author (system, user, or assistant).')
content: str = Field(..., description='The content of the message.')


class MiniMaxChatRequest(BaseModel):
model: str = Field(..., description='ID of the model to use.')
messages: list[MiniMaxChatMessage] = Field(..., description='A list of messages comprising the conversation.')
max_tokens: Optional[int] = Field(None, description='The maximum number of tokens to generate.')
temperature: Optional[float] = Field(
None,
description='Sampling temperature. Must be between 0 (exclusive) and 1 (inclusive).',
gt=0.0,
le=1.0,
)


class MiniMaxChatChoice(BaseModel):
index: int = Field(..., description='The index of the choice.')
message: MiniMaxChatMessage = Field(..., description='The generated message.')
finish_reason: Optional[str] = Field(None, description='The reason the model stopped generating.')


class MiniMaxChatUsage(BaseModel):
prompt_tokens: int = Field(0, description='Number of tokens in the prompt.')
completion_tokens: int = Field(0, description='Number of tokens in the generated response.')
total_tokens: int = Field(0, description='Total number of tokens used.')


class MiniMaxChatResponse(BaseModel):
id: Optional[str] = Field(None, description='A unique identifier for the chat completion.')
choices: list[MiniMaxChatChoice] = Field(..., description='A list of chat completion choices.')
usage: Optional[MiniMaxChatUsage] = Field(None, description='Usage statistics for the request.')
117 changes: 117 additions & 0 deletions comfy_api_nodes/nodes_minimax.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

from comfy_api.latest import IO, ComfyExtension
from comfy_api_nodes.apis.minimax import (
MiniMaxChatMessage,
MiniMaxChatModel,
MiniMaxChatRequest,
MiniMaxChatResponse,
MinimaxFileRetrieveResponse,
MiniMaxModel,
MinimaxTaskResultResponse,
Expand Down Expand Up @@ -437,6 +441,118 @@ async def execute(
return IO.NodeOutput(await download_url_to_video_output(file_url))


class MinimaxChatNode(IO.ComfyNode):
"""
Node to generate text responses using MiniMax language models.
"""

@classmethod
def define_schema(cls) -> IO.Schema:
return IO.Schema(
node_id="MinimaxChatNode",
display_name="MiniMax Chat",
category="api node/text/MiniMax",
description="Generate text responses using MiniMax language models (MiniMax-M2.7).",
inputs=[
IO.String.Input(
"prompt",
default="",
multiline=True,
tooltip="Text prompt for the model to respond to.",
),
IO.Combo.Input(
"model",
options=MiniMaxChatModel,
default=MiniMaxChatModel.M2_7.value,
tooltip="The MiniMax model to use for text generation.",
),
IO.String.Input(
"system_prompt",
multiline=True,
optional=True,
tooltip="Optional system instructions to guide the model's behavior.",
),
IO.Int.Input(
"max_tokens",
default=4096,
min=1,
max=204800,
step=1,
tooltip="Maximum number of tokens to generate in the response.",
optional=True,
),
IO.Float.Input(
"temperature",
default=0.7,
min=0.01,
max=1.0,
step=0.01,
tooltip="Controls randomness in the response. Higher values produce more creative output.",
optional=True,
),
],
outputs=[
IO.String.Output(),
],
hidden=[
IO.Hidden.auth_token_comfy_org,
IO.Hidden.api_key_comfy_org,
IO.Hidden.unique_id,
],
is_api_node=True,
price_badge=IO.PriceBadge(
depends_on=IO.PriceBadgeDepends(widgets=["model"]),
expr="""
(
$m := widgets.model;
$contains($m, "highspeed") ? {
"type": "list_usd",
"usd": [0.00004, 0.0002],
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
}
: {
"type": "list_usd",
"usd": [0.0001, 0.0006],
"format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
}
)
""",
),
Comment on lines +503 to +520
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's confirm the file exists and examine the relevant section
cat -n comfy_api_nodes/nodes_minimax.py | sed -n '500,525p'

Repository: Comfy-Org/ComfyUI

Length of output: 1113


🌐 Web query:

MiniMax M2.5 API pricing per token cost 2024 2025

💡 Result:

MiniMax M2.5 token pricing you’ll see published publicly is not a 2024/2025 price—MiniMax’s official pay‑as‑you‑go table listing MiniMax‑M2.5 appears in the current docs/pricing pages (live as of March 2026). [1]

Official MiniMax pay‑as‑you‑go (CNY)

Prices are shown as 元 / 1,000,000 tokens: [1]

  • Input: ¥2.1 / 1M tokens → ¥0.0000021 per token
  • Output: ¥8.4 / 1M tokens → ¥0.0000084 per token
  • (Also listed) Cache read: ¥0.21 / 1M; Cache write: ¥2.625 / 1M. [1]

Common USD quotes (third‑party trackers, for reference)

Artificial Analysis lists MiniMax‑M2.5 at: [2]

  • $0.30 / 1M input tokens (=$0.00000030/token)
  • $1.20 / 1M output tokens (=$0.00000120/token)

If you specifically need what MiniMax charged in 2024 or 2025, you’ll likely need an archived snapshot or a dated MiniMax announcement—because M2.5 pricing as such doesn’t appear to be a 2024/2025 item on the current official pages. [1]

Sources: [1] MiniMax Open Platform docs – Pay‑as‑you‑go pricing table (文本/按量计费) (platform.minimaxi.com) • [2] Artificial Analysis – MiniMax‑M2.5 pricing (artificialanalysis.ai)


🌐 Web query:

MiniMax M2.5 highspeed model pricing API cost

💡 Result:

MiniMax M2.5-highspeed API cost (Pay‑as‑you‑go)

From MiniMax’s pay‑as‑you‑go pricing table (prices in CNY per 1M tokens): Input ¥4.2 / 1M, Output ¥16.8 / 1M; cache read ¥0.21 / 1M, cache write ¥2.625 / 1M. [1]

Approximate USD equivalents (using ~¥6.88 per $1): input ~$0.61 / 1M, output ~$2.44 / 1M, cache read ~$0.03 / 1M, cache write ~$0.38 / 1M. [1][2]

(Reference) MiniMax M2.5 (non‑highspeed) PAYG

Input ¥2.1 / 1M (~$0.31), Output ¥8.4 / 1M (~$1.22). [1][2]

If you meant the Coding Plan (subscription, not per-token)

Plans that include MiniMax M2.5-highspeed are $40 / $80 / $150 per month (300 / 1000 / 2000 prompts per 5 hours). [3]

Sources: [1] MiniMax PAYG pricing table, [2] USD/CNY rate reference, [3] MiniMax Coding Plan pricing.


🏁 Script executed:

# Check if this node and price_badge are new additions
git log -1 --oneline comfy_api_nodes/nodes_minimax.py
git diff HEAD~1 comfy_api_nodes/nodes_minimax.py | head -100

Repository: Comfy-Org/ComfyUI

Length of output: 339


🏁 Script executed:

# Check the structure to confirm this is new code
git status comfy_api_nodes/nodes_minimax.py
git log --oneline -5 comfy_api_nodes/nodes_minimax.py

Repository: Comfy-Org/ComfyUI

Length of output: 201


Update price badge values to match current MiniMax API pricing.

The per-1K token amounts are materially below official rates. MiniMax's current pay-as-you-go pricing is $0.30/$1.20 per 1M tokens for M2.5 and $0.61/$2.44 per 1M tokens for M2.5-highspeed, which translates to $0.0003/$0.0012 and $0.0006/$0.0024 per 1K tokens respectively.

Suggested patch
                 (
                   $m := widgets.model;
                   $contains($m, "highspeed") ? {
                     "type": "list_usd",
-                    "usd": [0.00004, 0.0002],
+                    "usd": [0.0006, 0.0024],
                     "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                   }
                   : {
                     "type": "list_usd",
-                    "usd": [0.0001, 0.0006],
+                    "usd": [0.0003, 0.0012],
                     "format": { "approximate": true, "separator": "-", "suffix": " per 1K tokens" }
                   }
                 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@comfy_api_nodes/nodes_minimax.py` around lines 503 - 520, The price badge
values for MiniMax in the IO.PriceBadge expression are outdated; update the USD
per-1K token arrays to match MiniMax API pricing by replacing the current usd
arrays in the expression for the two branches: for the "highspeed" branch (when
$contains(widgets.model, "highspeed")) set usd to [0.0006, 0.0024], and for the
default branch set usd to [0.0003, 0.0012]; keep the surrounding structure
(price_badge = IO.PriceBadge, depends_on =
IO.PriceBadgeDepends(widgets=["model"]), and the expr using $m := widgets.model
and $contains) unchanged.

)

@classmethod
async def execute(
cls,
prompt: str,
model: str = MiniMaxChatModel.M2_7.value,
system_prompt: Optional[str] = None,
max_tokens: int = 4096,
temperature: float = 0.7,
) -> IO.NodeOutput:
validate_string(prompt, field_name="prompt")

messages: list[MiniMaxChatMessage] = []
if system_prompt:
messages.append(MiniMaxChatMessage(role="system", content=system_prompt))
messages.append(MiniMaxChatMessage(role="user", content=prompt))

response = await sync_op(
cls,
ApiEndpoint(path="/proxy/minimax/chat/completions", method="POST"),
response_model=MiniMaxChatResponse,
data=MiniMaxChatRequest(
model=MiniMaxChatModel(model).value,
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
),
)

if response.choices:
return IO.NodeOutput(response.choices[0].message.content)
Comment on lines +539 to +552
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the MiniMax-related files
find . -name "*minimax*" -type f | head -20

Repository: Comfy-Org/ComfyUI

Length of output: 129


🏁 Script executed:

# Let's examine the MiniMax API models file
wc -l ./comfy_api_nodes/apis/minimax.py

Repository: Comfy-Org/ComfyUI

Length of output: 98


🏁 Script executed:

# And the nodes file
wc -l ./comfy_api_nodes/nodes_minimax.py

Repository: Comfy-Org/ComfyUI

Length of output: 99


🏁 Script executed:

# Read the MiniMaxChatRequest definition
cat -n ./comfy_api_nodes/apis/minimax.py | grep -A 50 "class MiniMaxChatRequest"

Repository: Comfy-Org/ComfyUI

Length of output: 1685


🏁 Script executed:

# Read the execute method from nodes_minimax.py around lines 539-552
sed -n '530,560p' ./comfy_api_nodes/nodes_minimax.py

Repository: Comfy-Org/ComfyUI

Length of output: 1157


🏁 Script executed:

# Check git status and history to see if this code is new
cd ./comfy_api_nodes && git log --oneline -20 -- nodes_minimax.py

Repository: Comfy-Org/ComfyUI

Length of output: 135


🏁 Script executed:

# Check if reasoning_split field was previously in the file
git log -p -- comfy_api_nodes/apis/minimax.py | grep -A 20 "reasoning_split" | head -30

Repository: Comfy-Org/ComfyUI

Length of output: 43


🏁 Script executed:

# Get more context about the MinimaxChatNode.execute method - find the full function
sed -n '520,560p' ./comfy_api_nodes/nodes_minimax.py

Repository: Comfy-Org/ComfyUI

Length of output: 1384


🏁 Script executed:

# Check the actual diff of the recent commit
git show c01e019 -- comfy_api_nodes/nodes_minimax.py | head -100

Repository: Comfy-Org/ComfyUI

Length of output: 3387


🏁 Script executed:

# Also check if there are any references to <think> or reasoning in the codebase
rg -i "think|reasoning" comfy_api_nodes/

Repository: Comfy-Org/ComfyUI

Length of output: 2830


🌐 Web query:

MiniMax API OpenAI compatible chat completions <think> markup reasoning_split parameter

💡 Result:

MiniMax’s OpenAI-compatible Chat Completions endpoint (/v1/chat/completions) can return interleaved thinking markup (e.g., <think>...</think>) in the assistant message.content for models like MiniMax-M2.5 / M2.1 / M2 (and their highspeed variants). MiniMax’s docs say that if you see <think> content in content, you must preserve it completely when storing/replaying conversation history. [1]

To avoid having <think> appear inside message.content, enable Interleaved Thinking compatible format by passing the extra parameter:

  • reasoning_split: true (aka reasoning_split=True in SDKs)

Then the model’s thinking is provided separately in message.reasoning_details (which also must be preserved completely), while message.content is the user-facing answer. [1][2]

Example (OpenAI SDK-style):

from openai import OpenAI

client = OpenAI(
  api_key="YOUR_MINIMAX_KEY",
  base_url="https://api.minimax.io/v1",
)

resp = client.chat.completions.create(
  model="MiniMax-M2.5",
  messages=[{"role":"user","content":"Explain X briefly."}],
  extra_body={"reasoning_split": True},
)

This “split thinking into reasoning_details via reasoning_split=True” behavior is explicitly documented by MiniMax. [1][2][3]

Sources: MiniMax OpenAI-compatible API docs (including <think> handling and reasoning_splitreasoning_details) [1], MiniMax “Tool Use & Interleaved Thinking” docs [2], MiniMax M2.5 release technical read (compatibility + reasoning_split) [3].

References: [1] (platform.minimax.io) [2] (platform.minimax.io) [3] (atoms.dev)

Citations:


🌐 Web query:

MiniMax M2.5 model reasoning content OpenAI API documentation

💡 Result:

MiniMax M2.5’s OpenAI-compatible Chat Completions endpoint does not use OpenAI’s documented “reasoning items” schema. Instead, MiniMax injects the model’s reasoning into the normal message.content as <think>…</think>, and MiniMax explicitly says you must preserve that <think> block verbatim in the conversation history (especially for multi-turn/tool use), because the OpenAI ChatCompletions format has no native place for it. [1], [2]

In OpenAI’s own API documentation, “reasoning content/state” is handled in the Responses API as dedicated reasoning items/streaming events (e.g., response.reasoning_text.*) and optionally encrypted reasoning you can request via include: ["reasoning.encrypted_content"] and pass back on later turns—this is separate from content and is not expressed as <think> tags. [3], [4], [5]

So: if you’re using MiniMax M2.5 via an OpenAI-compatible Chat Completions API, follow MiniMax’s docs for <think> handling; if you want OpenAI’s documented reasoning mechanism, use OpenAI’s Responses API reasoning items/encrypted content flow. [1], [3]

Sources:
[1] MiniMax “Compatible OpenAI API” docs (OpenAI-compatible endpoint; <think> in content)
[2] MiniMax “Tool Use & Interleaved Thinking” docs (why <think> is used in OpenAI-native format)
[3] OpenAI API Reference — Responses (reasoning items / reasoning.encrypted_content)
[4] OpenAI API Reference — Responses streaming (response.reasoning_text.*)
[5] OpenAI Cookbook — Responses API reasoning items (include=["reasoning.encrypted_content"])


Set reasoning_split=True to exclude <think> markup from node output.

MiniMax's OpenAI-compatible API includes <think> reasoning markup directly in message.content for MiniMax-M2.5 models by default. Without reasoning_split=True, the node will return raw reasoning tags to users. This requires adding the reasoning_split field to MiniMaxChatRequest and enabling it in the request. (platform.minimax.io)

Suggested fix
# comfy_api_nodes/apis/minimax.py
 class MiniMaxChatRequest(BaseModel):
     model: str = Field(..., description='ID of the model to use.')
     messages: list[MiniMaxChatMessage] = Field(..., description='A list of messages comprising the conversation.')
     max_tokens: Optional[int] = Field(None, description='The maximum number of tokens to generate.')
     temperature: Optional[float] = Field(
         None,
         description='Sampling temperature. Must be between 0 (exclusive) and 1 (inclusive).',
         gt=0.0,
         le=1.0,
     )
+    reasoning_split: Optional[bool] = Field(
+        None,
+        description='Return reasoning separately from content for the OpenAI-compatible MiniMax API.',
+    )

# comfy_api_nodes/nodes_minimax.py
         data=MiniMaxChatRequest(
             model=MiniMaxChatModel(model).value,
             messages=messages,
             max_tokens=max_tokens,
             temperature=temperature,
+            reasoning_split=True,
         ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@comfy_api_nodes/nodes_minimax.py` around lines 539 - 552, The MiniMax chat
request currently omits the reasoning_split flag so message.content can contain
<think> reasoning tags; update the MiniMaxChatRequest constructed where sync_op
is called (the call that builds MiniMaxChatRequest with
model=MiniMaxChatModel(model).value, messages=messages, max_tokens=max_tokens,
temperature=temperature) to include reasoning_split=True so the API strips
<think> markup before returning content to be wrapped in IO.NodeOutput; ensure
this change is applied to the MiniMaxChatRequest used in the
ApiEndpoint("/proxy/minimax/chat/completions", method="POST") call that expects
MiniMaxChatResponse.

raise RuntimeError("No response generated by MiniMax model.")


class MinimaxExtension(ComfyExtension):
@override
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
Expand All @@ -445,6 +561,7 @@ async def get_node_list(self) -> list[type[IO.ComfyNode]]:
MinimaxImageToVideoNode,
# MinimaxSubjectToVideoNode,
MinimaxHailuoVideoNode,
MinimaxChatNode,
]


Expand Down
27 changes: 27 additions & 0 deletions tests-unit/comfy_api_test/minimax_models_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import pytest
from comfy_api_nodes.apis.minimax import MiniMaxChatModel


class TestMiniMaxChatModel:
def test_m27_in_model_list(self):
"""MiniMax-M2.7 should be available in the chat model enum."""
assert MiniMaxChatModel.M2_7.value == 'MiniMax-M2.7'

def test_m27_highspeed_in_model_list(self):
"""MiniMax-M2.7-highspeed should be available in the chat model enum."""
assert MiniMaxChatModel.M2_7_highspeed.value == 'MiniMax-M2.7-highspeed'

def test_m27_is_first_in_enum(self):
"""M2.7 should appear before older models in the enum."""
members = list(MiniMaxChatModel)
assert members[0] == MiniMaxChatModel.M2_7
assert members[1] == MiniMaxChatModel.M2_7_highspeed

def test_legacy_models_still_available(self):
"""Previous M2.5 models should still be available."""
assert MiniMaxChatModel.M2_5.value == 'MiniMax-M2.5'
assert MiniMaxChatModel.M2_5_highspeed.value == 'MiniMax-M2.5-highspeed'

def test_total_model_count(self):
"""Should have 4 chat models total (M2.7, M2.7-highspeed, M2.5, M2.5-highspeed)."""
assert len(MiniMaxChatModel) == 4
Loading