Merge pull request #328 from octo-patch/feature/upgrade-minimax-m3

feat: upgrade MiniMax default model to M3
This commit is contained in:
Dmitry Ng
2026-07-17 15:33:13 +04:00
committed by GitHub
8 changed files with 609 additions and 1 deletions
+6
View File
@@ -136,6 +136,11 @@ type Config struct {
QwenServerURL string `env:"QWEN_SERVER_URL" envDefault:"https://dashscope-us.aliyuncs.com/compatible-mode/v1"`
QwenProvider string `env:"QWEN_PROVIDER"`
// === LLM Provider: MiniMax ===
MiniMaxAPIKey string `env:"MINIMAX_API_KEY"`
MiniMaxServerURL string `env:"MINIMAX_SERVER_URL" envDefault:"https://api.minimax.io/v1"`
MiniMaxProvider string `env:"MINIMAX_PROVIDER"`
// === Search Engine: DuckDuckGo ===
DuckDuckGoEnabled bool `env:"DUCKDUCKGO_ENABLED" envDefault:"true"`
DuckDuckGoRegion string `env:"DUCKDUCKGO_REGION"`
@@ -323,6 +328,7 @@ func (c *Config) GetSecretPatterns() []patterns.Pattern {
{c.GLMAPIKey, "GLM Key"},
{c.KimiAPIKey, "Kimi Key"},
{c.QwenAPIKey, "Qwen Key"},
{c.MiniMaxAPIKey, "MiniMax Key"},
{c.GoogleAPIKey, "Google API Key"},
{c.GoogleCXKey, "Google CX Key"},
{c.OAuthGoogleClientID, "Google Client ID"},
+130
View File
@@ -0,0 +1,130 @@
simple:
model: MiniMax-M3
temperature: 0.5
top_p: 0.5
n: 1
max_tokens: 8192
price:
input: 0.60
output: 2.40
simple_json:
model: MiniMax-M3
temperature: 0.5
top_p: 0.5
n: 1
max_tokens: 4096
json: true
price:
input: 0.60
output: 2.40
primary_agent:
model: MiniMax-M3
temperature: 0.7
top_p: 0.8
n: 1
max_tokens: 16384
price:
input: 0.60
output: 2.40
assistant:
model: MiniMax-M3
temperature: 0.7
top_p: 0.8
n: 1
max_tokens: 16384
price:
input: 0.60
output: 2.40
generator:
model: MiniMax-M3
temperature: 0.7
top_p: 0.8
n: 1
max_tokens: 32768
price:
input: 0.60
output: 2.40
refiner:
model: MiniMax-M3
temperature: 0.7
top_p: 0.8
n: 1
max_tokens: 20480
price:
input: 0.60
output: 2.40
adviser:
model: MiniMax-M3
temperature: 0.7
top_p: 0.8
n: 1
max_tokens: 8192
price:
input: 0.60
output: 2.40
reflector:
model: MiniMax-M3
temperature: 0.5
top_p: 0.5
n: 1
max_tokens: 4096
price:
input: 0.60
output: 2.40
searcher:
model: MiniMax-M3
temperature: 0.7
top_p: 0.8
n: 1
max_tokens: 4096
price:
input: 0.60
output: 2.40
enricher:
model: MiniMax-M3
temperature: 0.7
top_p: 0.8
n: 1
max_tokens: 4096
price:
input: 0.60
output: 2.40
coder:
model: MiniMax-M3
temperature: 0.5
top_p: 0.5
n: 1
max_tokens: 20480
price:
input: 0.60
output: 2.40
installer:
model: MiniMax-M3
temperature: 0.5
top_p: 0.5
n: 1
max_tokens: 16384
price:
input: 0.60
output: 2.40
pentester:
model: MiniMax-M3
temperature: 0.5
top_p: 0.5
n: 1
max_tokens: 16384
price:
input: 0.60
output: 2.40
+194
View File
@@ -0,0 +1,194 @@
package minimax
import (
"context"
"embed"
"fmt"
"pentagi/pkg/config"
"pentagi/pkg/providers/pconfig"
"pentagi/pkg/providers/provider"
"pentagi/pkg/system"
"pentagi/pkg/templates"
"github.com/vxcontrol/langchaingo/llms"
"github.com/vxcontrol/langchaingo/llms/openai"
"github.com/vxcontrol/langchaingo/llms/streaming"
)
//go:embed config.yml models.yml
var configFS embed.FS
// MiniMaxAgentModel is the fallback model used when no agent-specific configuration exists.
// MiniMax-M3 is the latest flagship model (512K context, up to 128K output, image input support)
// and serves as the default for all agent types.
const MiniMaxAgentModel = "MiniMax-M3"
// MiniMaxToolCallIDTemplate is the pattern template for tool call IDs used by MiniMax.
const MiniMaxToolCallIDTemplate = "call_{r:24:b}"
func BuildProviderConfig(configData []byte) (*pconfig.ProviderConfig, error) {
defaultOptions := []llms.CallOption{
llms.WithModel(MiniMaxAgentModel),
llms.WithN(1),
llms.WithMaxTokens(4000),
}
providerConfig, err := pconfig.LoadConfigData(configData, defaultOptions)
if err != nil {
return nil, err
}
return providerConfig, nil
}
func DefaultProviderConfig() (*pconfig.ProviderConfig, error) {
configData, err := configFS.ReadFile("config.yml")
if err != nil {
return nil, err
}
return BuildProviderConfig(configData)
}
func DefaultModels() (pconfig.ModelsConfig, error) {
configData, err := configFS.ReadFile("models.yml")
if err != nil {
return nil, err
}
return pconfig.LoadModelsConfigData(configData)
}
type minimaxProvider struct {
llm *openai.LLM
models pconfig.ModelsConfig
providerName provider.ProviderName
providerConfig *pconfig.ProviderConfig
providerPrefix string
}
func New(
cfg *config.Config,
providerName provider.ProviderName,
providerConfig *pconfig.ProviderConfig,
) (provider.Provider, error) {
if cfg.MiniMaxAPIKey == "" {
return nil, fmt.Errorf("missing MINIMAX_API_KEY environment variable")
}
httpClient, err := system.GetHTTPClient(cfg)
if err != nil {
return nil, err
}
models, err := DefaultModels()
if err != nil {
return nil, err
}
client, err := openai.New(
openai.WithToken(cfg.MiniMaxAPIKey),
openai.WithModel(MiniMaxAgentModel),
openai.WithBaseURL(cfg.MiniMaxServerURL),
openai.WithHTTPClient(httpClient),
)
if err != nil {
return nil, err
}
return &minimaxProvider{
llm: client,
models: models,
providerName: providerName,
providerConfig: providerConfig,
providerPrefix: cfg.MiniMaxProvider,
}, nil
}
func (p *minimaxProvider) Type() provider.ProviderType {
return provider.ProviderMiniMax
}
func (p *minimaxProvider) Name() provider.ProviderName {
return p.providerName
}
func (p *minimaxProvider) GetRawConfig() []byte {
return p.providerConfig.GetRawConfig()
}
func (p *minimaxProvider) GetProviderConfig() *pconfig.ProviderConfig {
return p.providerConfig
}
func (p *minimaxProvider) GetPriceInfo(opt pconfig.ProviderOptionsType) *pconfig.PriceInfo {
return p.providerConfig.GetPriceInfoForType(opt)
}
func (p *minimaxProvider) GetModels() pconfig.ModelsConfig {
return p.models
}
func (p *minimaxProvider) Model(opt pconfig.ProviderOptionsType) string {
model := MiniMaxAgentModel
opts := llms.CallOptions{Model: &model}
for _, option := range p.providerConfig.GetOptionsForType(opt) {
option(&opts)
}
return opts.GetModel()
}
func (p *minimaxProvider) ModelWithPrefix(opt pconfig.ProviderOptionsType) string {
return provider.ApplyModelPrefix(p.Model(opt), p.providerPrefix)
}
func (p *minimaxProvider) Call(
ctx context.Context,
opt pconfig.ProviderOptionsType,
prompt string,
) (string, error) {
return provider.WrapGenerateFromSinglePrompt(
ctx, p, opt, p.llm, prompt,
p.providerConfig.GetOptionsForType(opt)...,
)
}
func (p *minimaxProvider) CallEx(
ctx context.Context,
opt pconfig.ProviderOptionsType,
chain []llms.MessageContent,
streamCb streaming.Callback,
) (*llms.ContentResponse, error) {
return provider.WrapGenerateContent(
ctx, p, opt, p.llm.GenerateContent, chain,
append([]llms.CallOption{
llms.WithStreamingFunc(streamCb),
}, p.providerConfig.GetOptionsForType(opt)...)...,
)
}
func (p *minimaxProvider) CallWithTools(
ctx context.Context,
opt pconfig.ProviderOptionsType,
chain []llms.MessageContent,
tools []llms.Tool,
streamCb streaming.Callback,
) (*llms.ContentResponse, error) {
return provider.WrapGenerateContent(
ctx, p, opt, p.llm.GenerateContent, chain,
append([]llms.CallOption{
llms.WithTools(tools),
llms.WithStreamingFunc(streamCb),
}, p.providerConfig.GetOptionsForType(opt)...)...,
)
}
func (p *minimaxProvider) GetUsage(info map[string]any) pconfig.CallUsage {
return pconfig.NewCallUsage(info)
}
func (p *minimaxProvider) GetToolCallIDTemplate(ctx context.Context, prompter templates.Prompter) (string, error) {
return provider.DetermineToolCallIDTemplate(ctx, p, pconfig.OptionsTypeSimple, prompter, MiniMaxToolCallIDTemplate)
}
@@ -0,0 +1,229 @@
package minimax
import (
"testing"
"pentagi/pkg/config"
"pentagi/pkg/providers/pconfig"
"pentagi/pkg/providers/provider"
)
func TestConfigLoading(t *testing.T) {
cfg := &config.Config{
MiniMaxAPIKey: "test-key",
MiniMaxServerURL: "https://api.minimax.io/v1",
}
providerConfig, err := DefaultProviderConfig()
if err != nil {
t.Fatalf("Failed to create provider config: %v", err)
}
prov, err := New(cfg, provider.DefaultProviderNameMiniMax, providerConfig)
if err != nil {
t.Fatalf("Failed to create provider: %v", err)
}
rawConfig := prov.GetRawConfig()
if len(rawConfig) == 0 {
t.Fatal("Raw config should not be empty")
}
providerConfig = prov.GetProviderConfig()
if providerConfig == nil {
t.Fatal("Provider config should not be nil")
}
for _, agentType := range pconfig.AllAgentTypes {
model := prov.Model(agentType)
if model == "" {
t.Errorf("Agent type %v should have a model assigned", agentType)
}
}
for _, agentType := range pconfig.AllAgentTypes {
priceInfo := prov.GetPriceInfo(agentType)
if priceInfo == nil {
t.Errorf("Agent type %v should have price information", agentType)
} else {
if priceInfo.Input <= 0 || priceInfo.Output <= 0 {
t.Errorf("Agent type %v should have positive input (%f) and output (%f) prices",
agentType, priceInfo.Input, priceInfo.Output)
}
}
}
}
func TestProviderType(t *testing.T) {
cfg := &config.Config{
MiniMaxAPIKey: "test-key",
MiniMaxServerURL: "https://api.minimax.io/v1",
}
providerConfig, err := DefaultProviderConfig()
if err != nil {
t.Fatalf("Failed to create provider config: %v", err)
}
prov, err := New(cfg, provider.DefaultProviderNameMiniMax, providerConfig)
if err != nil {
t.Fatalf("Failed to create provider: %v", err)
}
if prov.Type() != provider.ProviderMiniMax {
t.Errorf("Expected provider type %v, got %v", provider.ProviderMiniMax, prov.Type())
}
}
func TestModelsLoading(t *testing.T) {
models, err := DefaultModels()
if err != nil {
t.Fatalf("Failed to load models: %v", err)
}
if len(models) == 0 {
t.Fatal("Models list should not be empty")
}
wantModels := map[string]bool{
"MiniMax-M3": false,
"MiniMax-M2.7": false,
"MiniMax-M2.7-highspeed": false,
}
for _, model := range models {
if model.Name == "" {
t.Error("Model name should not be empty")
}
if model.Price == nil {
t.Errorf("Model %s should have price information", model.Name)
continue
}
if model.Price.Input <= 0 {
t.Errorf("Model %s should have positive input price", model.Name)
}
if model.Price.Output <= 0 {
t.Errorf("Model %s should have positive output price", model.Name)
}
if _, ok := wantModels[model.Name]; ok {
wantModels[model.Name] = true
}
}
for name, found := range wantModels {
if !found {
t.Errorf("Expected model %q in models list, but it was not found", name)
}
}
if models[0].Name != "MiniMax-M3" {
t.Errorf("Expected first model to be MiniMax-M3 (default), got %q", models[0].Name)
}
for _, model := range models {
if model.Name == "MiniMax-M2.5" || model.Name == "MiniMax-M2.5-highspeed" ||
model.Name == "MiniMax-M2.1" || model.Name == "MiniMax-M2" || model.Name == "MiniMax-M1" {
t.Errorf("Legacy model %q should not be in the model list", model.Name)
}
}
}
func TestModelWithPrefix(t *testing.T) {
cfg := &config.Config{
MiniMaxAPIKey: "test-key",
MiniMaxServerURL: "https://api.minimax.io/v1",
MiniMaxProvider: "minimax",
}
providerConfig, err := DefaultProviderConfig()
if err != nil {
t.Fatalf("Failed to create provider config: %v", err)
}
prov, err := New(cfg, provider.DefaultProviderNameMiniMax, providerConfig)
if err != nil {
t.Fatalf("Failed to create provider: %v", err)
}
for _, agentType := range pconfig.AllAgentTypes {
modelWithPrefix := prov.ModelWithPrefix(agentType)
model := prov.Model(agentType)
expected := "minimax/" + model
if modelWithPrefix != expected {
t.Errorf("Agent type %v: expected prefixed model %q, got %q", agentType, expected, modelWithPrefix)
}
}
}
func TestModelWithoutPrefix(t *testing.T) {
cfg := &config.Config{
MiniMaxAPIKey: "test-key",
MiniMaxServerURL: "https://api.minimax.io/v1",
}
providerConfig, err := DefaultProviderConfig()
if err != nil {
t.Fatalf("Failed to create provider config: %v", err)
}
prov, err := New(cfg, provider.DefaultProviderNameMiniMax, providerConfig)
if err != nil {
t.Fatalf("Failed to create provider: %v", err)
}
for _, agentType := range pconfig.AllAgentTypes {
modelWithPrefix := prov.ModelWithPrefix(agentType)
model := prov.Model(agentType)
if modelWithPrefix != model {
t.Errorf("Agent type %v: without prefix, ModelWithPrefix (%q) should equal Model (%q)",
agentType, modelWithPrefix, model)
}
}
}
func TestMissingAPIKey(t *testing.T) {
cfg := &config.Config{
MiniMaxServerURL: "https://api.minimax.io/v1",
}
providerConfig, err := DefaultProviderConfig()
if err != nil {
t.Fatalf("Failed to create provider config: %v", err)
}
_, err = New(cfg, provider.DefaultProviderNameMiniMax, providerConfig)
if err == nil {
t.Fatal("Expected error when API key is missing")
}
}
func TestGetUsage(t *testing.T) {
cfg := &config.Config{
MiniMaxAPIKey: "test-key",
MiniMaxServerURL: "https://api.minimax.io/v1",
}
providerConfig, err := DefaultProviderConfig()
if err != nil {
t.Fatalf("Failed to create provider config: %v", err)
}
prov, err := New(cfg, provider.DefaultProviderNameMiniMax, providerConfig)
if err != nil {
t.Fatalf("Failed to create provider: %v", err)
}
usage := prov.GetUsage(map[string]any{
"PromptTokens": 100,
"CompletionTokens": 50,
})
if usage.Input != 100 || usage.Output != 50 {
t.Errorf("Expected usage input=100 output=50, got input=%d output=%d", usage.Input, usage.Output)
}
}
+20
View File
@@ -0,0 +1,20 @@
- name: MiniMax-M3
description: MiniMax-M3 - Latest flagship model with 512K context window, up to 128K output tokens, and image input support. Suitable for general dialogue, code generation, complex reasoning, and tool calling.
thinking: false
price:
input: 0.60
output: 2.40
- name: MiniMax-M2.7
description: MiniMax-M2.7 - Previous flagship model with enhanced reasoning and coding capabilities. Supports tool calling, JSON output, and streaming.
thinking: false
price:
input: 0.40
output: 1.10
- name: MiniMax-M2.7-highspeed
description: MiniMax-M2.7-highspeed - High-speed version of M2.7 for low-latency scenarios. Same capabilities as M2.7 with improved response latency.
thinking: false
price:
input: 0.40
output: 1.10
@@ -30,6 +30,7 @@ const (
ProviderGLM ProviderType = "glm"
ProviderKimi ProviderType = "kimi"
ProviderQwen ProviderType = "qwen"
ProviderMiniMax ProviderType = "minimax"
)
type ProviderName string
@@ -49,6 +50,7 @@ const (
DefaultProviderNameGLM ProviderName = ProviderName(ProviderGLM)
DefaultProviderNameKimi ProviderName = ProviderName(ProviderKimi)
DefaultProviderNameQwen ProviderName = ProviderName(ProviderQwen)
DefaultProviderNameMiniMax ProviderName = ProviderName(ProviderMiniMax)
)
type Provider interface {
+26
View File
@@ -27,6 +27,7 @@ import (
"pentagi/pkg/providers/gemini"
"pentagi/pkg/providers/glm"
"pentagi/pkg/providers/kimi"
"pentagi/pkg/providers/minimax"
"pentagi/pkg/providers/ollama"
"pentagi/pkg/providers/openai"
"pentagi/pkg/providers/pconfig"
@@ -237,6 +238,12 @@ func NewProviderController(
defaultConfigs[provider.ProviderQwen] = config
}
if config, err := minimax.DefaultProviderConfig(); err != nil {
return nil, fmt.Errorf("failed to create minimax provider config: %w", err)
} else {
defaultConfigs[provider.ProviderMiniMax] = config
}
if cfg.OpenAIKey != "" {
p, err := openai.New(cfg, provider.DefaultProviderNameOpenAI, defaultConfigs[provider.ProviderOpenAI])
if err != nil {
@@ -330,6 +337,15 @@ func NewProviderController(
providers[provider.DefaultProviderNameQwen] = p
}
if cfg.MiniMaxAPIKey != "" {
p, err := minimax.New(cfg, provider.DefaultProviderNameMiniMax, defaultConfigs[provider.ProviderMiniMax])
if err != nil {
return nil, fmt.Errorf("failed to create minimax provider: %w", err)
}
providers[provider.DefaultProviderNameMiniMax] = p
}
summarizerAgent := csum.NewSummarizer(csum.SummarizerConfig{
PreserveLast: cfg.SummarizerPreserveLast,
UseQA: cfg.SummarizerUseQA,
@@ -753,6 +769,8 @@ func (pc *providerController) GetProvider(
return pc.Providers.Get(provider.DefaultProviderNameKimi)
case provider.DefaultProviderNameQwen:
return pc.Providers.Get(provider.DefaultProviderNameQwen)
case provider.DefaultProviderNameMiniMax:
return pc.Providers.Get(provider.DefaultProviderNameMiniMax)
}
return nil, fmt.Errorf("provider '%s' not found", prvname)
@@ -859,6 +877,12 @@ func (pc *providerController) NewProvider(prv database.Provider) (provider.Provi
return nil, fmt.Errorf("failed to build qwen provider config: %w", err)
}
return qwen.New(pc.cfg, providerName, qwenConfig)
case provider.ProviderMiniMax:
minimaxConfig, err := minimax.BuildProviderConfig(prv.Config)
if err != nil {
return nil, fmt.Errorf("failed to build minimax provider config: %w", err)
}
return minimax.New(pc.cfg, providerName, minimaxConfig)
default:
return nil, fmt.Errorf("unknown provider type: %s", prv.Type)
}
@@ -1243,6 +1267,8 @@ func (pc *providerController) buildProviderFromConfig(
return kimi.New(pc.cfg, prvname, config)
case provider.ProviderQwen:
return qwen.New(pc.cfg, prvname, config)
case provider.ProviderMiniMax:
return minimax.New(pc.cfg, prvname, config)
default:
return nil, fmt.Errorf("unknown provider type: %s", prvtype)
}
+2 -1
View File
@@ -29,7 +29,8 @@ func (s ProviderType) Valid() error {
provider.ProviderDeepSeek,
provider.ProviderGLM,
provider.ProviderKimi,
provider.ProviderQwen:
provider.ProviderQwen,
provider.ProviderMiniMax:
return nil
default:
return fmt.Errorf("invalid ProviderType: %s", s)