Add files via upload

This commit is contained in:
公明
2026-08-15 01:51:13 +08:00
committed by GitHub
parent 20b6fd241e
commit 5ce66ee2f8
2 changed files with 236 additions and 7 deletions
+106 -6
View File
@@ -36,7 +36,7 @@ func ApplyPlanExecutePlannerModelConfig(cfg *einoopenai.ChatModelConfig, oa *con
} }
mergeExtraRequestFields(cfg, oa.Reasoning.ExtraRequestFields) mergeExtraRequestFields(cfg, oa.Reasoning.ExtraRequestFields)
clearReasoningFromChatModelConfig(cfg) clearReasoningFromChatModelConfig(cfg)
if resolveWireProfile(oa, &oa.Reasoning) == wireDeepseek { if resolveWireProfile(oa, &oa.Reasoning) == wireDeepseek || oa.IsDeepSeekEndpointOrModel() {
// DeepSeek enables thinking by default, so omission would not actually // DeepSeek enables thinking by default, so omission would not actually
// disable it for the planner's forced tool-choice requests. // disable it for the planner's forced tool-choice requests.
applyThinkingDisabled(cfg) applyThinkingDisabled(cfg)
@@ -88,8 +88,9 @@ func ApplyToEinoChatModelConfig(cfg *einoopenai.ChatModelConfig, oa *config.Open
clearReasoningFromChatModelConfig(cfg) clearReasoningFromChatModelConfig(cfg)
// Strict OpenAI endpoints reject unknown `thinking` fields, whereas the // Strict OpenAI endpoints reject unknown `thinking` fields, whereas the
// DeepSeek API enables thinking by default and requires an explicit // DeepSeek API enables thinking by default and requires an explicit
// thinking.type=disabled switch. Keep that wire difference profile-scoped. // thinking.type=disabled switch. Detect the actual DeepSeek target even
if resolveWireProfile(oa, sr) == wireDeepseek { // when the configured reasoning profile was left as openai_compat.
if resolveWireProfile(oa, sr) == wireDeepseek || oa.IsDeepSeekEndpointOrModel() {
applyThinkingDisabled(cfg) applyThinkingDisabled(cfg)
} }
return return
@@ -118,6 +119,107 @@ func ApplyToEinoChatModelConfig(cfg *einoopenai.ChatModelConfig, oa *config.Open
} }
} }
// AgenticOpenAIExtraFields returns reasoning-related request fields for
// agenticopenai.ChatConfig. The agentic chat backend currently exposes provider
// extensions through ExtraFields instead of typed ReasoningEffort fields.
func AgenticOpenAIExtraFields(oa *config.OpenAIConfig, client *ClientIntent) map[string]any {
if oa == nil {
return nil
}
sr := &oa.Reasoning
allowClient := sr.AllowClientReasoningEffective()
mode := effectiveMode(sr, client, allowClient)
fields := cloneExtraRequestFields(sr.ExtraRequestFields)
if mode == "off" {
clearReasoningExtraFields(fields)
if resolveWireProfile(oa, sr) == wireDeepseek || oa.IsDeepSeekEndpointOrModel() {
if fields == nil {
fields = make(map[string]any)
}
fields["thinking"] = map[string]any{"type": "disabled"}
}
return fields
}
if strings.EqualFold(strings.TrimSpace(oa.Provider), "claude") ||
strings.EqualFold(strings.TrimSpace(oa.Provider), "anthropic") {
return fields
}
effort := effectiveEffort(sr, client, allowClient)
switch resolveWireProfile(oa, sr) {
case wireDeepseek:
if mode == "auto" || mode == "on" {
if fields == nil {
fields = make(map[string]any)
}
fields["thinking"] = map[string]any{"type": "enabled"}
}
if effort != "" {
if fields == nil {
fields = make(map[string]any)
}
fields["reasoning_effort"] = effortStringForAPI(effort)
}
case wireOutputConfig:
e := effort
if mode == "on" && e == "" {
e = "high"
}
if e != "" {
if fields == nil {
fields = make(map[string]any)
}
fields["output_config"] = map[string]any{"effort": effortStringForAPI(e)}
}
default:
e := effort
if mode == "on" && e == "" {
e = "medium"
}
if e != "" {
if fields == nil {
fields = make(map[string]any)
}
fields["reasoning_effort"] = effortStringForAPI(e)
}
}
return fields
}
// AgenticOpenAIPlannerExtraFields mirrors ApplyPlanExecutePlannerModelConfig for
// agenticopenai.ChatConfig: keep admin extras, strip reasoning controls, and
// explicitly disable DeepSeek thinking where omission would still think.
func AgenticOpenAIPlannerExtraFields(oa *config.OpenAIConfig) map[string]any {
if oa == nil {
return nil
}
fields := cloneExtraRequestFields(oa.Reasoning.ExtraRequestFields)
clearReasoningExtraFields(fields)
if resolveWireProfile(oa, &oa.Reasoning) == wireDeepseek || oa.IsDeepSeekEndpointOrModel() {
if fields == nil {
fields = make(map[string]any)
}
fields["thinking"] = map[string]any{"type": "disabled"}
}
return fields
}
func cloneExtraRequestFields(fields map[string]interface{}) map[string]any {
if len(fields) == 0 {
return nil
}
out := make(map[string]any, len(fields))
for k, v := range fields {
out[k] = v
}
return out
}
func clearReasoningExtraFields(fields map[string]any) {
for _, key := range []string{"thinking", "reasoning_effort", "output_config", "reasoning"} {
delete(fields, key)
}
}
// applyClaudeExtendedThinking sets Anthropic Messages API fields per official guidance: // applyClaudeExtendedThinking sets Anthropic Messages API fields per official guidance:
// - Adaptive models (4.6+): thinking.type=adaptive; output_config.effort only when user sets effort (API default is high). // - Adaptive models (4.6+): thinking.type=adaptive; output_config.effort only when user sets effort (API default is high).
// - Sonnet 3.7: thinking.type=enabled + budget_tokens=10000 (doc example); effort is not mapped — use extra_request_fields for custom budget. // - Sonnet 3.7: thinking.type=enabled + budget_tokens=10000 (doc example); effort is not mapped — use extra_request_fields for custom budget.
@@ -240,9 +342,7 @@ func resolveWireProfile(oa *config.OpenAIConfig, sr *config.OpenAIReasoningConfi
case "deepseek", "deepseek_compat": case "deepseek", "deepseek_compat":
return wireDeepseek return wireDeepseek
case "auto", "": case "auto", "":
bu := strings.ToLower(oa.BaseURL) if oa.IsDeepSeekEndpointOrModel() {
mo := strings.ToLower(oa.Model)
if strings.Contains(bu, "deepseek") || strings.Contains(mo, "deepseek") {
return wireDeepseek return wireDeepseek
} }
return wireOpenAI return wireOpenAI
+130 -1
View File
@@ -69,6 +69,104 @@ func TestApplyOpenAICompat_xhighExtraField(t *testing.T) {
} }
} }
func TestAgenticOpenAIExtraFields_openAICompatReasoningEffort(t *testing.T) {
oa := &config.OpenAIConfig{
Reasoning: config.OpenAIReasoningConfig{
Profile: "openai_compat",
Mode: "on",
Effort: "high",
ExtraRequestFields: map[string]interface{}{
"vendor_option": true,
},
},
}
got := AgenticOpenAIExtraFields(oa, nil)
if got["reasoning_effort"] != "high" {
t.Fatalf("reasoning_effort=%#v, want high in %#v", got["reasoning_effort"], got)
}
if got["vendor_option"] != true {
t.Fatalf("vendor option not preserved: %#v", got)
}
}
func TestAgenticOpenAIExtraFields_reasoningOffPreservesUnrelatedFields(t *testing.T) {
oa := &config.OpenAIConfig{
Model: "gpt-4o-mini",
Reasoning: config.OpenAIReasoningConfig{
Profile: "openai_compat",
Mode: "off",
Effort: "high",
ExtraRequestFields: map[string]interface{}{
"reasoning_effort": "high",
"thinking": map[string]any{"type": "enabled"},
"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_deepseekDisablesThinking(t *testing.T) {
oa := &config.OpenAIConfig{
BaseURL: "https://api.deepseek.com",
Model: "deepseek-chat",
Reasoning: config.OpenAIReasoningConfig{
Profile: "auto",
Mode: "on",
ExtraRequestFields: map[string]interface{}{
"reasoning_effort": "high",
"vendor_option": true,
},
},
}
got := AgenticOpenAIPlannerExtraFields(oa)
if got["reasoning_effort"] != nil {
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, got %#v", got)
}
if got["vendor_option"] != true {
t.Fatalf("vendor option not preserved: %#v", got)
}
}
func TestAgenticOpenAIPlannerExtraFields_deepseekEndpointWinsOverOpenAIProfile(t *testing.T) {
oa := &config.OpenAIConfig{
BaseURL: "https://api.deepseek.com/v1",
Model: "deepseek-v4-flash",
Reasoning: config.OpenAIReasoningConfig{
Profile: "openai_compat",
Mode: "on",
Effort: "high",
ExtraRequestFields: map[string]interface{}{
"reasoning_effort": "high",
"vendor_option": true,
},
},
}
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)
}
if got["vendor_option"] != true {
t.Fatalf("vendor option not preserved: %#v", got)
}
}
func TestApplyPlanExecutePlannerModelConfig_stripsReasoningWhenGlobalOn(t *testing.T) { func TestApplyPlanExecutePlannerModelConfig_stripsReasoningWhenGlobalOn(t *testing.T) {
cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{ cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{
"thinking": map[string]any{"type": "enabled"}, "thinking": map[string]any{"type": "enabled"},
@@ -91,6 +189,37 @@ func TestApplyPlanExecutePlannerModelConfig_stripsReasoningWhenGlobalOn(t *testi
} }
} }
func TestApplyPlanExecutePlannerModelConfig_deepseekEndpointWinsOverOpenAIProfile(t *testing.T) {
cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{
"thinking": map[string]any{"type": "enabled"},
"reasoning_effort": "high",
"vendor_option": true,
}}
oa := &config.OpenAIConfig{
BaseURL: "https://api.deepseek.com/v1",
Model: "deepseek-v4-flash",
Reasoning: config.OpenAIReasoningConfig{
Profile: "openai_compat",
Mode: "on",
Effort: "high",
},
}
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)
}
if cfg.ExtraFields["vendor_option"] != true {
t.Fatalf("expected unrelated extra field preserved, got %#v", cfg.ExtraFields)
}
}
func TestApplyReasoningOff_omitsAllReasoningFields(t *testing.T) { func TestApplyReasoningOff_omitsAllReasoningFields(t *testing.T) {
cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{ cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{
"thinking": map[string]any{"type": "enabled"}, "thinking": map[string]any{"type": "enabled"},
@@ -127,7 +256,7 @@ func TestApplyReasoningOff_clientOverrideOmit(t *testing.T) {
} }
func TestApplyReasoningOff_deepseekExplicitlyDisablesDefaultThinking(t *testing.T) { func TestApplyReasoningOff_deepseekExplicitlyDisablesDefaultThinking(t *testing.T) {
for _, profile := range []string{"deepseek_compat", "auto"} { for _, profile := range []string{"deepseek_compat", "auto", "openai_compat"} {
t.Run(profile, func(t *testing.T) { t.Run(profile, func(t *testing.T) {
cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{ cfg := &einoopenai.ChatModelConfig{ExtraFields: map[string]any{
"reasoning_effort": "high", "reasoning_effort": "high",