Merge pull request #1429 from neavo/fix/gemini-python-sdk-thinking-fields

fix(gemini): support snake_case thinking config fields from Python SDK
This commit is contained in:
Chén Mù
2026-02-05 14:32:58 +08:00
committed by GitHub
7 changed files with 211 additions and 68 deletions

View File

@@ -388,7 +388,12 @@ func extractGeminiConfig(body []byte, provider string) ThinkingConfig {
} }
// Check thinkingLevel first (Gemini 3 format takes precedence) // Check thinkingLevel first (Gemini 3 format takes precedence)
if level := gjson.GetBytes(body, prefix+".thinkingLevel"); level.Exists() { level := gjson.GetBytes(body, prefix+".thinkingLevel")
if !level.Exists() {
// Google official Gemini Python SDK sends snake_case field names
level = gjson.GetBytes(body, prefix+".thinking_level")
}
if level.Exists() {
value := level.String() value := level.String()
switch value { switch value {
case "none": case "none":
@@ -401,7 +406,12 @@ func extractGeminiConfig(body []byte, provider string) ThinkingConfig {
} }
// Check thinkingBudget (Gemini 2.5 format) // Check thinkingBudget (Gemini 2.5 format)
if budget := gjson.GetBytes(body, prefix+".thinkingBudget"); budget.Exists() { budget := gjson.GetBytes(body, prefix+".thinkingBudget")
if !budget.Exists() {
// Google official Gemini Python SDK sends snake_case field names
budget = gjson.GetBytes(body, prefix+".thinking_budget")
}
if budget.Exists() {
value := int(budget.Int()) value := int(budget.Int())
switch value { switch value {
case 0: case 0:

View File

@@ -94,8 +94,10 @@ func (a *Applier) applyCompatible(body []byte, config thinking.ThinkingConfig, m
} }
func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) { func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) {
// Remove conflicting field to avoid both thinkingLevel and thinkingBudget in output // Remove conflicting fields to avoid both thinkingLevel and thinkingBudget in output
result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingBudget") result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingBudget")
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.thinking_budget")
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.thinking_level")
// Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing. // Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing.
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts") result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts")
@@ -114,28 +116,30 @@ 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
} }
func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo, isClaude bool) ([]byte, error) { func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo, isClaude bool) ([]byte, error) {
// Remove conflicting field to avoid both thinkingLevel and thinkingBudget in output // Remove conflicting fields to avoid both thinkingLevel and thinkingBudget in output
result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingLevel") result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingLevel")
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.thinking_level")
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.thinking_budget")
// Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing. // Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing.
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 {
case thinking.ModeNone:
includeThoughts = false
case thinking.ModeAuto:
includeThoughts = true
default:
includeThoughts = budget > 0
}
// Apply Claude-specific constraints // Apply Claude-specific constraints first to get the final budget value
if isClaude && modelInfo != nil { if isClaude && modelInfo != nil {
budget, result = a.normalizeClaudeBudget(budget, result, modelInfo) budget, result = a.normalizeClaudeBudget(budget, result, modelInfo)
// Check if budget was removed entirely // Check if budget was removed entirely
@@ -144,6 +148,37 @@ func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig,
} }
} }
// For ModeNone, always set includeThoughts to false regardless of user setting.
// This ensures that when user requests budget=0 (disable thinking output),
// the includeThoughts is correctly set to false even if budget is clamped to min.
if config.Mode == thinking.ModeNone {
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", false)
return result, nil
}
// Determine includeThoughts: respect user's explicit setting from original body if provided
// Support both camelCase and snake_case variants
var includeThoughts bool
var userSetIncludeThoughts bool
if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
includeThoughts = inc.Bool()
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.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)
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts) result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts)
return result, nil return result, nil

View File

