mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 20:30:51 +08:00
fix(thinking): support user-defined includeThoughts setting with camelCase and snake_case variants
Fixes #1378
This commit is contained in:
@@ -116,7 +116,16 @@ func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig)
|
|||||||
|
|
||||||
level := string(config.Level)
|
level := string(config.Level)
|
||||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingLevel", level)
|
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingLevel", level)
|
||||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", true)
|
|
||||||
|
// Respect user's explicit includeThoughts setting from original body; default to true if not set
|
||||||
|
// Support both camelCase and snake_case variants
|
||||||
|
includeThoughts := true
|
||||||
|
if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
|
||||||
|
includeThoughts = inc.Bool()
|
||||||
|
} else if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.include_thoughts"); inc.Exists() {
|
||||||
|
includeThoughts = inc.Bool()
|
||||||
|
}
|
||||||
|
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts)
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,14 +138,29 @@ func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig,
|
|||||||
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts")
|
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts")
|
||||||
|
|
||||||
budget := config.Budget
|
budget := config.Budget
|
||||||
includeThoughts := false
|
|
||||||
switch config.Mode {
|
// Determine includeThoughts: respect user's explicit setting from original body if provided
|
||||||
case thinking.ModeNone:
|
// Support both camelCase and snake_case variants
|
||||||
includeThoughts = false
|
var includeThoughts bool
|
||||||
case thinking.ModeAuto:
|
var userSetIncludeThoughts bool
|
||||||
includeThoughts = true
|
if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
|
||||||
default:
|
includeThoughts = inc.Bool()
|
||||||
includeThoughts = budget > 0
|
userSetIncludeThoughts = true
|
||||||
|
} else if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.include_thoughts"); inc.Exists() {
|
||||||
|
includeThoughts = inc.Bool()
|
||||||
|
userSetIncludeThoughts = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userSetIncludeThoughts {
|
||||||
|
// No explicit setting, use default logic based on mode
|
||||||
|
switch config.Mode {
|
||||||
|
case thinking.ModeNone:
|
||||||
|
includeThoughts = false
|
||||||
|
case thinking.ModeAuto:
|
||||||
|
includeThoughts = true
|
||||||
|
default:
|
||||||
|
includeThoughts = budget > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply Claude-specific constraints
|
// Apply Claude-specific constraints
|
||||||
|
|||||||
@@ -140,7 +140,16 @@ func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig)
|
|||||||
|
|
||||||
level := string(config.Level)
|
level := string(config.Level)
|
||||||
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.thinkingLevel", level)
|
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.thinkingLevel", level)
|
||||||
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.includeThoughts", true)
|
|
||||||
|
// Respect user's explicit includeThoughts setting from original body; default to true if not set
|
||||||
|
// Support both camelCase and snake_case variants
|
||||||
|
includeThoughts := true
|
||||||
|
if inc := gjson.GetBytes(body, "generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
|
||||||
|
includeThoughts = inc.Bool()
|
||||||
|
} else if inc := gjson.GetBytes(body, "generationConfig.thinkingConfig.include_thoughts"); inc.Exists() {
|
||||||
|
includeThoughts = inc.Bool()
|
||||||
|
}
|
||||||
|
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.includeThoughts", includeThoughts)
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,18 +162,33 @@ func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig)
|
|||||||
result, _ = sjson.DeleteBytes(result, "generationConfig.thinkingConfig.include_thoughts")
|
result, _ = sjson.DeleteBytes(result, "generationConfig.thinkingConfig.include_thoughts")
|
||||||
|
|
||||||
budget := config.Budget
|
budget := config.Budget
|
||||||
// ModeNone semantics:
|
|
||||||
// - ModeNone + Budget=0: completely disable thinking
|
// Determine includeThoughts: respect user's explicit setting from original body if provided
|
||||||
// - ModeNone + Budget>0: forced to think but hide output (includeThoughts=false)
|
// Support both camelCase and snake_case variants
|
||||||
// When ZeroAllowed=false, ValidateConfig clamps Budget to Min while preserving ModeNone.
|
var includeThoughts bool
|
||||||
includeThoughts := false
|
var userSetIncludeThoughts bool
|
||||||
switch config.Mode {
|
if inc := gjson.GetBytes(body, "generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
|
||||||
case thinking.ModeNone:
|
includeThoughts = inc.Bool()
|
||||||
includeThoughts = false
|
userSetIncludeThoughts = true
|
||||||
case thinking.ModeAuto:
|
} else if inc := gjson.GetBytes(body, "generationConfig.thinkingConfig.include_thoughts"); inc.Exists() {
|
||||||
includeThoughts = true
|
includeThoughts = inc.Bool()
|
||||||
default:
|
userSetIncludeThoughts = true
|
||||||
includeThoughts = budget > 0
|
}
|
||||||
|
|
||||||
|
if !userSetIncludeThoughts {
|
||||||
|
// No explicit setting, use default logic based on mode
|
||||||
|
// ModeNone semantics:
|
||||||
|
// - ModeNone + Budget=0: completely disable thinking
|
||||||
|
// - ModeNone + Budget>0: forced to think but hide output (includeThoughts=false)
|
||||||
|
// When ZeroAllowed=false, ValidateConfig clamps Budget to Min while preserving ModeNone.
|
||||||
|
switch config.Mode {
|
||||||
|
case thinking.ModeNone:
|
||||||
|
includeThoughts = false
|
||||||
|
case thinking.ModeAuto:
|
||||||
|
includeThoughts = true
|
||||||
|
default:
|
||||||
|
includeThoughts = budget > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.thinkingBudget", budget)
|
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.thinkingBudget", budget)
|
||||||
|
|||||||
@@ -101,7 +101,16 @@ func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig)
|
|||||||
|
|
||||||
level := string(config.Level)
|
level := string(config.Level)
|
||||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingLevel", level)
|
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingLevel", level)
|
||||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", true)
|
|
||||||
|
// Respect user's explicit includeThoughts setting from original body; default to true if not set
|
||||||
|
// Support both camelCase and snake_case variants
|
||||||
|
includeThoughts := true
|
||||||
|
if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
|
||||||
|
includeThoughts = inc.Bool()
|
||||||
|
} else if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.include_thoughts"); inc.Exists() {
|
||||||
|
includeThoughts = inc.Bool()
|
||||||
|
}
|
||||||
|
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts)
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,14 +123,29 @@ func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig)
|
|||||||
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts")
|
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts")
|
||||||
|
|
||||||
budget := config.Budget
|
budget := config.Budget
|
||||||
includeThoughts := false
|
|
||||||
switch config.Mode {
|
// Determine includeThoughts: respect user's explicit setting from original body if provided
|
||||||
case thinking.ModeNone:
|
// Support both camelCase and snake_case variants
|
||||||
includeThoughts = false
|
var includeThoughts bool
|
||||||
case thinking.ModeAuto:
|
var userSetIncludeThoughts bool
|
||||||
includeThoughts = true
|
if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
|
||||||
default:
|
includeThoughts = inc.Bool()
|
||||||
includeThoughts = budget > 0
|
userSetIncludeThoughts = true
|
||||||
|
} else if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.include_thoughts"); inc.Exists() {
|
||||||
|
includeThoughts = inc.Bool()
|
||||||
|
userSetIncludeThoughts = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userSetIncludeThoughts {
|
||||||
|
// No explicit setting, use default logic based on mode
|
||||||
|
switch config.Mode {
|
||||||
|
case thinking.ModeNone:
|
||||||
|
includeThoughts = false
|
||||||
|
case thinking.ModeAuto:
|
||||||
|
includeThoughts = true
|
||||||
|
default:
|
||||||
|
includeThoughts = budget > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
|
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
|
||||||
|
|||||||
@@ -1441,28 +1441,6 @@ func TestThinkingE2EMatrix_Body(t *testing.T) {
|
|||||||
expectValue: "medium",
|
expectValue: "medium",
|
||||||
expectErr: false,
|
expectErr: false,
|
||||||
},
|
},
|
||||||
// Case 9001: thinking_budget=64000 (snake_case) → high (Gemini -> Codex)
|
|
||||||
{
|
|
||||||
name: "9001",
|
|
||||||
from: "gemini",
|
|
||||||
to: "codex",
|
|
||||||
model: "level-model",
|
|
||||||
inputJSON: `{"model":"level-model","contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinking_budget":64000}}}`,
|
|
||||||
expectField: "reasoning.effort",
|
|
||||||
expectValue: "high",
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
// Case 9002: thinking_level=high (snake_case) → reasoning_effort=high (Gemini -> OpenAI)
|
|
||||||
{
|
|
||||||
name: "9002",
|
|
||||||
from: "gemini",
|
|
||||||
to: "openai",
|
|
||||||
model: "level-model",
|
|
||||||
inputJSON: `{"model":"level-model","contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinking_level":"high"}}}`,
|
|
||||||
expectField: "reasoning_effort",
|
|
||||||
expectValue: "high",
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
// Case 11: Claude no param → passthrough (no thinking)
|
// Case 11: Claude no param → passthrough (no thinking)
|
||||||
{
|
{
|
||||||
name: "11",
|
name: "11",
|
||||||
@@ -1473,17 +1451,6 @@ func TestThinkingE2EMatrix_Body(t *testing.T) {
|
|||||||
expectField: "",
|
expectField: "",
|
||||||
expectErr: false,
|
expectErr: false,
|
||||||
},
|
},
|
||||||
// Case 9003: thinking_budget=8192 (snake_case) → thinking.budget_tokens=8192 (Gemini -> Claude)
|
|
||||||
{
|
|
||||||
name: "9003",
|
|
||||||
from: "gemini",
|
|
||||||
to: "claude",
|
|
||||||
model: "level-model",
|
|
||||||
inputJSON: `{"model":"level-model","contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinking_budget":8192}}}`,
|
|
||||||
expectField: "thinking.budget_tokens",
|
|
||||||
expectValue: "8192",
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
// Case 12: thinking.budget_tokens=8192 → medium
|
// Case 12: thinking.budget_tokens=8192 → medium
|
||||||
{
|
{
|
||||||
name: "12",
|
name: "12",
|
||||||
@@ -1557,19 +1524,6 @@ func TestThinkingE2EMatrix_Body(t *testing.T) {
|
|||||||
|
|
||||||
// gemini-budget-model (Min=128, Max=20000, ZeroAllowed=false, DynamicAllowed=true)
|
// gemini-budget-model (Min=128, Max=20000, ZeroAllowed=false, DynamicAllowed=true)
|
||||||
|
|
||||||
// Case 9004: thinking_budget=8192 (snake_case) → passthrough+normalize to thinkingBudget (Gemini -> Gemini)
|
|
||||||
{
|
|
||||||
name: "9004",
|
|
||||||
from: "gemini",
|
|
||||||
to: "gemini",
|
|
||||||
model: "gemini-budget-model",
|
|
||||||
inputJSON: `{"model":"gemini-budget-model","contents":[{"role":"user","parts":[{"text":"hi"}]}],"generationConfig":{"thinkingConfig":{"thinking_budget":8192}}}`,
|
|
||||||
expectField: "generationConfig.thinkingConfig.thinkingBudget",
|
|
||||||
expectValue: "8192",
|
|
||||||
includeThoughts: "true",
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Case 18: No param → passthrough
|
// Case 18: No param → passthrough
|
||||||
{
|
{
|
||||||
name: "18",
|
name: "18",
|
||||||
|
|||||||
Reference in New Issue
Block a user