Respect OpenAI reasoning profile for DeepSeek-named models

This commit is contained in:
temp
2026-08-24 13:55:07 +08:00
parent e218316c55
commit d80e27e950
5 changed files with 125 additions and 38 deletions
+4 -5
View File
@@ -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 约束)。
+4 -4
View File
@@ -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()
}
+6 -6
View File
@@ -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
View File
@@ -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) {