MiniMax-M2.7 tool messages: gateway rejects standard OpenAI string content instead of normalizing it #1475
Summary
The devshard gateway (devshardctl) rejects standard OpenAI-format role:"tool"
messages for MiniMax-M2.7 with an HTTP 400, when their content is a plain string
(the most common and spec-compliant OpenAI shape). Multi-turn tool-calling
requests from standard OpenAI clients (coding agents, etc.) fail as a result.
Where this comes from
Introduced in #1427 (Aggregated gateway fixes, @gmorgachev), which added the
messagevalidators package. The MiniMax tool-message content shape is enforced by:
devshard/cmd/devshardctl/messagevalidators/minimax_tool_message.go- Wired in
devshard/cmd/devshardctl/request_filters_messages.goas theContentValidatoronmodelRoles[miniMaxM27ModelID][tool].
Present in release/v0.2.13-devshard-v3.0.0 / release/devshard/v3.0.0 /
release/v0.2.14-testnet-7. Observed on running image
ghcr.io/gonka-ai/devshard-gateway:mainnet-v0.2.13-v3-post1.
The design gap
For MiniMax-M2.7, the normalizer chain deliberately skips the tool role:
messagevalidators.EmptyContentNormalizer{ SkipRoles: []string{"tool"} },
messagevalidators.TextPartsFlattener{ SkipRoles: []string{"tool"} },
...and then attaches a validate-only MinimaxToolMessage validator. So the
gateway knows MiniMax needs content as a [{name,type,text}] array, but instead
of normalizing string content up to that shape, it hard-rejects with 400. This
pushes a MiniMax-specific quirk onto every OpenAI-compatible caller, which
contradicts the OpenAI-compat contract.
Log evidence (production, mainnet-v0.2.13-v3-post1)
devshardctl emits its own parse-failure stage — this is not an upstream
passthrough error:
stage=gateway_parse_failed error="messages[2].content: must be a non-empty array of {name,type,text} objects: not an array"
stage=gateway_parse_failed error="messages[3].content: must be a non-empty array of {name,type,text} objects: not an array"
stage=gateway_parse_failed error="messages[9].content: must be a non-empty array of {name,type,text} objects: not an array"
stage=gateway_parse_failed error="messages[15].content: must be a non-empty array of {name,type,text} objects: not an array"
Surfaced to callers (via new-api) as:
channel error (channel #3, status code: 400): messages[N].content: must be a non-empty array of {name,type,text} objects: not an array type=upstream_error
Notes:
- The failing index is never messages[0] — always a mid-conversation
tool message in multi-turn tool-calling flows. The first user turn (string
content) passes; the tool-result turns fail.
- In the same window MiniMax-M2.7 served 183k+ status=200 responses, so this is
purely the tool-message content shape, not a backend/model issue.
Proposed fix
Add a tool-role content normalizer for MiniMax-M2.7 (mirroring the existing
TextPartsFlattener / EmptyContentNormalizer pattern) that upgrades a plain
string content into the required array shape before validation, e.g.:
The existing caps in request_filters_config.go still apply after normalization
(MaxEntries=16, NameMaxLen=64, TextMaxSize=64KiB, closed {name,type,text} key
allow-list). This keeps the SGLang #16057 defensive intent while restoring
OpenAI-compat for standard clients.
Doc reference: docs/chat-api/minimax-m2.7.md.
cc @gmorgachev
💬 Comments (1)
🔄 Auto-synced from Issue #1475 every hour.
Thanks — this looks already fixed by #1467 (
fix(gateway): minimax tools regression, merged intoupgrade-v0.2.14), which removes theMinimaxToolMessagearray-shape validator sorole:"tool"messages accept standard OpenAI string content again (unifiedValidateRequiredContentFieldpath for all models). Closing as duplicate. Now just waiting for a mainnet gateway image containing #1467 — the currently deployedmainnet-v0.2.13-v3-post1still carries the pre-fix validator.