@@ -118,8 +118,10 @@ func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig)
// - ModeNone + Budget>0: forced to think but hide output (includeThoughts=false) // - ModeNone + Budget>0: forced to think but hide output (includeThoughts=false)
// ValidateConfig sets config.Level to the lowest level when ModeNone + Budget > 0. // ValidateConfig sets config.Level to the lowest level when ModeNone + Budget > 0.
// Remove conflicting field to avoid both thinkingLevel and thinkingBudget in output // Remove conflicting fields to avoid both thinkingLevel and thinkingBudget in output
result, _ := sjson.DeleteBytes(body, "generationConfig.thinkingConfig.thinkingBudget") result, _ := sjson.DeleteBytes(body, "generationConfig.thinkingConfig.thinkingBudget")
result, _ = sjson.DeleteBytes(result, "generationConfig.thinkingConfig.thinking_budget")
result, _ = sjson.DeleteBytes(result, "generationConfig.thinkingConfig.thinking_level")
// Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing. // Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing.
result, _ = sjson.DeleteBytes(result, "generationConfig.thinkingConfig.include_thoughts") result, _ = sjson.DeleteBytes(result, "generationConfig.thinkingConfig.include_thoughts")
@@ -138,30 +140,59 @@ 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
} }
func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) { func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) {
// Remove conflicting field to avoid both thinkingLevel and thinkingBudget in output // Remove conflicting fields to avoid both thinkingLevel and thinkingBudget in output
result, _ := sjson.DeleteBytes(body, "generationConfig.thinkingConfig.thinkingLevel") result, _ := sjson.DeleteBytes(body, "generationConfig.thinkingConfig.thinkingLevel")
result, _ = sjson.DeleteBytes(result, "generationConfig.thinkingConfig.thinking_level")
result, _ = sjson.DeleteBytes(result, "generationConfig.thinkingConfig.thinking_budget")
// Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing. // Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing.
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 // For ModeNone, always set includeThoughts to false regardless of user setting.
// - ModeNone + Budget>0: forced to think but hide output (includeThoughts=false) // This ensures that when user requests budget=0 (disable thinking output),
// When ZeroAllowed=false, ValidateConfig clamps Budget to Min while preserving ModeNone. // the includeThoughts is correctly set to false even if budget is clamped to min.
includeThoughts := false if config.Mode == thinking.ModeNone {
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.thinkingBudget", budget)
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.includeThoughts", false)
return result, nil
}
// Determine includeThoughts: respect user's explicit setting from original body if provided
// Support both camelCase and snake_case variants
var includeThoughts bool
var userSetIncludeThoughts bool
if inc := gjson.GetBytes(body, "generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
includeThoughts = inc.Bool()
userSetIncludeThoughts = true
} else if inc := gjson.GetBytes(body, "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 { switch config.Mode {
case thinking.ModeNone:
includeThoughts = false
case thinking.ModeAuto: case thinking.ModeAuto:
includeThoughts = true includeThoughts = true
default: default:
includeThoughts = budget > 0 includeThoughts = budget > 0
} }
}
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.thinkingBudget", budget) result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.thinkingBudget", budget)
result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.includeThoughts", includeThoughts) result, _ = sjson.SetBytes(result, "generationConfig.thinkingConfig.includeThoughts", includeThoughts)

View File

@@ -79,8 +79,10 @@ func (a *Applier) applyCompatible(body []byte, config thinking.ThinkingConfig) (
} }
func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) { func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) {
// Remove conflicting field to avoid both thinkingLevel and thinkingBudget in output // Remove conflicting fields to avoid both thinkingLevel and thinkingBudget in output
result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingBudget") result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingBudget")
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.thinking_budget")
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.thinking_level")
// Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing. // Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing.
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts") result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts")
@@ -99,26 +101,59 @@ 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
} }
func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) { func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) {
// Remove conflicting field to avoid both thinkingLevel and thinkingBudget in output // Remove conflicting fields to avoid both thinkingLevel and thinkingBudget in output
result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingLevel") result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingLevel")
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.thinking_level")
result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.thinking_budget")
// Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing. // Normalize includeThoughts field name to avoid oneof conflicts in upstream JSON parsing.
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
// For ModeNone, always set includeThoughts to false regardless of user setting.
// This ensures that when user requests budget=0 (disable thinking output),
// the includeThoughts is correctly set to false even if budget is clamped to min.
if config.Mode == thinking.ModeNone {
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", false)
return result, nil
}
// Determine includeThoughts: respect user's explicit setting from original body if provided
// Support both camelCase and snake_case variants
var includeThoughts bool
var userSetIncludeThoughts bool
if inc := gjson.GetBytes(body, "request.generationConfig.thinkingConfig.includeThoughts"); inc.Exists() {
includeThoughts = inc.Bool()
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 { switch config.Mode {
case thinking.ModeNone:
includeThoughts = false
case thinking.ModeAuto: case thinking.ModeAuto:
includeThoughts = true includeThoughts = true
default: default:
includeThoughts = budget > 0 includeThoughts = budget > 0
} }
}
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget", budget) result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts) result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts)

View File

