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: 6 additions & 2 deletions api/service/hub_adaptor/openai/adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/relay/adaptor/doubao"
"github.com/songquanpeng/one-api/relay/adaptor/minimax"
"github.com/songquanpeng/one-api/relay/adaptor/novita"
"github.com/songquanpeng/one-api/relay/channeltype"
"github.com/songquanpeng/one-api/relay/meta"
Expand Down Expand Up @@ -57,7 +56,12 @@ func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
requestURL = fmt.Sprintf("/openai/deployments/%s/%s", model_, task)
return GetFullRequestURL(meta.BaseURL, requestURL, meta.ChannelType), nil
case channeltype.Minimax:
return minimax.GetRequestURL(meta)
// Use standard OpenAI-compatible endpoint.
// MiniMax's new API at api.minimax.io/v1 supports the standard
// /v1/chat/completions format for both M2.x and legacy abab models.
// The upstream one-api adaptor used the deprecated /v1/text/chatcompletion_v2
// endpoint which is no longer recommended.
return GetFullRequestURL(meta.BaseURL, meta.RequestURLPath, meta.ChannelType), nil
case channeltype.Doubao:
return doubao.GetRequestURL(meta)
case channeltype.Novita:
Expand Down
97 changes: 97 additions & 0 deletions api/service/hub_adaptor/openai/adaptor_minimax_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package openai

import (
"testing"

"github.com/songquanpeng/one-api/relay/channeltype"
"github.com/songquanpeng/one-api/relay/meta"
)

func TestGetRequestURL_Minimax(t *testing.T) {
adaptor := &Adaptor{}

tests := []struct {
name string
baseURL string
requestPath string
model string
expected string
}{
{
name: "M2.7 model with standard base URL",
baseURL: "https://api.minimax.io/v1",
requestPath: "/v1/chat/completions",
model: "MiniMax-M2.7",
expected: "https://api.minimax.io/v1/v1/chat/completions",
},
{
name: "M2.7-highspeed with standard base URL",
baseURL: "https://api.minimax.io",
requestPath: "/v1/chat/completions",
model: "MiniMax-M2.7-highspeed",
expected: "https://api.minimax.io/v1/chat/completions",
},
{
name: "Legacy abab model with standard base URL",
baseURL: "https://api.minimax.io",
requestPath: "/v1/chat/completions",
model: "abab6.5-chat",
expected: "https://api.minimax.io/v1/chat/completions",
},
{
name: "China base URL",
baseURL: "https://api.minimaxi.com",
requestPath: "/v1/chat/completions",
model: "MiniMax-M2.7",
expected: "https://api.minimaxi.com/v1/chat/completions",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := &meta.Meta{
ChannelType: channeltype.Minimax,
BaseURL: tt.baseURL,
RequestURLPath: tt.requestPath,
ActualModelName: tt.model,
}
adaptor.Init(m)

url, err := adaptor.GetRequestURL(m)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if url != tt.expected {
t.Errorf("expected URL %q, got %q", tt.expected, url)
}
})
}
}

func TestGetRequestURL_MinimaxUsesOpenAICompatFormat(t *testing.T) {
// Verify MiniMax no longer uses the deprecated /v1/text/chatcompletion_v2 endpoint
adaptor := &Adaptor{}
m := &meta.Meta{
ChannelType: channeltype.Minimax,
BaseURL: "https://api.minimax.io",
RequestURLPath: "/v1/chat/completions",
ActualModelName: "MiniMax-M2.7",
}
adaptor.Init(m)

url, err := adaptor.GetRequestURL(m)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

// Should NOT contain the deprecated endpoint
if url == "https://api.minimax.io/v1/text/chatcompletion_v2" {
t.Error("MiniMax should use OpenAI-compatible /v1/chat/completions, not deprecated /v1/text/chatcompletion_v2")
}

// Should contain the standard OpenAI path
expected := "https://api.minimax.io/v1/chat/completions"
if url != expected {
t.Errorf("expected %q, got %q", expected, url)
}
}
19 changes: 17 additions & 2 deletions api/service/hub_adaptor/openai/compatible.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/songquanpeng/one-api/relay/adaptor/doubao"
"github.com/songquanpeng/one-api/relay/adaptor/groq"
"github.com/songquanpeng/one-api/relay/adaptor/lingyiwanwu"
"github.com/songquanpeng/one-api/relay/adaptor/minimax"
"github.com/songquanpeng/one-api/relay/adaptor/mistral"
"github.com/songquanpeng/one-api/relay/adaptor/moonshot"
"github.com/songquanpeng/one-api/relay/adaptor/novita"
Expand All @@ -18,6 +17,22 @@ import (
"github.com/songquanpeng/one-api/relay/channeltype"
)

