Skip to content

Commit e083383

Browse files
authored
Merge pull request #65 from octo-patch/feature/add-minimax-provider
feat: upgrade MiniMax to M2.7 models with OpenAI-compatible API
2 parents 6e2b683 + bf526e4 commit e083383

5 files changed

Lines changed: 361 additions & 4 deletions

File tree

api/service/hub_adaptor/openai/adaptor.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"github.com/gin-gonic/gin"
1818
"github.com/songquanpeng/one-api/common/logger"
1919
"github.com/songquanpeng/one-api/relay/adaptor/doubao"
20-
"github.com/songquanpeng/one-api/relay/adaptor/minimax"
2120
"github.com/songquanpeng/one-api/relay/adaptor/novita"
2221
"github.com/songquanpeng/one-api/relay/channeltype"
2322
"github.com/songquanpeng/one-api/relay/meta"
@@ -57,7 +56,12 @@ func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
5756
requestURL = fmt.Sprintf("/openai/deployments/%s/%s", model_, task)
5857
return GetFullRequestURL(meta.BaseURL, requestURL, meta.ChannelType), nil
5958
case channeltype.Minimax:
60-
return minimax.GetRequestURL(meta)
59+
// Use standard OpenAI-compatible endpoint.
60+
// MiniMax's new API at api.minimax.io/v1 supports the standard
61+
// /v1/chat/completions format for both M2.x and legacy abab models.
62+
// The upstream one-api adaptor used the deprecated /v1/text/chatcompletion_v2
63+
// endpoint which is no longer recommended.
64+
return GetFullRequestURL(meta.BaseURL, meta.RequestURLPath, meta.ChannelType), nil
6165
case channeltype.Doubao:
6266
return doubao.GetRequestURL(meta)
6367
case channeltype.Novita:
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package openai
2+
3+
import (
4+
"testing"
5+
6+
"github.com/songquanpeng/one-api/relay/channeltype"
7+
"github.com/songquanpeng/one-api/relay/meta"
8+
)
9+
10+
func TestGetRequestURL_Minimax(t *testing.T) {
11+
adaptor := &Adaptor{}
12+
13+
tests := []struct {
14+
name string
15+
baseURL string
16+
requestPath string
17+
model string
18+
expected string
19+
}{
20+
{
21+
name: "M2.7 model with standard base URL",
22+
baseURL: "https://api.minimax.io/v1",
23+
requestPath: "/v1/chat/completions",
24+
model: "MiniMax-M2.7",
25+
expected: "https://api.minimax.io/v1/v1/chat/completions",
26+
},
27+
{
28+
name: "M2.7-highspeed with standard base URL",
29+
baseURL: "https://api.minimax.io",
30+
requestPath: "/v1/chat/completions",
31+
model: "MiniMax-M2.7-highspeed",
32+
expected: "https://api.minimax.io/v1/chat/completions",
33+
},
34+
{
35+
name: "Legacy abab model with standard base URL",
36+
baseURL: "https://api.minimax.io",
37+
requestPath: "/v1/chat/completions",
38+
model: "abab6.5-chat",
39+
expected: "https://api.minimax.io/v1/chat/completions",
40+
},
41+
{
42+
name: "China base URL",
43+
baseURL: "https://api.minimaxi.com",
44+
requestPath: "/v1/chat/completions",
45+
model: "MiniMax-M2.7",
46+
expected: "https://api.minimaxi.com/v1/chat/completions",
47+
},
48+
}
49+
50+
for _, tt := range tests {
51+
t.Run(tt.name, func(t *testing.T) {
52+
m := &meta.Meta{
53+
ChannelType: channeltype.Minimax,
54+
BaseURL: tt.baseURL,
55+
RequestURLPath: tt.requestPath,
56+
ActualModelName: tt.model,
57+
}
58+
adaptor.Init(m)
59+
60+
url, err := adaptor.GetRequestURL(m)
61+
if err != nil {
62+
t.Fatalf("unexpected error: %v", err)
63+
}
64+
if url != tt.expected {
65+
t.Errorf("expected URL %q, got %q", tt.expected, url)
66+
}
67+
})
68+
}
69+
}
70+
71+
func TestGetRequestURL_MinimaxUsesOpenAICompatFormat(t *testing.T) {
72+
// Verify MiniMax no longer uses the deprecated /v1/text/chatcompletion_v2 endpoint
73+
adaptor := &Adaptor{}
74+
m := &meta.Meta{
75+
ChannelType: channeltype.Minimax,
76+
BaseURL: "https://api.minimax.io",
77+
RequestURLPath: "/v1/chat/completions",
78+
ActualModelName: "MiniMax-M2.7",
79+
}
80+
adaptor.Init(m)
81+
82+
url, err := adaptor.GetRequestURL(m)
83+
if err != nil {
84+
t.Fatalf("unexpected error: %v", err)
85+
}
86+
87+
// Should NOT contain the deprecated endpoint
88+
if url == "https://api.minimax.io/v1/text/chatcompletion_v2" {
89+
t.Error("MiniMax should use OpenAI-compatible /v1/chat/completions, not deprecated /v1/text/chatcompletion_v2")
90+
}
91+
92+
// Should contain the standard OpenAI path
93+
expected := "https://api.minimax.io/v1/chat/completions"
94+
if url != expected {
95+
t.Errorf("expected %q, got %q", expected, url)
96+
}
97+
}

