Merge pull request #541 from edouard-claude/feat/mistral-provider

feat: add native Mistral AI provider support
This commit is contained in:
美電球 2026-02-23 21:06:26 +08:00 committed by GitHub
commit ae74fa3812
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 60 additions and 9 deletions

View file

@ -196,6 +196,10 @@
"volcengine": { "volcengine": {
"api_key": "", "api_key": "",
"api_base": "" "api_base": ""
},
"mistral": {
"api_key": "",
"api_base": "https://api.mistral.ai/v1"
} }
}, },
"tools": { "tools": {

View file

@ -324,6 +324,7 @@ type ProvidersConfig struct {
GitHubCopilot ProviderConfig `json:"github_copilot"` GitHubCopilot ProviderConfig `json:"github_copilot"`
Antigravity ProviderConfig `json:"antigravity"` Antigravity ProviderConfig `json:"antigravity"`
Qwen ProviderConfig `json:"qwen"` Qwen ProviderConfig `json:"qwen"`
Mistral ProviderConfig `json:"mistral"`
} }
// IsEmpty checks if all provider configs are empty (no API keys or API bases set) // IsEmpty checks if all provider configs are empty (no API keys or API bases set)
@ -345,7 +346,8 @@ func (p ProvidersConfig) IsEmpty() bool {
p.VolcEngine.APIKey == "" && p.VolcEngine.APIBase == "" && p.VolcEngine.APIKey == "" && p.VolcEngine.APIBase == "" &&
p.GitHubCopilot.APIKey == "" && p.GitHubCopilot.APIBase == "" && p.GitHubCopilot.APIKey == "" && p.GitHubCopilot.APIBase == "" &&
p.Antigravity.APIKey == "" && p.Antigravity.APIBase == "" && p.Antigravity.APIKey == "" && p.Antigravity.APIBase == "" &&
p.Qwen.APIKey == "" && p.Qwen.APIBase == "" p.Qwen.APIKey == "" && p.Qwen.APIBase == "" &&
p.Mistral.APIKey == "" && p.Mistral.APIBase == ""
} }
// MarshalJSON implements custom JSON marshaling for ProvidersConfig // MarshalJSON implements custom JSON marshaling for ProvidersConfig
@ -644,7 +646,8 @@ func (c *Config) HasProvidersConfig() bool {
v.VolcEngine.APIKey != "" || v.VolcEngine.APIBase != "" || v.VolcEngine.APIKey != "" || v.VolcEngine.APIBase != "" ||
v.GitHubCopilot.APIKey != "" || v.GitHubCopilot.APIBase != "" || v.GitHubCopilot.APIKey != "" || v.GitHubCopilot.APIBase != "" ||
v.Antigravity.APIKey != "" || v.Antigravity.APIBase != "" || v.Antigravity.APIKey != "" || v.Antigravity.APIBase != "" ||
v.Qwen.APIKey != "" || v.Qwen.APIBase != "" v.Qwen.APIKey != "" || v.Qwen.APIBase != "" ||
v.Mistral.APIKey != "" || v.Mistral.APIBase != ""
} }
// ValidateModelList validates all ModelConfig entries in the model_list. // ValidateModelList validates all ModelConfig entries in the model_list.

View file

@ -255,6 +255,14 @@ func DefaultConfig() *Config {
APIKey: "ollama", APIKey: "ollama",
}, },
// Mistral AI - https://console.mistral.ai/api-keys
{
ModelName: "mistral-small",
Model: "mistral/mistral-small-latest",
APIBase: "https://api.mistral.ai/v1",
APIKey: "",
},
// VLLM (local) - http://localhost:8000 // VLLM (local) - http://localhost:8000
{ {
ModelName: "local-model", ModelName: "local-model",

View file

@ -324,6 +324,22 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig {
}, true }, true
}, },
}, },
{
providerNames: []string{"mistral"},
protocol: "mistral",
buildConfig: func(p ProvidersConfig) (ModelConfig, bool) {
if p.Mistral.APIKey == "" && p.Mistral.APIBase == "" {
return ModelConfig{}, false
}
return ModelConfig{
ModelName: "mistral",
Model: "mistral/mistral-small-latest",
APIKey: p.Mistral.APIKey,
APIBase: p.Mistral.APIBase,
Proxy: p.Mistral.Proxy,
}, true
},
},
} }
// Process each provider migration // Process each provider migration

View file

@ -131,14 +131,15 @@ func TestConvertProvidersToModelList_AllProviders(t *testing.T) {
GitHubCopilot: ProviderConfig{ConnectMode: "grpc"}, GitHubCopilot: ProviderConfig{ConnectMode: "grpc"},
Antigravity: ProviderConfig{AuthMethod: "oauth"}, Antigravity: ProviderConfig{AuthMethod: "oauth"},
Qwen: ProviderConfig{APIKey: "key17"}, Qwen: ProviderConfig{APIKey: "key17"},
Mistral: ProviderConfig{APIKey: "key18"},
}, },
} }
result := ConvertProvidersToModelList(cfg) result := ConvertProvidersToModelList(cfg)
// All 17 providers should be converted // All 18 providers should be converted
if len(result) != 17 { if len(result) != 18 {
t.Errorf("len(result) = %d, want 17", len(result)) t.Errorf("len(result) = %d, want 18", len(result))
} }
} }

