Move minimax reasoning_split injection to provider factory
Inject reasoning_split at provider creation time to allow user ExtraBody settings to be preserved
This commit is contained in:
parent
f2985b8bee
commit
608ec6d329
3 changed files with 122 additions and 22 deletions
|
|
@ -1194,27 +1194,6 @@ func TestConfigLogLevelEmpty(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultConfig_MinimaxExtraBody(t *testing.T) {
|
|
||||||
cfg := DefaultConfig()
|
|
||||||
|
|
||||||
var minimaxCfg *ModelConfig
|
|
||||||
for i := range cfg.ModelList {
|
|
||||||
if cfg.ModelList[i].Model == "minimax/MiniMax-M2.5" {
|
|
||||||
minimaxCfg = &cfg.ModelList[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if minimaxCfg == nil {
|
|
||||||
t.Fatal("Minimax model not found in ModelList")
|
|
||||||
}
|
|
||||||
if minimaxCfg.ExtraBody == nil {
|
|
||||||
t.Fatal("Minimax ExtraBody should not be nil")
|
|
||||||
}
|
|
||||||
if got, ok := minimaxCfg.ExtraBody["reasoning_split"]; !ok || got != true {
|
|
||||||
t.Fatalf("Minimax ExtraBody[reasoning_split] = %v, want true", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestModelConfig_ExtraBodyRoundTrip(t *testing.T) {
|
func TestModelConfig_ExtraBodyRoundTrip(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
cfgPath := filepath.Join(dir, "config.json")
|
cfgPath := filepath.Join(dir, "config.json")
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
|
||||||
case "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia",
|
case "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia",
|
||||||
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
|
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
|
||||||
"vivgrid", "volcengine", "vllm", "qwen", "qwen-intl", "qwen-international", "dashscope-intl",
|
"vivgrid", "volcengine", "vllm", "qwen", "qwen-intl", "qwen-international", "dashscope-intl",
|
||||||
"qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita",
|
"qwen-us", "dashscope-us", "mistral", "avian", "longcat", "modelscope", "novita",
|
||||||
"coding-plan", "alibaba-coding", "qwen-coding":
|
"coding-plan", "alibaba-coding", "qwen-coding":
|
||||||
// All other OpenAI-compatible HTTP providers
|
// All other OpenAI-compatible HTTP providers
|
||||||
if cfg.APIKey() == "" && cfg.APIBase == "" {
|
if cfg.APIKey() == "" && cfg.APIBase == "" {
|
||||||
|
|
@ -136,6 +136,31 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
|
||||||
cfg.ExtraBody,
|
cfg.ExtraBody,
|
||||||
), modelID, nil
|
), modelID, nil
|
||||||
|
|
||||||
|
case "minimax":
|
||||||
|
// Minimax requires reasoning_split: true in the request body
|
||||||
|
if cfg.APIKey == "" && cfg.APIBase == "" {
|
||||||
|
return nil, "", fmt.Errorf("api_key or api_base is required for HTTP-based protocol %q", protocol)
|
||||||
|
}
|
||||||
|
apiBase := cfg.APIBase
|
||||||
|
if apiBase == "" {
|
||||||
|
apiBase = getDefaultAPIBase(protocol)
|
||||||
|
}
|
||||||
|
extraBody := cfg.ExtraBody
|
||||||
|
if extraBody == nil {
|
||||||
|
extraBody = make(map[string]any)
|
||||||
|
}
|
||||||
|
if _, ok := extraBody["reasoning_split"]; !ok {
|
||||||
|
extraBody["reasoning_split"] = true
|
||||||
|
}
|
||||||
|
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
|
||||||
|
cfg.APIKey,
|
||||||
|
apiBase,
|
||||||
|
cfg.Proxy,
|
||||||
|
cfg.MaxTokensField,
|
||||||
|
cfg.RequestTimeout,
|
||||||
|
extraBody,
|
||||||
|
), modelID, nil
|
||||||
|
|
||||||
case "anthropic":
|
case "anthropic":
|
||||||
if cfg.AuthMethod == "oauth" || cfg.AuthMethod == "token" {
|
if cfg.AuthMethod == "oauth" || cfg.AuthMethod == "token" {
|
||||||
// Use OAuth credentials from auth store
|
// Use OAuth credentials from auth store
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
package providers
|
package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -604,3 +605,98 @@ func TestGetDefaultAPIBase_QwenUSAliases(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateProviderFromConfig_MinimaxInjectsReasoningSplit(t *testing.T) {
|
||||||
|
var requestBody map[string]any
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"ok"},"finish_reason":"stop"}]}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
cfg := &config.ModelConfig{
|
||||||
|
ModelName: "test-minimax",
|
||||||
|
Model: "minimax/MiniMax-M2.5",
|
||||||
|
APIKey: "test-key",
|
||||||
|
APIBase: server.URL,
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, modelID, err := CreateProviderFromConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateProviderFromConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
if provider == nil {
|
||||||
|
t.Fatal("CreateProviderFromConfig() returned nil provider")
|
||||||
|
}
|
||||||
|
if modelID != "MiniMax-M2.5" {
|
||||||
|
t.Errorf("modelID = %q, want %q", modelID, "MiniMax-M2.5")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = provider.Chat(
|
||||||
|
t.Context(),
|
||||||
|
[]Message{{Role: "user", Content: "hi"}},
|
||||||
|
nil,
|
||||||
|
modelID,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Chat() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify reasoning_split is automatically injected
|
||||||
|
if got, ok := requestBody["reasoning_split"]; !ok || got != true {
|
||||||
|
t.Fatalf("reasoning_split = %v, want true", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateProviderFromConfig_MinimaxPreservesUserExtraBody(t *testing.T) {
|
||||||
|
var requestBody map[string]any
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"ok"},"finish_reason":"stop"}]}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
cfg := &config.ModelConfig{
|
||||||
|
ModelName: "test-minimax-custom",
|
||||||
|
Model: "minimax/MiniMax-M2.5",
|
||||||
|
APIKey: "test-key",
|
||||||
|
APIBase: server.URL,
|
||||||
|
ExtraBody: map[string]any{"custom_field": "test"},
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, modelID, err := CreateProviderFromConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateProviderFromConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = provider.Chat(
|
||||||
|
t.Context(),
|
||||||
|
[]Message{{Role: "user", Content: "hi"}},
|
||||||
|
nil,
|
||||||
|
modelID,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Chat() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify reasoning_split is automatically injected
|
||||||
|
if got, ok := requestBody["reasoning_split"]; !ok || got != true {
|
||||||
|
t.Fatalf("reasoning_split = %v, want true", got)
|
||||||
|
}
|
||||||
|
// Verify user's custom field is preserved
|
||||||
|
if got, ok := requestBody["custom_field"]; !ok || got != "test" {
|
||||||
|
t.Fatalf("custom_field = %v, want test", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue