feat(openai): support adjusting thinking level for MiniMax-M3#3432
Closed
lightfront wants to merge 1 commit into
Closed
feat(openai): support adjusting thinking level for MiniMax-M3#3432lightfront wants to merge 1 commit into
lightfront wants to merge 1 commit into
Conversation
MiniMax-M3 exposes a single binary thinking knob (adaptive|disabled)
on its OpenAI-compatible endpoint at api.minimaxi.com, but our
provider unconditionally emits reasoning_effort — which M3 rejects.
Detect api.minimaxi.com as a separate wire shape alongside
api.deepseek.com and:
- emit thinking.type=adaptive|disabled in buildRequest
- omit reasoning_effort entirely (M3 has no level scale)
- translate /effort auto to 'adaptive' (the M3 default, since M3
ships with thinking on out of the box and 'auto' semantically
means 'don't override the model default')
- map legacy level names from other vendors so a stale /effort
value still resolves to a valid M3 level:
off → disabled (retired DeepSeek 'no thinking' — M3
actually supports 'thinking off')
low/medium/high → adaptive
xhigh/max → disabled
- in NormalizeEffort, surface a MiniMax-specific usage hint
(auto|adaptive|disabled) when an unknown level is supplied
internal/config/effort.go mirrors the change: M3 entries now expose
/levels=[auto,adaptive,disabled] with default=adaptive. Tests cover
the wire shape (TestBuildRequestMiniMaxThinking) and the
config-layer remap (TestNormalizeEffortMiniMax,
TestNormalizeEffortMiniMaxRejectsGarbage,
TestEffectiveEffortMiniMax).
Refactor: the host-matching logic was duplicated across
internal/provider/openai/openai.go (private *BaseURL helpers) and
internal/config/effort.go (private *Entry wrappers). Extract the
shared primitive into internal/provider/openai/host.go as
matchesVendorHost, plus thin exported wrappers IsDeepSeek and
IsMiniMax. The *Entry wrappers in effort.go now just gate on the
openai kind and delegate. Adding a new gateway is now a one-line
change in host.go rather than a four-line change across two files.
Add internal/provider/openai/host_test.go pinning the matching
rule (canonical hostname exact match, plus any subdomain of the
apex) directly at the source.
57c3c34 to
c45d9b8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MiniMax-M3 exposes a single binary thinking knob (
adaptive|disabled) on its OpenAI-compatible endpoint atapi.minimaxi.com. Today our provider unconditionally emitsreasoning_effort, which M3 rejects — so users with a MiniMax provider can't actually configure the thinking level even though theEffortCapabilityplumbing implies they can.This PR teaches the openai provider and the
EffortCapabilitylayer to recognizeapi.minimaxi.comas a separate wire shape, mirroring the existing DeepSeek handling.Wire-format change
For a
base_urlofapi.minimaxi.com(or any*.minimaxi.comsubdomain),buildRequestnow:thinking.type = "adaptive"when/effort auto(the empty-effort case, which is also the M3 model default)thinking.type = "adaptive"for the explicitadaptivelevelthinking.type = "disabled"for thedisabledlevelreasoning_effortentirely — M3 has no level scale and rejects the fieldThis is in addition to the existing DeepSeek path, which still emits both
thinking.type = "enabled"andreasoning_effort = high|max.User-facing
EffortCapabilitychangeM3 entries now expose
/effort auto|adaptive|disabledwith defaultadaptive. Legacy level names from other vendors resolve to a valid M3 level so stale/effortvalues don't error:autoadaptiveon the wire)adaptiveadaptivedisableddisabledoffdisabledlow/medium/highadaptivexhigh/maxdisabledUnknown values get a MiniMax-specific usage hint (
/effort auto|adaptive|disabled) instead of the generic message.Refactor bundled in
The host-matching logic was duplicated across
internal/provider/openai/openai.go(private*BaseURLhelpers) andinternal/config/effort.go(private*Entrywrappers). Adding a new gateway would have required updating both files in lockstep, with no compiler-level guarantee they stay in sync.I extracted the shared primitive into a new
internal/provider/openai/host.go:The
*Entrywrappers ineffort.gonow just gate on the openai kind and delegate.net/urlis no longer imported ineffort.gooropenai.go.The new
host_test.gopins the matching rule (canonical hostname exact match, plus any subdomain of the apex) directly at the source.Tests
TestBuildRequestMiniMaxThinking— wire-shape coverage for all three levels (auto/adaptive/disabled), plus assertion thatreasoning_effortis omittedTestNewMiniMaxEffortValidation— boot-time validation of accepted and rejected effort valuesTestNewMiniMaxSetsFlag— base-URL detection (with and without/v1suffix)TestIsMiniMaxEntry— host detection edge cases (subdomain variants accepted, bare apex rejected, wrong-spelling rejected)TestEffortCapabilityMiniMax—/levelsand default for M3 entriesTestNormalizeEffortMiniMax— full table of legacy level remapsTestNormalizeEffortMiniMaxRejectsGarbage— MiniMax-specific error messageTestEffectiveEffortMiniMax—EffectiveEffortresolutionTestIsDeepSeek/TestIsMiniMax(new inhost_test.go) — host-matching primitivesAll MiniMax and DeepSeek tests pass; the existing
TestRealDeepSeekCacheProbeand the 18+ other openai tests are unchanged and still green.Out of scope / not changed
<think>…</think>block incontentis handled bythink.go(already in upstream from19bf1872). This PR doesn't touch it.Test results