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
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ ACESTEP_INIT_LLM=auto
# API key for authentication (optional)
# ACESTEP_API_KEY=sk-your-secret-key

# MiniMax external LM settings (optional)
# Shared by the OpenAI Chat and Anthropic Messages provider options.
# ACESTEP_MINIMAX_API_KEY=your-minimax-api-key
# Global base URLs (default). Use the Mainland China hosts when required:
# OpenAI Chat: https://api.minimaxi.com/v1/chat/completions
# Anthropic Messages: https://api.minimaxi.com/anthropic/v1/messages
# ACESTEP_EXTERNAL_BASE_URL=https://api.minimax.io/v1/chat/completions

# ==================== Gradio UI Settings ====================
# Server port (default: 7860)
# PORT=7860
Expand Down
2 changes: 1 addition & 1 deletion acestep/text_tasks/external_ai_request_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def build_request_for_protocol(
"max_tokens": max_tokens or int(os.getenv("ACESTEP_OPENAI_MAX_TOKENS", "3072")),
"temperature": 0.4,
}
if require_json_output and provider in {"openai", "zai"}:
if require_json_output and provider in {"minimax", "openai", "zai"}:
payload["response_format"] = {"type": "json_object"}
payload["stop"] = ["```"]
Comment on lines +137 to 139

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'external_ai_request_helpers(_test)?\.py$' . | sed 's#^\./##'

echo "== helper outline =="
helper="$(fd 'external_ai_request_helpers\.py$' . | head -n1)"
if [ -n "$helper" ]; then
  ast-grep outline "$helper" --view compact || true
  wc -l "$helper"
  sed -n '1,220p' "$helper"
fi

echo "== test relevant lines =="
if [ -n "${test:-}" ]; then
  test="${test:-$(fd 'external_ai_request_helpers_test\.py$' . | head -n1)}"
fi
test="$(fd 'external_ai_request_helpers_test\.py$' . | head -n1 || true)"
if [ -n "$test" ]; then
  wc -l "$test"
  sed -n '80,170p' "$test"
fi

echo "== search minimax/thinking params =="
rg -n "minimax|thinking|response_format|json_object|stop" .

Repository: ace-step/ACE-Step-1.5

Length of output: 50379


🌐 Web query:

MiniMax text-chat OpenAI thinking field disabled adaptive thinking response_format json_object documentation

💡 Result:

To control MiniMax model thinking and output formatting via the OpenAI-compatible Chat Completions API, use the following guidelines based on official documentation: Thinking Control For models that support it (e.g., MiniMax-M3), you can control the generation of thinking content by passing a thinking parameter within the extra_body of your API request [1][2]. - To disable thinking: Set extra_body={"thinking": {"type": "disabled"}} [1][3]. Note that for M2.x models, thinking cannot be disabled; the parameter may be accepted, but thinking will remain active [1][2][3]. - To enable thinking: Set extra_body={"thinking": {"type": "adaptive"}} [1][2]. This is generally the default behavior for M3 on the OpenAI-compatible route [3]. When handling interleaved thinking, you can use the extra_body parameter "reasoning_split": True to receive thinking content in a separate reasoning_details field rather than embedded in the content field [4][5][6]. If reasoning_split is false or omitted, the thinking content is wrapped in tags within the standard content field [4][5]. Response Format (JSON) Support for the response_format parameter, including {"type": "json_object"}, varies by model and endpoint [7][8][9]. While some documentation or third-party implementations suggest support for structured output via JSON mode [9][10], the native API specification notes that certain models (such as MiniMax-Text-01) have specific support for response_format [7]. For many standard MiniMax models (like M2.5) on the OpenAI-compatible endpoint, response_format may be ignored or limited, necessitating alternative methods for ensuring structured output [7]. Always verify specific model capabilities in the official MiniMax platform documentation for the most current information [11][1].

Citations:


🌐 Web query:

site:platform.minimax.io/docs/guides/text-openai-api MiniMax-M3 response_format json_object thinking disabled

💡 Result:

