mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-29 06:00:52 +02:00
fix: normalize deepseek config profile
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -946,10 +947,12 @@ func (c *Config) ApplyDefaultAIChannel() {
|
|||||||
if c == nil {
|
if c == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.NormalizeAIProviderProfiles()
|
||||||
c.AI.EnsureDefaultFromOpenAI(c.OpenAI)
|
c.AI.EnsureDefaultFromOpenAI(c.OpenAI)
|
||||||
if oa, _, ok := c.AI.ResolveChannel(c.AI.DefaultChannel); ok {
|
if oa, _, ok := c.AI.ResolveChannel(c.AI.DefaultChannel); ok {
|
||||||
c.OpenAI = oa
|
c.OpenAI = oa
|
||||||
}
|
}
|
||||||
|
c.NormalizeAIProviderProfiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c OpenAIConfig) MaxCompletionTokensEffective() int {
|
func (c OpenAIConfig) MaxCompletionTokensEffective() int {
|
||||||
@@ -968,6 +971,50 @@ func (c OpenAIConfig) IsDeepSeekEndpointOrModel() bool {
|
|||||||
return strings.Contains(baseURL, "deepseek")
|
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 约束)。
|
// OpenAIReasoningConfig 全局默认与网关 profile(对话页可通过 ChatRequest.reasoning 覆盖,受 AllowClientReasoning 约束)。
|
||||||
type OpenAIReasoningConfig struct {
|
type OpenAIReasoningConfig struct {
|
||||||
// Mode: auto(默认)| on | off | default(与 auto 相同)。
|
// Mode: auto(默认)| on | off | default(与 auto 相同)。
|
||||||
@@ -1399,6 +1446,7 @@ func Load(path string) (*Config, error) {
|
|||||||
if cfg.Audit.MaxDetailBytes <= 0 {
|
if cfg.Audit.MaxDetailBytes <= 0 {
|
||||||
cfg.Audit.MaxDetailBytes = 8192
|
cfg.Audit.MaxDetailBytes = 8192
|
||||||
}
|
}
|
||||||
|
cfg.NormalizeAIProviderProfiles()
|
||||||
cfg.ApplyDefaultAIChannel()
|
cfg.ApplyDefaultAIChannel()
|
||||||
if err := validateOpenAIOutputLimits(cfg.OpenAI); err != nil {
|
if err := validateOpenAIOutputLimits(cfg.OpenAI); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -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) {
|
func TestSummarizationUserIntentLedgerRunesEffective(t *testing.T) {
|
||||||
var zero MultiAgentEinoMiddlewareConfig
|
var zero MultiAgentEinoMiddlewareConfig
|
||||||
if got := zero.SummarizationUserIntentLedgerMaxRunesEffective(); got != DefaultSummarizationUserIntentLedgerMaxRunes {
|
if got := zero.SummarizationUserIntentLedgerMaxRunesEffective(); got != DefaultSummarizationUserIntentLedgerMaxRunes {
|
||||||
|
|||||||
@@ -1169,6 +1169,8 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.config.NormalizeAIProviderProfiles()
|
||||||
|
|
||||||
// 保存配置到文件
|
// 保存配置到文件
|
||||||
if err := h.saveConfig(); err != nil {
|
if err := h.saveConfig(); err != nil {
|
||||||
h.logger.Error("保存配置失败", zap.Error(err))
|
h.logger.Error("保存配置失败", zap.Error(err))
|
||||||
@@ -1744,6 +1746,8 @@ func (h *ConfigHandler) ApplyConfig(c *gin.Context) {
|
|||||||
|
|
||||||
// saveConfig 保存配置到文件
|
// saveConfig 保存配置到文件
|
||||||
func (h *ConfigHandler) saveConfig() error {
|
func (h *ConfigHandler) saveConfig() error {
|
||||||
|
h.config.NormalizeAIProviderProfiles()
|
||||||
|
|
||||||
// 读取现有配置文件并创建备份
|
// 读取现有配置文件并创建备份
|
||||||
data, err := os.ReadFile(h.configPath)
|
data, err := os.ReadFile(h.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user