View file

@ -22,6 +22,7 @@ var supportedProviders = map[string]bool{
"qwen": true, "qwen": true,
"deepseek": true, "deepseek": true,
"github_copilot": true, "github_copilot": true,
"mistral": true,
} }
var supportedChannels = map[string]bool{ var supportedChannels = map[string]bool{

View file

@ -172,6 +172,15 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
sel.model = "deepseek-chat" sel.model = "deepseek-chat"
} }
} }
case "mistral":
if cfg.Providers.Mistral.APIKey != "" {
sel.apiKey = cfg.Providers.Mistral.APIKey
sel.apiBase = cfg.Providers.Mistral.APIBase
sel.proxy = cfg.Providers.Mistral.Proxy
if sel.apiBase == "" {
sel.apiBase = "https://api.mistral.ai/v1"
}
}
case "github_copilot", "copilot": case "github_copilot", "copilot":
sel.providerType = providerTypeGitHubCopilot sel.providerType = providerTypeGitHubCopilot
if cfg.Providers.GitHubCopilot.APIBase != "" { if cfg.Providers.GitHubCopilot.APIBase != "" {
@ -275,6 +284,13 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
if sel.apiBase == "" { if sel.apiBase == "" {
sel.apiBase = "http://localhost:11434/v1" sel.apiBase = "http://localhost:11434/v1"
} }
case (strings.Contains(lowerModel, "mistral") || strings.HasPrefix(model, "mistral/")) && cfg.Providers.Mistral.APIKey != "":
sel.apiKey = cfg.Providers.Mistral.APIKey
sel.apiBase = cfg.Providers.Mistral.APIBase
sel.proxy = cfg.Providers.Mistral.Proxy
if sel.apiBase == "" {
sel.apiBase = "https://api.mistral.ai/v1"
}
case cfg.Providers.VLLM.APIBase != "": case cfg.Providers.VLLM.APIBase != "":
sel.apiKey = cfg.Providers.VLLM.APIKey sel.apiKey = cfg.Providers.VLLM.APIKey
sel.apiBase = cfg.Providers.VLLM.APIBase sel.apiBase = cfg.Providers.VLLM.APIBase

View file

@ -88,7 +88,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
case "openrouter", "groq", "zhipu", "gemini", "nvidia", case "openrouter", "groq", "zhipu", "gemini", "nvidia",
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
"volcengine", "vllm", "qwen": "volcengine", "vllm", "qwen", "mistral":
// All other OpenAI-compatible HTTP providers // All other OpenAI-compatible HTTP providers
if cfg.APIKey == "" && cfg.APIBase == "" { if cfg.APIKey == "" && cfg.APIBase == "" {
return nil, "", fmt.Errorf("api_key or api_base is required for HTTP-based protocol %q", protocol) return nil, "", fmt.Errorf("api_key or api_base is required for HTTP-based protocol %q", protocol)
@ -186,6 +186,8 @@ func getDefaultAPIBase(protocol string) string {
return "https://dashscope.aliyuncs.com/compatible-mode/v1" return "https://dashscope.aliyuncs.com/compatible-mode/v1"
case "vllm": case "vllm":
return "http://localhost:8000/v1" return "http://localhost:8000/v1"
case "mistral":
return "https://api.mistral.ai/v1"
default: default:
return "" return ""
} }

View file

@ -240,7 +240,7 @@ func normalizeModel(model, apiBase string) string {
prefix := strings.ToLower(model[:idx]) prefix := strings.ToLower(model[:idx])
switch prefix { switch prefix {
case "moonshot", "nvidia", "groq", "ollama", "deepseek", "google", "openrouter", "zhipu": case "moonshot", "nvidia", "groq", "ollama", "deepseek", "google", "openrouter", "zhipu", "mistral":
return model[idx+1:] return model[idx+1:]
default: default:
return model return model

View file

@ -4,8 +4,8 @@ type ToolCall struct {
ID string `json:"id"` ID string `json:"id"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Function *FunctionCall `json:"function,omitempty"` Function *FunctionCall `json:"function,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"-"`
Arguments map[string]any `json:"arguments,omitempty"` Arguments map[string]any `json:"-"`
ThoughtSignature string `json:"-"` // Internal use only ThoughtSignature string `json:"-"` // Internal use only
ExtraContent *ExtraContent `json:"extra_content,omitempty"` ExtraContent *ExtraContent `json:"extra_content,omitempty"`
} }