fix: normalize deepseek config profile

This commit is contained in:
temp
2026-08-25 18:37:08 +08:00
parent d7581a6373
commit d6d48c97c0
3 changed files with 130 additions and 0 deletions
+48
View File
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io/fs"
"net/url"
"os"
"path/filepath"
"strconv"
@@ -946,10 +947,12 @@ func (c *Config) ApplyDefaultAIChannel() {
if c == nil {
return
}
c.NormalizeAIProviderProfiles()
c.AI.EnsureDefaultFromOpenAI(c.OpenAI)
if oa, _, ok := c.AI.ResolveChannel(c.AI.DefaultChannel); ok {
c.OpenAI = oa
}
c.NormalizeAIProviderProfiles()
}
func (c OpenAIConfig) MaxCompletionTokensEffective() int {
@@ -968,6 +971,50 @@ func (c OpenAIConfig) IsDeepSeekEndpointOrModel() bool {
return strings.Contains(baseURL, "deepseek")
}
func (c OpenAIConfig) IsDeepSeekOfficialEndpoint() bool {
host := normalizedURLHost(c.BaseURL)
return host == "api.deepseek.com"
}
func normalizedURLHost(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return ""
}
parsed, err := url.Parse(raw)
if err != nil || parsed.Host == "" {
parsed, err = url.Parse("https://" + strings.TrimLeft(raw, "/"))
if err != nil {
return ""
}
}
return strings.ToLower(strings.TrimPrefix(parsed.Hostname(), "www."))
}
func NormalizeOpenAIProviderProfile(oa *OpenAIConfig) {
if oa == nil {
return
}
if oa.IsDeepSeekOfficialEndpoint() {
oa.Reasoning.Profile = "deepseek"
}
}
func (c *Config) NormalizeAIProviderProfiles() {
if c == nil {
return
}
NormalizeOpenAIProviderProfile(&c.OpenAI)
if c.AI.Channels != nil {
for id, ch := range c.AI.Channels {
oa := ch.ToOpenAIConfig()
NormalizeOpenAIProviderProfile(&oa)
ch.Reasoning = oa.Reasoning
c.AI.Channels[id] = ch
}
}
}
// OpenAIReasoningConfig 全局默认与网关 profile(对话页可通过 ChatRequest.reasoning 覆盖,受 AllowClientReasoning 约束)。
type OpenAIReasoningConfig struct {
// Mode: auto(默认)| on | off | default(与 auto 相同)。
@@ -1399,6 +1446,7 @@ func Load(path string) (*Config, error) {
if cfg.Audit.MaxDetailBytes <= 0 {
cfg.Audit.MaxDetailBytes = 8192
}
cfg.NormalizeAIProviderProfiles()
cfg.ApplyDefaultAIChannel()
if err := validateOpenAIOutputLimits(cfg.OpenAI); err != nil {
return nil, err
+78
View File
@@ -160,6 +160,84 @@ func TestLoadUsesAIDefaultChannelAsRuntimeOpenAI(t *testing.T) {
}
}
func TestNormalizeAIProviderProfilesForOfficialDeepSeekEndpoint(t *testing.T) {
cfg := &Config{
OpenAI: OpenAIConfig{
BaseURL: "https://api.deepseek.com/v1",
Model: "deepseek-chat",
Reasoning: OpenAIReasoningConfig{
Profile: "openai_compat",
},
},
AI: AIConfig{
Channels: map[string]AIChannelConfig{
"official": {
BaseURL: "api.deepseek.com/v1",
Model: "deepseek-chat",
Reasoning: OpenAIReasoningConfig{
Profile: "auto",
},
},
"gateway": {
BaseURL: "https://compatible.example.com/v1",
Model: "deepseek-chat",
Reasoning: OpenAIReasoningConfig{
Profile: "openai_compat",
},
},
},
},
}
cfg.NormalizeAIProviderProfiles()
if cfg.OpenAI.Reasoning.Profile != "deepseek" {
t.Fatalf("openai profile = %q, want deepseek", cfg.OpenAI.Reasoning.Profile)
}
if got := cfg.AI.Channels["official"].Reasoning.Profile; got != "deepseek" {
t.Fatalf("official channel profile = %q, want deepseek", got)
}
if got := cfg.AI.Channels["gateway"].Reasoning.Profile; got != "openai_compat" {
t.Fatalf("gateway profile should be preserved, got %q", got)
}
}
func TestLoadNormalizesDefaultChannelForOfficialDeepSeekEndpoint(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "config.yaml")
initial := strings.Join([]string{
"ai:",
" default_channel: deepseek",
" channels:",
" deepseek:",
" name: DeepSeek",
" provider: openai_compatible",
" base_url: https://api.deepseek.com/v1",
" api_key: deepseek-key",
" model: deepseek-chat",
" reasoning:",
" profile: openai_compat",
"server:",
" host: 127.0.0.1",
" port: 8080",
"",
}, "\n")
if err := os.WriteFile(path, []byte(initial), 0644); err != nil {
t.Fatalf("write config: %v", err)
}
cfg, err := Load(path)
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.OpenAI.Reasoning.Profile != "deepseek" {
t.Fatalf("runtime OpenAI profile = %q, want deepseek", cfg.OpenAI.Reasoning.Profile)
}
if got := cfg.AI.Channels["deepseek"].Reasoning.Profile; got != "deepseek" {
t.Fatalf("channel profile = %q, want deepseek", got)
}
}
func TestSummarizationUserIntentLedgerRunesEffective(t *testing.T) {
var zero MultiAgentEinoMiddlewareConfig
if got := zero.SummarizationUserIntentLedgerMaxRunesEffective(); got != DefaultSummarizationUserIntentLedgerMaxRunes {
+4
View File
@@ -1169,6 +1169,8 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
}
}
h.config.NormalizeAIProviderProfiles()
// 保存配置到文件
if err := h.saveConfig(); err != nil {
h.logger.Error("保存配置失败", zap.Error(err))
@@ -1744,6 +1746,8 @@ func (h *ConfigHandler) ApplyConfig(c *gin.Context) {
// saveConfig 保存配置到文件
func (h *ConfigHandler) saveConfig() error {
h.config.NormalizeAIProviderProfiles()
// 读取现有配置文件并创建备份
data, err := os.ReadFile(h.configPath)
if err != nil {