MiniMax’s OpenAI-compatible text API supports the M2.x reasoning models (MiniMax-M2.7, MiniMax-M2.5, MiniMax-M2.1, MiniMax-M2) and reasoning_split=True to separate thinking into reasoning_details. The docs also say response_format is only supported by MiniMax-Text-01, so response_format: {"type":"json_object"} is not documented for MiniMax-M2.x. I also found no documented thinking=disabled option for the OpenAI-compatible M2.x endpoint. (platform.minimax.io)

If you need JSON output with M2.x, the docs suggest using prompt instructions (e.g. “Return a JSON array only”) rather than response_format. (platform.minimax.io)

Citations:


🌐 Web query:

site:platform.minimax.io "MiniMax-M3" "thinking" "adaptive" "disabled"

💡 Result:

For the MiniMax-M3 model, the thinking (or reasoning) capability can be controlled via the API to be enabled or disabled. Documentation from different integration paths (OpenAI SDK vs. Anthropic SDK) provides slightly different phrasing regarding defaults and parameter names, but the underlying functionality is consistent [1][2]. Thinking/Reasoning Control for MiniMax-M3: - To disable thinking: Set the parameter thinking: {"type": "disabled"} in the extra_body (for OpenAI SDK) [1] or the thinking field (for Anthropic SDK) [2]. When disabled, the model skips thinking and provides a direct response [1][2]. - To enable thinking: Set thinking: {"type": "adaptive"} [1][2]. - Defaults: Depending on the specific API documentation page, the default behavior for MiniMax-M3 is stated as either "on by default" [1] or "off by default" [2]. Given this, it is recommended to explicitly set the thinking parameter to your desired state to ensure consistent behavior [1][2]. - Alternative Reasoning Parameter: In some contexts, particularly the Responses API, the capability is controlled via a reasoning field [3]. Setting reasoning: {"effort": "none"} disables reasoning output [3]. Values such as minimal, low, medium, or high enable it [3]. Important Considerations: - M2.x models: Thinking cannot be disabled for M2.x models; even if thinking: {"type": "disabled"} is passed, thinking remains active [1][2][3]. - Message History: When using Interleaved Thinking, it is critical to preserve the entire response message—including reasoning_content or reasoning_details—in the message history for subsequent turns to ensure the model maintains optimal performance [4]. For developers, it is highly recommended to review the specific API documentation relevant to your SDK (e.g., OpenAI vs. Anthropic vs. Responses API) as parameter keys can vary slightly between these implementations [1][2][3].

Citations:


🌐 Web query:

site:platform.minimax.io docs/api-reference/text-chat-openai "extra_body" "thinking" "MiniMax-M3"

💡 Result:

When using the MiniMax-M3 model via the OpenAI-compatible Chat Completions API, you can control the model's thinking behavior and output format using the extra_body parameter in your API request [1][2]. Thinking Control The thinking parameter within extra_body allows you to manage whether the model emits thinking content [1]: - {"type": "adaptive"}: Explicitly enables thinking (the default behavior for MiniMax-M3 if the parameter is omitted) [1][3]. - {"type": "disabled"}: Disables thinking content, causing the model to answer directly [1][3]. Output Formatting (reasoning_split) You can use the reasoning_split parameter within extra_body to determine how thinking content is returned [1][4]: - reasoning_split=True: Separates thinking content into distinct fields (reasoning_content and reasoning_details) [1][4]. This is the recommended "Interleaved Thinking" compatible format, which provides a more developer-friendly output [4][5]. - reasoning_split=False: Embeds the model's thinking within the content field, enclosed in tags [4][5]. This format requires manual parsing if you wish to separate the reasoning from the final answer [4]. Important Usage Note If you are implementing multi-turn conversations, it is essential that you preserve the entire response object (including reasoning content or details) and pass it back in the message history for subsequent turns [4][5]. Modifying or removing the thinking content from the history can negatively impact the model's performance and chain-of-thought capabilities [4][5]. Example Usage python client.chat.completions.create( model="MiniMax-M3", messages=[{"role": "user", "content": "Hi, how are you?"}], extra_body={ "thinking": {"type": "adaptive"}, "reasoning_split": True, },)

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== callers of build_request_for_protocol =="
rg -n "build_request_for_protocol\s*\(" . --glob '!**/*.pyc' --glob '!docs/**/*' --glob '!**/node_modules/**' | head -n 120

