mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-28 21:50:43 +02:00
Respect OpenAI reasoning profile for DeepSeek-named models
This commit is contained in:
@@ -959,13 +959,12 @@ func (c OpenAIConfig) MaxCompletionTokensEffective() int {
|
||||
}
|
||||
|
||||
// IsDeepSeekEndpointOrModel reports whether the channel targets DeepSeek's
|
||||
// official-compatible API or a DeepSeek model family. This is separate from the
|
||||
// reasoning profile: profile controls field mapping, while DeepSeek has provider
|
||||
// constraints such as default thinking mode and no tool_choice in thinking mode.
|
||||
// official-compatible API endpoint. The historical name is kept for compatibility;
|
||||
// model names alone are not enough to infer DeepSeek wire behavior behind
|
||||
// OpenAI-compatible gateways.
|
||||
func (c OpenAIConfig) IsDeepSeekEndpointOrModel() bool {
|
||||
baseURL := strings.ToLower(strings.TrimSpace(c.BaseURL))
|
||||
model := strings.ToLower(strings.TrimSpace(c.Model))
|
||||
return strings.Contains(baseURL, "deepseek") || strings.Contains(model, "deepseek")
|
||||
return strings.Contains(baseURL, "deepseek")
|
||||
}
|
||||
|
||||
// OpenAIReasoningConfig 全局默认与网关 profile(对话页可通过 ChatRequest.reasoning 覆盖,受 AllowClientReasoning 约束)。
|
||||
|
||||
@@ -205,7 +205,7 @@ func TestReasoningToolChoiceCompatRoundTripperDeepSeek(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReasoningToolChoiceCompatRoundTripperDeepSeekEndpointWinsOverProfile(t *testing.T) {
|
||||
func TestReasoningToolChoiceCompatRoundTripperOpenAIProfileWinsOverDeepSeekEndpoint(t *testing.T) {
|
||||
var gotBody string
|
||||
rt := &reasoningToolChoiceCompatRoundTripper{
|
||||
cfg: &config.OpenAIConfig{
|
||||
@@ -235,11 +235,11 @@ func TestReasoningToolChoiceCompatRoundTripperDeepSeekEndpointWinsOverProfile(t
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(gotBody, "tool_choice") {
|
||||
t.Fatalf("expected DeepSeek tool_choice stripped despite openai_compat profile, got %s", gotBody)
|
||||
if !strings.Contains(gotBody, "tool_choice") {
|
||||
t.Fatalf("expected tool_choice preserved for explicit openai_compat profile, got %s", gotBody)
|
||||
}
|
||||
if !strings.Contains(gotBody, "tools") {
|
||||
t.Fatalf("expected tools preserved for DeepSeek, got %s", gotBody)
|
||||
t.Fatalf("expected tools preserved for explicit openai_compat profile, got %s", gotBody)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,9 +55,6 @@ func isDeepSeekToolChoiceCompatProfile(cfg *config.OpenAIConfig) bool {
|
||||
if cfg == nil {
|
||||
return false
|
||||
}
|
||||
if cfg.IsDeepSeekEndpointOrModel() {
|
||||
return true
|
||||
}
|
||||
profile := strings.ToLower(strings.TrimSpace(cfg.Reasoning.ProfileEffective()))
|
||||
if profile == "deepseek" || profile == "deepseek_compat" {
|
||||
return true
|
||||
@@ -65,5 +62,5 @@ func isDeepSeekToolChoiceCompatProfile(cfg *config.OpenAIConfig) bool {
|
||||
if profile != "" && profile != "auto" {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
return cfg.IsDeepSeekEndpointOrModel()
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func ApplyPlanExecutePlannerModelConfig(cfg *einoopenai.ChatModelConfig, oa *con
|
||||
}
|
||||
mergeExtraRequestFields(cfg, oa.Reasoning.ExtraRequestFields)
|
||||
clearReasoningFromChatModelConfig(cfg)
|
||||
if resolveWireProfile(oa, &oa.Reasoning) == wireDeepseek || oa.IsDeepSeekEndpointOrModel() {
|
||||
if resolveWireProfile(oa, &oa.Reasoning) == wireDeepseek {
|
||||
// DeepSeek enables thinking by default, so omission would not actually
|
||||
// disable it for the planner's forced tool-choice requests.
|
||||
applyThinkingDisabled(cfg)
|
||||
@@ -88,9 +88,9 @@ func ApplyToEinoChatModelConfig(cfg *einoopenai.ChatModelConfig, oa *config.Open
|
||||
clearReasoningFromChatModelConfig(cfg)
|
||||
// Strict OpenAI endpoints reject unknown `thinking` fields, whereas the
|
||||
// DeepSeek API enables thinking by default and requires an explicit
|
||||
// thinking.type=disabled switch. Detect the actual DeepSeek target even
|
||||
// when the configured reasoning profile was left as openai_compat.
|
||||
if resolveWireProfile(oa, sr) == wireDeepseek || oa.IsDeepSeekEndpointOrModel() {
|
||||
// thinking.type=disabled switch. The configured profile is authoritative;
|
||||
// auto-detection only happens inside resolveWireProfile for profile=auto.
|
||||
if resolveWireProfile(oa, sr) == wireDeepseek {
|
||||
applyThinkingDisabled(cfg)
|
||||
}
|
||||
return
|
||||
@@ -132,7 +132,7 @@ func AgenticOpenAIExtraFields(oa *config.OpenAIConfig, client *ClientIntent) map
|
||||
fields := cloneExtraRequestFields(sr.ExtraRequestFields)
|
||||
if mode == "off" {
|
||||
clearReasoningExtraFields(fields)
|
||||
if resolveWireProfile(oa, sr) == wireDeepseek || oa.IsDeepSeekEndpointOrModel() {
|
||||
if resolveWireProfile(oa, sr) == wireDeepseek {
|
||||
if fields == nil {
|
||||
fields = make(map[string]any)
|
||||
}
|
||||
@@ -194,7 +194,7 @@ func AgenticOpenAIPlannerExtraFields(oa *config.OpenAIConfig) map[string]any {
|
||||
}
|
||||
fields := cloneExtraRequestFields(oa.Reasoning.ExtraRequestFields)
|
||||
clearReasoningExtraFields(fields)
|
||||
if resolveWireProfile(oa, &oa.Reasoning) == wireDeepseek || oa.IsDeepSeekEndpointOrModel() {
|
||||
if resolveWireProfile(oa, &oa.Reasoning) == wireDeepseek {
|
||||
if fields == nil {
|
||||
fields = make(map[string]any)
|
||||
}
|
||||
|
||||
+110
-19
@@ -140,7 +140,7 @@ func TestAgenticOpenAIPlannerExtraFields_deepseekDisablesThinking(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgenticOpenAIPlannerExtraFields_deepseekEndpointWinsOverOpenAIProfile(t *testing.T) {
|
||||
func TestAgenticOpenAIPlannerExtraFields_openAIProfileWinsOverDeepseekEndpoint(t *testing.T) {
|
||||
oa := &config.OpenAIConfig{
|
||||
BaseURL: "https://api.deepseek.com/v1",
|
||||
Model: "deepseek-v4-flash",
|
||||
@@ -155,12 +155,10 @@ func TestAgenticOpenAIPlannerExtraFields_deepseekEndpointWinsOverOpenAIProfile(t
|
||||
},
|
||||
}
|
||||
got := AgenticOpenAIPlannerExtraFields(oa)
|
||||
if _, ok := got["reasoning_effort"]; ok {
|
||||
t.Fatalf("planner should strip reasoning_effort: %#v", got)
|
||||
}
|
||||
thinking, ok := got["thinking"].(map[string]any)
|
||||
if !ok || thinking["type"] != "disabled" {
|
||||
t.Fatalf("expected deepseek thinking disabled despite openai_compat profile, got %#v", got)
|
||||
for _, key := range reasoningPayloadKeysForTest {
|
||||
if _, ok := got[key]; ok {
|
||||
t.Fatalf("planner fields unexpectedly contain %q: %#v", key, got)
|
||||
}
|
||||
}
|
||||
if got["vendor_option"] != true {
|
||||
t.Fatalf("vendor option not preserved: %#v", got)
|
||||
@@ -189,7 +187,7 @@ func TestApplyPlanExecutePlannerModelConfig_stripsReasoningWhenGlobalOn(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyPlanExecutePlannerModelConfig_deepseekEndpointWinsOverOpenAIProfile(t *testing.T) {
|
||||
func TestApplyPlanExecutePlannerModelConfig_openAIProfileWinsOverDeepseekEndpoint(t *testing.T) {
|
||||
cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{
|
||||
"thinking": map[string]any{"type": "enabled"},
|
||||
"reasoning_effort": "high",
|
||||
@@ -205,16 +203,7 @@ func TestApplyPlanExecutePlannerModelConfig_deepseekEndpointWinsOverOpenAIProfil
|
||||
},
|
||||
}
|
||||
ApplyPlanExecutePlannerModelConfig(cfg, oa)
|
||||
if cfg.ReasoningEffort != "" {
|
||||
t.Fatalf("expected ReasoningEffort omitted, got %q", cfg.ReasoningEffort)
|
||||
}
|
||||
if _, ok := cfg.ExtraFields["reasoning_effort"]; ok {
|
||||
t.Fatalf("expected reasoning_effort omitted, got %#v", cfg.ExtraFields)
|
||||
}
|
||||
thinking, ok := cfg.ExtraFields["thinking"].(map[string]any)
|
||||
if !ok || thinking["type"] != "disabled" {
|
||||
t.Fatalf("expected deepseek thinking disabled despite openai_compat profile, got %#v", cfg.ExtraFields)
|
||||
}
|
||||
assertNoReasoningFields(t, cfg)
|
||||
if cfg.ExtraFields["vendor_option"] != true {
|
||||
t.Fatalf("expected unrelated extra field preserved, got %#v", cfg.ExtraFields)
|
||||
}
|
||||
@@ -246,6 +235,89 @@ func TestApplyReasoningOff_omitsAllReasoningFields(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyReasoningOff_openAICompatDeepseekModelOmitsAllReasoningFields(t *testing.T) {
|
||||
allowClient := false
|
||||
cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{
|
||||
"thinking": map[string]any{"type": "enabled"},
|
||||
"reasoning_effort": "high",
|
||||
}}
|
||||
oa := &config.OpenAIConfig{
|
||||
Provider: "openai_compatible",
|
||||
BaseURL: "http://your-gateway:port/v1",
|
||||
Model: "deepseek-v4-flash-0731",
|
||||
Reasoning: config.OpenAIReasoningConfig{
|
||||
Mode: "off",
|
||||
Effort: "high",
|
||||
Profile: "openai_compat",
|
||||
AllowClientReasoning: &allowClient,
|
||||
ExtraRequestFields: map[string]interface{}{
|
||||
"thinking": map[string]any{"type": "disabled"},
|
||||
"output_config": map[string]any{"effort": "high"},
|
||||
"vendor_option": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
ApplyToEinoChatModelConfig(cfg, oa, nil)
|
||||
assertNoReasoningFields(t, cfg)
|
||||
if cfg.ExtraFields["vendor_option"] != true {
|
||||
t.Fatalf("expected unrelated extra field preserved, got %#v", cfg.ExtraFields)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgenticOpenAIExtraFields_openAICompatDeepseekModelOmitsAllReasoningFields(t *testing.T) {
|
||||
oa := &config.OpenAIConfig{
|
||||
Provider: "openai_compatible",
|
||||
BaseURL: "http://your-gateway:port/v1",
|
||||
Model: "deepseek-v4-flash-0731",
|
||||
Reasoning: config.OpenAIReasoningConfig{
|
||||
Mode: "off",
|
||||
Effort: "high",
|
||||
Profile: "openai_compat",
|
||||
ExtraRequestFields: map[string]interface{}{
|
||||
"thinking": map[string]any{"type": "disabled"},
|
||||
"reasoning_effort": "high",
|
||||
"vendor_option": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
got := AgenticOpenAIExtraFields(oa, nil)
|
||||
for _, key := range reasoningPayloadKeysForTest {
|
||||
if _, ok := got[key]; ok {
|
||||
t.Fatalf("agentic fields unexpectedly contain %q: %#v", key, got)
|
||||
}
|
||||
}
|
||||
if got["vendor_option"] != true {
|
||||
t.Fatalf("vendor option not preserved: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgenticOpenAIPlannerExtraFields_openAICompatDeepseekModelOmitsAllReasoningFields(t *testing.T) {
|
||||
oa := &config.OpenAIConfig{
|
||||
Provider: "openai_compatible",
|
||||
BaseURL: "http://your-gateway:port/v1",
|
||||
Model: "deepseek-v4-flash-0731",
|
||||
Reasoning: config.OpenAIReasoningConfig{
|
||||
Mode: "on",
|
||||
Effort: "high",
|
||||
Profile: "openai_compat",
|
||||
ExtraRequestFields: map[string]interface{}{
|
||||
"thinking": map[string]any{"type": "enabled"},
|
||||
"reasoning_effort": "high",
|
||||
"vendor_option": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
got := AgenticOpenAIPlannerExtraFields(oa)
|
||||
for _, key := range reasoningPayloadKeysForTest {
|
||||
if _, ok := got[key]; ok {
|
||||
t.Fatalf("planner fields unexpectedly contain %q: %#v", key, got)
|
||||
}
|
||||
}
|
||||
if got["vendor_option"] != true {
|
||||
t.Fatalf("vendor option not preserved: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyReasoningOff_clientOverrideOmit(t *testing.T) {
|
||||
cfg := &einoopenai.ChatModelConfig{}
|
||||
oa := &config.OpenAIConfig{Reasoning: config.OpenAIReasoningConfig{
|
||||
@@ -256,7 +328,7 @@ func TestApplyReasoningOff_clientOverrideOmit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestApplyReasoningOff_deepseekExplicitlyDisablesDefaultThinking(t *testing.T) {
|
||||
for _, profile := range []string{"deepseek_compat", "auto", "openai_compat"} {
|
||||
for _, profile := range []string{"deepseek_compat", "auto"} {
|
||||
t.Run(profile, func(t *testing.T) {
|
||||
cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{
|
||||
"reasoning_effort": "high",
|
||||
@@ -287,6 +359,25 @@ func TestApplyReasoningOff_deepseekExplicitlyDisablesDefaultThinking(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyReasoningOff_openAIProfileWinsOverDeepseekEndpoint(t *testing.T) {
|
||||
cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{
|
||||
"reasoning_effort": "high",
|
||||
"vendor_option": true,
|
||||
}}
|
||||
oa := &config.OpenAIConfig{
|
||||
BaseURL: "https://api.deepseek.com",
|
||||
Model: "deepseek-v4-pro",
|
||||
Reasoning: config.OpenAIReasoningConfig{
|
||||
Mode: "off", Effort: "high", Profile: "openai_compat",
|
||||
},
|
||||
}
|
||||
ApplyToEinoChatModelConfig(cfg, oa, nil)
|
||||
assertNoReasoningFields(t, cfg)
|
||||
if cfg.ExtraFields["vendor_option"] != true {
|
||||
t.Fatalf("expected unrelated extra field preserved, got %#v", cfg.ExtraFields)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyReasoningOff_wirePayloadOmitsThinking(t *testing.T) {
|
||||
var requestBody map[string]any
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user