api/service/hub_adaptor/openai/compatible.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/songquanpeng/one-api/relay/adaptor/doubao"
88
"github.com/songquanpeng/one-api/relay/adaptor/groq"
99
"github.com/songquanpeng/one-api/relay/adaptor/lingyiwanwu"
10-
"github.com/songquanpeng/one-api/relay/adaptor/minimax"
1110
"github.com/songquanpeng/one-api/relay/adaptor/mistral"
1211
"github.com/songquanpeng/one-api/relay/adaptor/moonshot"
1312
"github.com/songquanpeng/one-api/relay/adaptor/novita"
@@ -18,6 +17,22 @@ import (
1817
"github.com/songquanpeng/one-api/relay/channeltype"
1918
)
2019

20+
// MiniMaxModelList contains the current MiniMax model IDs.
21+
// The upstream one-api dependency (v0.6.10) only includes legacy abab* models
22+
// which use the deprecated /v1/text/chatcompletion_v2 endpoint.
23+
// MiniMax now provides an OpenAI-compatible API at api.minimax.io/v1 with
24+
// the M2.7 series models. We override the model list here to reflect the
25+
// latest available models while keeping backward compatibility with abab.
26+
var MiniMaxModelList = []string{
27+
"MiniMax-M2.7",
28+
"MiniMax-M2.7-highspeed",
29+
"abab6.5-chat",
30+
"abab6.5s-chat",
31+
"abab6-chat",
32+
"abab5.5-chat",
33+
"abab5.5s-chat",
34+
}
35+
2136
var CompatibleChannels = []int{
2237
channeltype.Azure,
2338
channeltype.AI360,
@@ -47,7 +62,7 @@ func GetCompatibleChannelMeta(channelType int) (string, []string) {
4762
case channeltype.Baichuan:
4863
return "baichuan", baichuan.ModelList
4964
case channeltype.Minimax:
50-
return "minimax", minimax.ModelList
65+
return "minimax", MiniMaxModelList
5166
case channeltype.Mistral:
5267
return "mistralai", mistral.ModelList
5368
case channeltype.Groq:
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package openai
2+
3+
import (
4+
"testing"
5+
6+
"github.com/songquanpeng/one-api/relay/channeltype"
7+
)
8+
9+
func TestMiniMaxModelList(t *testing.T) {
10+
// Verify MiniMaxModelList contains the latest M2.7 models
11+
expectedModels := map[string]bool{
12+
"MiniMax-M2.7": false,
13+
"MiniMax-M2.7-highspeed": false,
14+
}
15+
16+
for _, model := range MiniMaxModelList {
17+
if _, ok := expectedModels[model]; ok {
18+
expectedModels[model] = true
19+
}
20+
}
21+
22+
for model, found := range expectedModels {
23+
if !found {
24+
t.Errorf("MiniMaxModelList missing required model: %s", model)
25+
}
26+
}
27+
}
28+
29+
func TestMiniMaxModelListBackwardCompat(t *testing.T) {
30+
// Verify legacy abab models are still present for backward compatibility
31+
legacyModels := []string{"abab6.5-chat", "abab5.5-chat"}
32+
modelSet := make(map[string]bool)
33+
for _, m := range MiniMaxModelList {
34+
modelSet[m] = true
35+
}
36+
37+
for _, model := range legacyModels {
38+
if !modelSet[model] {
39+
t.Errorf("MiniMaxModelList missing legacy model %s (backward compatibility)", model)
40+
}
41+
}
42+
}
43+
44+
func TestGetCompatibleChannelMeta_Minimax(t *testing.T) {
45+
name, models := GetCompatibleChannelMeta(channeltype.Minimax)
46+
47+
if name != "minimax" {
48+
t.Errorf("expected channel name 'minimax', got '%s'", name)
49+
}
50+
51+
if len(models) == 0 {
52+
t.Error("expected non-empty model list for minimax channel")
53+
}
54+
55+
// Verify the returned list matches our local override, not the upstream one-api list
56+
hasM27 := false
57+
for _, m := range models {
58+
if m == "MiniMax-M2.7" {
59+
hasM27 = true
60+
break
61+
}
62+
}
63+
if !hasM27 {
64+
t.Error("minimax channel meta should return MiniMax-M2.7 model")
65+
}
66+
}
67+
68+
func TestGetCompatibleChannelMeta_OtherChannels(t *testing.T) {
69+
// Verify other channels still return correct metadata
70+
tests := []struct {
71+
channelType int
72+
expectedName string
73+
}{
74+
{channeltype.Azure, "azure"},
75+
{channeltype.DeepSeek, "deepseek"},
76+
{channeltype.Groq, "groq"},
77+
}
78+
79+
for _, tt := range tests {
80+
name, models := GetCompatibleChannelMeta(tt.channelType)
81+
if name != tt.expectedName {
82+
t.Errorf("channel type %d: expected name '%s', got '%s'", tt.channelType, tt.expectedName, name)
83+
}
84+
if len(models) == 0 {
85+
t.Errorf("channel type %d: expected non-empty model list", tt.channelType)
86+
}
87+
}
88+
}
89+
90+
func TestCompatibleChannelsIncludesMinimax(t *testing.T) {
91+
found := false
92+
for _, ch := range CompatibleChannels {
93+
if ch == channeltype.Minimax {
94+
found = true
95+
break
96+
}
97+
}
98+
if !found {
99+
t.Error("CompatibleChannels should include channeltype.Minimax")
100+
}
101+
}

0 commit comments

Comments
 (0)