echo "== relevant external_ai_request_helpers_test lines =="
test="$(fd 'external_ai_request_helpers_test\.py$' . | head -n1)"
sed -n '110,155p' "$test"

echo "== behavioral probe: current helper payload for MiniMax JSON request =="
python3 - <<'PY'
import sys
sys.path.insert(0, '.')
from acestep.text_tasks.external_ai_request_helpers import build_request_for_protocol

payload, headers = build_request_for_protocol(
    protocol="openai_chat",
    provider="minimax",
    api_key="test-key",
    model="MiniMax-M3",
    messages=[{"role": "system", "content": "s"}, {"role": "user", "content": "u"}],
    base_url="https://api.minimax.io/v1/chat/completions",
    max_tokens=768,
    require_json_output=True,
)

def report(k):
    print(f"{k}: {k in payload!r} {payload.get(k)!r}")

report("thinking")
report("response_format")
report("stop")
PY

Repository: ace-step/ACE-Step-1.5

Length of output: 3354


Disable MiniMax M3 thinking for format JSON requests.

For MiniMax-M3, omitting thinking on the MiniMax endpoint enables adaptive thinking by default. When require_json_output=True for MiniMax, emit thinking: {"type": "disabled" and assert that payload in the MiniMax test as well, not field absence.

📍 Affects 2 files
  • acestep/text_tasks/external_ai_request_helpers.py#L137-L139 (this comment)
  • acestep/text_tasks/external_ai_request_helpers_test.py#L130-L146
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@acestep/text_tasks/external_ai_request_helpers.py` around lines 137 - 139,
Update the JSON-output payload branch in the request helper to add MiniMax’s
disabled-thinking setting when provider is "minimax", using thinking {"type":
"disabled"} while preserving the existing response_format and stop fields. In
acestep/text_tasks/external_ai_request_helpers_test.py lines 130-146, update the
MiniMax assertion to require this thinking payload rather than asserting the
field is absent.

Source: MCP tools

if disable_thinking and provider == "zai":
Expand Down
18 changes: 18 additions & 0 deletions acestep/text_tasks/external_ai_request_helpers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,24 @@ def test_build_request_for_protocol_requests_json_output_for_openai_format(self)
self.assertEqual(payload["stop"], ["```"])
self.assertNotIn("thinking", payload)

def test_build_request_for_protocol_requests_json_output_for_minimax_format(self) -> None:
"""MiniMax format-mode requests should use OpenAI-compatible JSON output flags."""

payload, _headers = build_request_for_protocol(
protocol="openai_chat",
provider="minimax",
api_key="test-key",
model="MiniMax-M3",
messages=[{"role": "system", "content": "s"}, {"role": "user", "content": "u"}],
base_url="https://api.minimax.io/v1/chat/completions",
max_tokens=768,
require_json_output=True,
)

self.assertEqual(payload["response_format"], {"type": "json_object"})
self.assertEqual(payload["stop"], ["```"])
self.assertNotIn("thinking", payload)

def test_build_request_for_protocol_disables_zai_thinking_and_requests_json(self) -> None:
"""Z.ai format calls should disable thinking and request JSON output."""

Expand Down
32 changes: 31 additions & 1 deletion acestep/text_tasks/external_lm_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,36 @@ class ExternalProviderProfile:
("OpenAI chat completions", "https://api.openai.com/v1/chat/completions"),
),
),
"minimax": ExternalProviderProfile(
provider_id="minimax",
label="MiniMax",
protocol="openai_chat",
default_model="MiniMax-M3",
default_base_url="https://api.minimax.io/v1/chat/completions",
api_key_env="ACESTEP_MINIMAX_API_KEY",
api_key_required=True,
secret_path_env="ACESTEP_MINIMAX_SECRET_PATH",
secret_file_name="minimax_api_key.enc",
base_url_presets=(
("Global chat completions", "https://api.minimax.io/v1/chat/completions"),
("Mainland China chat completions", "https://api.minimaxi.com/v1/chat/completions"),
),
),
"minimax_anthropic": ExternalProviderProfile(
provider_id="minimax_anthropic",
label="MiniMax (Anthropic API)",
protocol="anthropic_messages",
default_model="MiniMax-M3",
default_base_url="https://api.minimax.io/anthropic/v1/messages",
api_key_env="ACESTEP_MINIMAX_API_KEY",
api_key_required=True,
secret_path_env="ACESTEP_MINIMAX_SECRET_PATH",
secret_file_name="minimax_api_key.enc",
base_url_presets=(
("Global Anthropic messages", "https://api.minimax.io/anthropic/v1/messages"),
("Mainland China Anthropic messages", "https://api.minimaxi.com/anthropic/v1/messages"),
),
),
Comment on lines +71 to +85

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg 'acestep/text_tasks/(external_lm_providers|external_ai_request_helpers)\.py$|external_lm_providers\.py|external_ai_request_helpers\.py' || true