@@ -116,7 +116,11 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream
// Include thoughts configuration for reasoning process visibility // Include thoughts configuration for reasoning process visibility
// Translator only does format conversion, ApplyThinking handles model capability validation. // Translator only does format conversion, ApplyThinking handles model capability validation.
if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() {
if thinkingLevel := thinkingConfig.Get("thinkingLevel"); thinkingLevel.Exists() { thinkingLevel := thinkingConfig.Get("thinkingLevel")
if !thinkingLevel.Exists() {
thinkingLevel = thinkingConfig.Get("thinking_level")
}
if thinkingLevel.Exists() {
level := strings.ToLower(strings.TrimSpace(thinkingLevel.String())) level := strings.ToLower(strings.TrimSpace(thinkingLevel.String()))
switch level { switch level {
case "": case "":
@@ -132,7 +136,12 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream
out, _ = sjson.Set(out, "thinking.budget_tokens", budget) out, _ = sjson.Set(out, "thinking.budget_tokens", budget)
} }
} }
} else if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() { } else {
thinkingBudget := thinkingConfig.Get("thinkingBudget")
if !thinkingBudget.Exists() {
thinkingBudget = thinkingConfig.Get("thinking_budget")
}
if thinkingBudget.Exists() {
budget := int(thinkingBudget.Int()) budget := int(thinkingBudget.Int())
switch budget { switch budget {
case 0: case 0:
@@ -152,6 +161,7 @@ func ConvertGeminiRequestToClaude(modelName string, inputRawJSON []byte, stream
} }
} }
} }
}
// System instruction conversion to Claude Code format // System instruction conversion to Claude Code format
if sysInstr := root.Get("system_instruction"); sysInstr.Exists() { if sysInstr := root.Get("system_instruction"); sysInstr.Exists() {

View File

@@ -243,16 +243,26 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
out, _ = sjson.Set(out, "parallel_tool_calls", true) out, _ = sjson.Set(out, "parallel_tool_calls", true)
// Convert Gemini thinkingConfig to Codex reasoning.effort. // Convert Gemini thinkingConfig to Codex reasoning.effort.
// Note: Google official Python SDK sends snake_case fields (thinking_level/thinking_budget).
effortSet := false effortSet := false
if genConfig := root.Get("generationConfig"); genConfig.Exists() { if genConfig := root.Get("generationConfig"); genConfig.Exists() {
if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() {
if thinkingLevel := thinkingConfig.Get("thinkingLevel"); thinkingLevel.Exists() { thinkingLevel := thinkingConfig.Get("thinkingLevel")
if !thinkingLevel.Exists() {
thinkingLevel = thinkingConfig.Get("thinking_level")
}
if thinkingLevel.Exists() {
effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String())) effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String()))
if effort != "" { if effort != "" {
out, _ = sjson.Set(out, "reasoning.effort", effort) out, _ = sjson.Set(out, "reasoning.effort", effort)
effortSet = true effortSet = true
} }
} else if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() { } else {
thinkingBudget := thinkingConfig.Get("thinkingBudget")
if !thinkingBudget.Exists() {
thinkingBudget = thinkingConfig.Get("thinking_budget")
}
if thinkingBudget.Exists() {
if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok { if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok {
out, _ = sjson.Set(out, "reasoning.effort", effort) out, _ = sjson.Set(out, "reasoning.effort", effort)
effortSet = true effortSet = true
@@ -260,6 +270,7 @@ func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool)
} }
} }
} }
}
if !effortSet { if !effortSet {
// No thinking config, set default effort // No thinking config, set default effort
out, _ = sjson.Set(out, "reasoning.effort", "medium") out, _ = sjson.Set(out, "reasoning.effort", "medium")

View File

@@ -83,20 +83,31 @@ func ConvertGeminiRequestToOpenAI(modelName string, inputRawJSON []byte, stream
} }
// Map Gemini thinkingConfig to OpenAI reasoning_effort. // Map Gemini thinkingConfig to OpenAI reasoning_effort.
// Always perform conversion to support allowCompat models that may not be in registry // Always perform conversion to support allowCompat models that may not be in registry.
// Note: Google official Python SDK sends snake_case fields (thinking_level/thinking_budget).
if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() {
if thinkingLevel := thinkingConfig.Get("thinkingLevel"); thinkingLevel.Exists() { thinkingLevel := thinkingConfig.Get("thinkingLevel")
if !thinkingLevel.Exists() {
thinkingLevel = thinkingConfig.Get("thinking_level")
}
if thinkingLevel.Exists() {
effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String())) effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String()))
if effort != "" { if effort != "" {
out, _ = sjson.Set(out, "reasoning_effort", effort) out, _ = sjson.Set(out, "reasoning_effort", effort)
} }
} else if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() { } else {
thinkingBudget := thinkingConfig.Get("thinkingBudget")
if !thinkingBudget.Exists() {
thinkingBudget = thinkingConfig.Get("thinking_budget")
}
if thinkingBudget.Exists() {
if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok { if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok {
out, _ = sjson.Set(out, "reasoning_effort", effort) out, _ = sjson.Set(out, "reasoning_effort", effort)
} }
} }
} }
} }
}
// Stream parameter // Stream parameter
out, _ = sjson.Set(out, "stream", stream) out, _ = sjson.Set(out, "stream", stream)