// MiniMaxModelList contains the current MiniMax model IDs.
// The upstream one-api dependency (v0.6.10) only includes legacy abab* models
// which use the deprecated /v1/text/chatcompletion_v2 endpoint.
// MiniMax now provides an OpenAI-compatible API at api.minimax.io/v1 with
// the M2.7 series models. We override the model list here to reflect the
// latest available models while keeping backward compatibility with abab.
var MiniMaxModelList = []string{
"MiniMax-M2.7",
"MiniMax-M2.7-highspeed",
"abab6.5-chat",
"abab6.5s-chat",
"abab6-chat",
"abab5.5-chat",
"abab5.5s-chat",
}

var CompatibleChannels = []int{
channeltype.Azure,
channeltype.AI360,
Expand Down Expand Up @@ -47,7 +62,7 @@ func GetCompatibleChannelMeta(channelType int) (string, []string) {
case channeltype.Baichuan:
return "baichuan", baichuan.ModelList
case channeltype.Minimax:
return "minimax", minimax.ModelList
return "minimax", MiniMaxModelList
case channeltype.Mistral:
return "mistralai", mistral.ModelList
case channeltype.Groq:
Expand Down
101 changes: 101 additions & 0 deletions api/service/hub_adaptor/openai/compatible_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package openai

import (
"testing"

"github.com/songquanpeng/one-api/relay/channeltype"
)

func TestMiniMaxModelList(t *testing.T) {
// Verify MiniMaxModelList contains the latest M2.7 models
expectedModels := map[string]bool{
"MiniMax-M2.7": false,
"MiniMax-M2.7-highspeed": false,
}

for _, model := range MiniMaxModelList {
if _, ok := expectedModels[model]; ok {
expectedModels[model] = true
}
}

for model, found := range expectedModels {
if !found {
t.Errorf("MiniMaxModelList missing required model: %s", model)
}
}
}

func TestMiniMaxModelListBackwardCompat(t *testing.T) {
// Verify legacy abab models are still present for backward compatibility
legacyModels := []string{"abab6.5-chat", "abab5.5-chat"}
modelSet := make(map[string]bool)
for _, m := range MiniMaxModelList {
modelSet[m] = true
}

for _, model := range legacyModels {
if !modelSet[model] {
t.Errorf("MiniMaxModelList missing legacy model %s (backward compatibility)", model)
}
}
}

func TestGetCompatibleChannelMeta_Minimax(t *testing.T) {
name, models := GetCompatibleChannelMeta(channeltype.Minimax)

if name != "minimax" {
t.Errorf("expected channel name 'minimax', got '%s'", name)
}

if len(models) == 0 {
t.Error("expected non-empty model list for minimax channel")
}

// Verify the returned list matches our local override, not the upstream one-api list
hasM27 := false
for _, m := range models {
if m == "MiniMax-M2.7" {
hasM27 = true
break
}
}
if !hasM27 {
t.Error("minimax channel meta should return MiniMax-M2.7 model")
}
}

func TestGetCompatibleChannelMeta_OtherChannels(t *testing.T) {
// Verify other channels still return correct metadata
tests := []struct {
channelType int
expectedName string
}{
{channeltype.Azure, "azure"},
{channeltype.DeepSeek, "deepseek"},
{channeltype.Groq, "groq"},
}

for _, tt := range tests {
name, models := GetCompatibleChannelMeta(tt.channelType)
if name != tt.expectedName {
t.Errorf("channel type %d: expected name '%s', got '%s'", tt.channelType, tt.expectedName, name)
}
if len(models) == 0 {
t.Errorf("channel type %d: expected non-empty model list", tt.channelType)
}
}
}

func TestCompatibleChannelsIncludesMinimax(t *testing.T) {
found := false
for _, ch := range CompatibleChannels {
if ch == channeltype.Minimax {
found = true
break
}
}
if !found {
t.Error("CompatibleChannels should include channeltype.Minimax")
}
}
Loading