echo "== file line counts =="
wc -l acestep/text_tasks/external_lm_providers.py acestep/text_tasks/external_ai_request_helpers.py

echo "== relevant provider lines =="
sed -n '1,130p' acestep/text_tasks/external_lm_providers.py

echo "== relevant helper lines =="
sed -n '1,220p' acestep/text_tasks/external_ai_request_helpers.py

echo "== search provider/auth references =="
rg -n "minimax_anthropic|anthropic_messages|x-api-key|Authorization: Bearer|ACESTEP_MINIMAX_MINIMAX|secret_path_env|ExternalProviderProfile" acestep/text_tasks acestep || true

Repository: ace-step/ACE-Step-1.5

Length of output: 20967


🌐 Web query:

MiniMax Anthropic API authorization header x-api-key Authorization Bearer platform.minimax.io

💡 Result:

To authenticate with the MiniMax API—including its Anthropic-compatible endpoints—you must use the HTTP Authorization header with the Bearer authentication scheme [1][2][3]. The correct header format is: Authorization: Bearer YOUR_API_KEY While some services and SDKs (such as those designed for Anthropic's native API) default to the x-api-key header, MiniMax endpoints explicitly require the Authorization header [4][5][6]. Using x-api-key may result in authentication errors (such as HTTP 401 or system error 1033) even when accessing Anthropic-compatible routes [4][5][6]. If both headers are provided, the Authorization header takes precedence [3]. Key implementation details: - Base URL: For Anthropic-compatible requests, the base URL is typically https://api.minimax.io/anthropic/v1 [4][7]. - Header requirement: Always use Authorization: Bearer <API_KEY> [8][3][5]. Ensure there is a single space between the word Bearer and your API key [7][9]. - Account Keys: Ensure you are using the correct key (Pay-as-you-go API key or Subscription Key) as obtained from your MiniMax account management portal [7][10][11]. These keys are not always interchangeable depending on the billing model [11].

Citations:


🌐 Web query:

site:platform.minimax.io/docs/api-reference/text-post HTTP authorization header x-api-key

💡 Result:

The x-api-key header is a commonly used custom HTTP header for transmitting API keys, but it is not a formal or IANA-registered standard. While many web services implement it as a mechanism for authentication or authorization, it is considered a non-standard practice. In contrast, the official HTTP Authorization header is the standard mechanism for providing authentication credentials, typically using schemes like Bearer or Basic. Using the Authorization header with a custom scheme or standard token format is generally preferred for interoperability and adherence to HTTP specifications over custom headers like x-api-key. Developers often choose x-api-key for its simplicity in implementation or to bypass specific intermediary requirements, but it lacks the universal support and security semantics defined for the Authorization header.

Citations:


Use MiniMax’s documented Authorization: Bearer header for the MiniMax Anthropic profile.

minimax_anthropic uses the anthropic_messages protocol, but the shared Anthropic request builder only sends x-api-key, while MiniMax Anthropic-compatible endpoints require Authorization: Bearer <API_KEY>. Add provider-specific auth handling for this profile and test that the generated headers match MiniMax’s requirement.

🧰 Tools
🪛 Ruff (0.16.0)

[error] 79-79: Possible hardcoded password assigned to argument: "secret_path_env"

(S106)


[error] 80-80: Possible hardcoded password assigned to argument: "secret_file_name"

(S106)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@acestep/text_tasks/external_lm_providers.py` around lines 71 - 85, Update the
authentication handling used by the minimax_anthropic ExternalProviderProfile so
requests to its Anthropic-compatible endpoints send Authorization: Bearer with
the API key instead of relying on the shared x-api-key behavior. Add
provider-specific handling keyed to minimax_anthropic, and add coverage
verifying the generated headers use the required Bearer authorization.

Source: MCP tools

"claude": ExternalProviderProfile(
provider_id="claude",
label="Anthropic Claude",
Expand Down Expand Up @@ -101,7 +131,7 @@ def get_external_provider_profile(provider: str | None) -> ExternalProviderProfi
def get_external_provider_choices() -> list[tuple[str, str]]:
"""Return provider dropdown choices as ``(label, value)`` pairs."""

order = ("zai", "openai", "claude", "ollama")
order = ("zai", "openai", "minimax", "minimax_anthropic", "claude", "ollama")
return [
(_EXTERNAL_PROVIDER_PROFILES[provider_id].label, provider_id)
for provider_id in order
Expand Down
58 changes: 58 additions & 0 deletions acestep/text_tasks/external_lm_providers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,79 @@

from acestep.text_tasks.external_lm_providers import (
CUSTOM_BASE_URL_PRESET,
build_external_model_choice,
get_external_base_url_preset_choices,
get_external_base_url_preset_value,
get_external_provider_choices,
get_external_provider_profile,
)


class ExternalLmProvidersTests(unittest.TestCase):
"""Verify provider lookup and base-URL preset helpers stay explicit."""

def test_get_external_provider_profile_returns_minimax_defaults(self) -> None:
"""MiniMax should expose OpenAI-compatible chat defaults."""

profile = get_external_provider_profile("minimax")

self.assertEqual(profile.protocol, "openai_chat")
self.assertEqual(profile.default_model, "MiniMax-M3")
self.assertEqual(
profile.default_base_url,
"https://api.minimax.io/v1/chat/completions",
)
self.assertEqual(profile.api_key_env, "ACESTEP_MINIMAX_API_KEY")

def test_get_external_provider_profile_returns_minimax_anthropic_defaults(self) -> None:
"""MiniMax also exposes an Anthropic Messages provider option."""

profile = get_external_provider_profile("minimax_anthropic")

self.assertEqual(profile.protocol, "anthropic_messages")
self.assertEqual(profile.default_model, "MiniMax-M3")
self.assertEqual(
profile.default_base_url,
"https://api.minimax.io/anthropic/v1/messages",
)
self.assertEqual(profile.api_key_env, "ACESTEP_MINIMAX_API_KEY")

def test_minimax_base_url_presets_cover_global_and_china(self) -> None:
"""MiniMax presets should offer both the global and Mainland China hosts."""

openai_presets = dict(get_external_base_url_preset_choices("minimax"))
anthropic_presets = dict(get_external_base_url_preset_choices("minimax_anthropic"))

self.assertIn("https://api.minimax.io/v1/chat/completions", openai_presets.values())
self.assertIn("https://api.minimaxi.com/v1/chat/completions", openai_presets.values())
self.assertIn(
"https://api.minimax.io/anthropic/v1/messages", anthropic_presets.values()
)
self.assertIn(
"https://api.minimaxi.com/anthropic/v1/messages", anthropic_presets.values()
)

def test_get_external_provider_profile_rejects_unknown_provider(self) -> None:
"""Unknown providers should fail fast instead of silently defaulting."""

with self.assertRaises(ValueError):
get_external_provider_profile("mystery")

def test_get_external_provider_choices_includes_minimax(self) -> None:
"""Provider choices should expose both MiniMax options to the picker."""

choices = get_external_provider_choices()

self.assertIn(("MiniMax", "minimax"), choices)
self.assertIn(("MiniMax (Anthropic API)", "minimax_anthropic"), choices)

def test_build_external_model_choice_defaults_minimax_model(self) -> None:
"""Missing MiniMax model names should fall back to the provider default."""

choice = build_external_model_choice("minimax", "")

self.assertEqual(choice, "external:minimax:MiniMax-M3")

def test_base_url_preset_helpers_use_shared_custom_token(self) -> None:
"""Custom base-URL selection should use the centralized custom token."""

Expand Down