Fix deepseek model max_tokens validation error

This commit is contained in:
liugangjian 2026-03-05 09:02:19 +08:00
parent 028605cfd0
commit 1abcc95ed6
4 changed files with 127 additions and 7 deletions

View file

@ -217,7 +217,9 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
strings.HasPrefix(model, "openai/") || strings.HasPrefix(model, "openai/") ||
strings.HasPrefix(model, "meta-llama/") || strings.HasPrefix(model, "meta-llama/") ||
strings.HasPrefix(model, "deepseek/") || strings.HasPrefix(model, "deepseek/") ||
strings.HasPrefix(model, "deepseek-ai/") ||
strings.HasPrefix(model, "google/"): strings.HasPrefix(model, "google/"):
sel.apiKey = cfg.Providers.OpenRouter.APIKey sel.apiKey = cfg.Providers.OpenRouter.APIKey
sel.proxy = cfg.Providers.OpenRouter.Proxy sel.proxy = cfg.Providers.OpenRouter.Proxy
if cfg.Providers.OpenRouter.APIBase != "" { if cfg.Providers.OpenRouter.APIBase != "" {
@ -279,7 +281,35 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
if sel.apiBase == "" { if sel.apiBase == "" {
sel.apiBase = "https://api.groq.com/openai/v1" sel.apiBase = "https://api.groq.com/openai/v1"
} }
case (strings.Contains(lowerModel, "deepseek") || strings.HasPrefix(model, "deepseek/") || strings.HasPrefix(model, "deepseek-ai/")) && cfg.Providers.DeepSeek.APIKey != "":
sel.apiKey = cfg.Providers.DeepSeek.APIKey
sel.apiBase = cfg.Providers.DeepSeek.APIBase
sel.proxy = cfg.Providers.DeepSeek.Proxy
if sel.apiBase == "" {
sel.apiBase = "https://api.deepseek.com/v1"
}
// Keep original model name logic for backward compatibility
if !strings.HasPrefix(model, "deepseek/") && !strings.HasPrefix(model, "deepseek-ai/") {
if model != "deepseek-chat" && model != "deepseek-reasoner" {
sel.model = "deepseek-chat"
}
}
case (strings.Contains(lowerModel, "nvidia") || strings.HasPrefix(model, "nvidia/")) && cfg.Providers.Nvidia.APIKey != "": case (strings.Contains(lowerModel, "nvidia") || strings.HasPrefix(model, "nvidia/")) && cfg.Providers.Nvidia.APIKey != "":
sel.apiKey = cfg.Providers.Nvidia.APIKey sel.apiKey = cfg.Providers.Nvidia.APIKey
sel.apiBase = cfg.Providers.Nvidia.APIBase sel.apiBase = cfg.Providers.Nvidia.APIBase
sel.proxy = cfg.Providers.Nvidia.Proxy sel.proxy = cfg.Providers.Nvidia.Proxy

View file

@ -55,6 +55,13 @@ func ExtractProtocol(model string) (protocol, modelID string) {
// It uses the protocol prefix in the Model field to determine which provider to create. // It uses the protocol prefix in the Model field to determine which provider to create.
// Supported protocols: openai, litellm, anthropic, antigravity, claude-cli, codex-cli, github-copilot // Supported protocols: openai, litellm, anthropic, antigravity, claude-cli, codex-cli, github-copilot
// Returns the provider, the model ID (without protocol prefix), and any error. // Returns the provider, the model ID (without protocol prefix), and any error.
// ExtractProtocol extracts the protocol prefix and model identifier from a model string.
// If no prefix is specified, it defaults to "openai".
// Examples:
// - "openai/gpt-4o" -> ("openai", "gpt-4o")
// - "anthropic/claude-sonnet-4.6" -> ("anthropic", "claude-sonnet-4.6")
// - "gpt-4o" -> ("openai", "gpt-4o") // default protocol
func ExtractProtocol(model string) (protocol, modelID string) {
func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, error) { func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, error) {
if cfg == nil { if cfg == nil {
return nil, "", fmt.Errorf("config is nil") return nil, "", fmt.Errorf("config is nil")
@ -84,12 +91,17 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
if apiBase == "" { if apiBase == "" {
apiBase = getDefaultAPIBase(protocol) apiBase = getDefaultAPIBase(protocol)
} }
// For Ollama models, default to higher timeout if none specified
requestTimeout := cfg.RequestTimeout
if protocol == "ollama" && requestTimeout <= 0 {
requestTimeout = 300 // Ollama models often need more time, default to 300 seconds
}
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout( return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
cfg.APIKey, cfg.APIKey,
apiBase, apiBase,
cfg.Proxy, cfg.Proxy,
cfg.MaxTokensField, cfg.MaxTokensField,
cfg.RequestTimeout, requestTimeout,
), modelID, nil ), modelID, nil
case "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia", case "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia",
@ -103,12 +115,17 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
if apiBase == "" { if apiBase == "" {
apiBase = getDefaultAPIBase(protocol) apiBase = getDefaultAPIBase(protocol)
} }
// For Ollama models, default to higher timeout if none specified
requestTimeout := cfg.RequestTimeout
if protocol == "ollama" && requestTimeout <= 0 {
requestTimeout = 300 // Ollama models often need more time, default to 300 seconds
}
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout( return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
cfg.APIKey, cfg.APIKey,
apiBase, apiBase,
cfg.Proxy, cfg.Proxy,
cfg.MaxTokensField, cfg.MaxTokensField,
cfg.RequestTimeout, requestTimeout,
), modelID, nil ), modelID, nil
case "anthropic": case "anthropic":
@ -128,12 +145,17 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
if cfg.APIKey == "" { if cfg.APIKey == "" {
return nil, "", fmt.Errorf("api_key is required for anthropic protocol (model: %s)", cfg.Model) return nil, "", fmt.Errorf("api_key is required for anthropic protocol (model: %s)", cfg.Model)
} }
// For Ollama models, default to higher timeout if none specified
requestTimeout := cfg.RequestTimeout
if protocol == "ollama" && requestTimeout <= 0 {
requestTimeout = 300 // Ollama models often need more time, default to 300 seconds
}
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout( return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
cfg.APIKey, cfg.APIKey,
apiBase, apiBase,
cfg.Proxy, cfg.Proxy,
cfg.MaxTokensField, cfg.MaxTokensField,
cfg.RequestTimeout, requestTimeout,
), modelID, nil ), modelID, nil
case "antigravity": case "antigravity":

View file

@ -179,9 +179,64 @@ func TestResolveProviderSelection(t *testing.T) {
setup: func(cfg *config.Config) { setup: func(cfg *config.Config) {
cfg.Agents.Defaults.Model = "openrouter/auto" cfg.Agents.Defaults.Model = "openrouter/auto"
}, },
wantErrSubstr: "no API key configured for provider", {
name: "deepseek-ai model with DeepSeek API key uses DeepSeek API",
setup: func(cfg *config.Config) {
cfg.Agents.Defaults.Model = "deepseek-ai/DeepSeek-V3.2"
cfg.Providers.DeepSeek.APIKey = "deepseek-key"
cfg.Providers.DeepSeek.APIBase = "https://api.deepseek.com/v1"
}, },
}
wantType: providerTypeHTTPCompat,
wantAPIBase: "https://api.deepseek.com/v1",
},
{
name: "deepseek-ai prefix model routes to DeepSeek when key configured",
setup: func(cfg *config.Config) {
cfg.Agents.Defaults.Model = "deepseek-ai/deepseek-coder"
cfg.Providers.DeepSeek.APIKey = "deepseek-key"
},
wantType: providerTypeHTTPCompat,
wantAPIBase: "https://api.deepseek.com/v1",
},
{
name: "deepseek model still works for backward compatibility",
setup: func(cfg *config.Config) {
cfg.Agents.Defaults.Model = "deepseek/deepseek-chat"
cfg.Providers.DeepSeek.APIKey = "deepseek-key"
},
wantType: providerTypeHTTPCompat,
wantAPIBase: "https://api.deepseek.com/v1",
},
}
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -137,8 +137,21 @@ func (p *Provider) Chat(
fieldName = "max_tokens" fieldName = "max_tokens"
} }
} }
// Validate max_tokens range for deepseek model
if strings.Contains(strings.ToLower(model), "deepseek") {
// Clamp max_tokens to the valid range [1, 8192]
adjustedMaxTokens := maxTokens
if maxTokens > 8192 {
adjustedMaxTokens = 8192
}
if maxTokens < 1 {
adjustedMaxTokens = 1
}
requestBody[fieldName] = adjustedMaxTokens
} else {
requestBody[fieldName] = maxTokens requestBody[fieldName] = maxTokens
} }
}
if temperature, ok := asFloat(options["temperature"]); ok { if temperature, ok := asFloat(options["temperature"]); ok {
lowerModel := strings.ToLower(model) lowerModel := strings.ToLower(model)